2011-12-19

在 Android 裡使用 AsyncTask 來處理費時的運算

由於 Android 有五秒的 ANR 限制,所以像是網路存取(HttpGetHttpPost)這類費時的運算,就不適合由 Main Thread 處理。

因為 Android UI 不是 Thread safe,因此所有的 UI 操作都得透過 Main Thread,而當費時的運算過程或者結果不會影響到 UI 的話,那只要起一個 Thread 去處理就可以了,但是當過程或結果需要與 UI 互動時,就得用上 AsyncTask。

AsyncTask 會起一個背景執行的 Thread,並提供前中後三個 callback 供開發者使用。

至於 AsyncTask 與同樣可以更新 UI 的 Handler + Worker Thread 的差異,改天再做研究。

main.xml
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="fill_parent"
    android:layout_height="fill_parent"
    android:orientation="vertical" >

    <Button
        android:layout_width="fill_parent"
        android:layout_height="wrap_content"
        android:onClick="onClick"
        android:text="開始計算" />

    <TextView
        android:id="@+id/tv"
        android:layout_width="fill_parent"
        android:layout_height="wrap_content" />

</LinearLayout>
Calculator
public class Calculator extends AsyncTask<List<Integer>, Integer, Integer> {

    private static final String TAG = "Calculator";
    private Context ctx;
    private TextView tv;
    private Integer result;
    private Integer progress;

    public Calculator(Context ctx) {
        this.setContext(ctx);
    }

    public void setContext(Context ctx) {
        // 用來更新 UI
        this.ctx = ctx;
        this.tv = (TextView) ((Activity) this.ctx).findViewById(R.id.tv);
        this.progressUI();
    }

    @Override
    protected Integer doInBackground(List<Integer>... params) {
        LogUtils.thread("執行計算...");
        // 在 Worker Thread 裡執行,所以不可更新 UI
        this.result = 0;
        Integer p;
        for (int i = 0; i < params[0].size(); i++) {
            p = params[0].get(i);
            this.result += p.intValue();
            LogUtils.thread("計算過程..." + this.result);
            // 更新進度
            this.publishProgress(100 * (i + 1) / params[0].size());
            try {
                Thread.sleep(1000);
            }
            catch (InterruptedException e) {
            }
        }
        LogUtils.thread("執行計算完成..." + this.result);
        return this.result;
    }

    @Override
    protected void onPreExecute() {
        super.onPreExecute();
        LogUtils.thread("計算前...");
        // 在 Main Thread 裡執行,可更新 UI
        this.progress = 0;
    }

    @Override
    protected void onPostExecute(Integer result) {
        super.onPostExecute(result);
        this.result = result;
        LogUtils.thread("計算結果..." + this.result);
        // 在 Main Thread 裡執行,可更新 UI
        this.syncUI();
    }

    @Override
    protected void onProgressUpdate(Integer... values) {
        super.onProgressUpdate(values);
        this.progress = values[0];
        LogUtils.thread("更新進度..." + this.progress);
        // 在 Main Thread 裡執行,可更新 UI
        this.progressUI();
    }

    public void syncUI() {
        LogUtils.thread("更新 UI...");
        this.tv.setText("計算結果..." + this.result);
    }

    private void progressUI() {
        LogUtils.thread("更新進度...");
        this.tv.setText("目前進度..." + this.progress);
    }
}
AsyncTaskActivity
public class AsyncTaskActivity extends Activity {

    private static final String TAG = "AsyncTaskActivity";
    private Calculator task;

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.main);
        // 重新連線
        this.task = (Calculator) this.getLastNonConfigurationInstance();
        if (this.task != null) {
            Log.d(TAG, "與 AsyncTask 重新連線...");
            this.task.setContext(this);
            // 在 Activity 重起過程中已計算完畢,所以要呼叫 AsyncTask 來更新 UI
            if (this.task.getStatus() == AsyncTask.Status.FINISHED) {
                this.task.syncUI();
            }
        }
    }

    @Override
    public Object onRetainNonConfigurationInstance() {
        // 保留 Activity 與 AsyncTask 的關聯
        // 供 Activity 因故重起後,可以找回已在執行的 AsyncTask
        return this.task;
    }

    public void onClick(View v) {
        if (this.task != null) {
            AsyncTask.Status status = this.task.getStatus();
            Log.d(TAG, "目前狀態:" + status);
            if (status != AsyncTask.Status.FINISHED) {
                Log.d(TAG, "前一計算未完成,不可重起計算");
                return;
            }
            Log.d(TAG, "前一計算已完成,可重起計算");
        }
        Log.d(TAG, "新計算...");
        this.task = new Calculator(this);
        List<Integer> params = new ArrayList<Integer>();
        for (int i = 0; i < 10; i++) {
            params.add(i);
        }
        // 只能呼叫一次 execute(...)
        this.task.execute(params);
        // 可以手動中斷
        // this.task.cancel(true);
        // 判斷是否終止
        // if (this.task.isCancelled()){ }
        // 也可以取得計算結果
        // Integer result = this.task.get();
    }
}
LogUtils
public class LogUtils {

    private static final String TAG = "LogUtils";

    public static final void thread() {
        LogUtils.thread(null);
    }

    public static final void thread(String msg) {
        Thread t = Thread.currentThread();
        Log.d(TAG,
                "<" + t.getName() + ">id: " + t.getId() + ", Priority: "
                        + t.getPriority() + ", Group: "
                        + t.getThreadGroup().getName()
                        + (msg != null ? ", Msg: " + msg : ""));
    }
}
相關文章

2 則留言:

  1. 原來Android有內建這個東西,我之前都用Thread自己去寫、去調...好像還有一個ActitivyThread的API,我改天也來研究看看

    回覆刪除
  2. 原來還有這個API可以使用啊,我之前都是用Thread自己寫、自己修runtime error...

    回覆刪除