2016年3月21日

AsyncTask 讓背景任務繫結到 UI Thread

AsyncTask 是在背景執行緒上運作的非同步技術,並將結果送回 UI Thread。

public class MinimalTask extends AsyncTask {
    protected Object doInBackground(Object... object) {
        // 實作要在背景執行緒上執行的任務
    }
}

AsyncTask 工作流程

限制一次一個 AsyncTask

public class AsyncTaskStatusActivity extends Activity {
    private AsyncTask myAsyncTask;
    
    public void onExecute(View v) {
        if(myAsyncTask!=null && myAsyncTask.getStatus() != AsyncTask.Status.RUNNING) {
            myAsyncTask= new MyAsyncTask().execute();
        }
    }
    
    private static class MyAsyncTask extends AsyncTask<String, Void, Void> {
        protected void doInBackground(String... s) {
            return null;
        }
    }
}

下載圖片 的範例

public class FileDownloadActivity extends Activity {

    // 多個圖片的 urls
    private static final String[] DOWNLOAD_URLS = {
            "http://developer.android.com/design/media/devices_displays_density@2x.png",
            "http://developer.android.com/design/media/iconography_launcher_example2.png"
    };

    DownloadTask mFileDownloaderTask;

    // Views from layout file
    private ProgressBar mProgressBar;
    private LinearLayout mLayoutImages;

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        setContentView(R.layout.activity_file_download);
        mProgressBar = (ProgressBar) findViewById(R.id.progress_bar);
        mProgressBar.setMax(DOWNLOAD_URLS.length);
        mLayoutImages = (LinearLayout) findViewById(R.id.layout_images);

        mFileDownloaderTask = new DownloadTask(this);
        // 執行mFileDownloaderTask
        mFileDownloaderTask.execute(DOWNLOAD_URLS);
    }

    @Override
    protected void onDestroy() {
        // 要同時取消 mFileDownloaderTask,避免 memory leak
        super.onDestroy();
        mFileDownloaderTask.setActivity(null);
        mFileDownloaderTask.cancel(true);
    }

    private static class DownloadTask extends AsyncTask<String, Bitmap, Void> {

        private FileDownloadActivity mActivity;
        private int mCount = 0;

        public DownloadTask(FileDownloadActivity activity) {
            mActivity = activity;
        }

        public void setActivity(FileDownloadActivity activity) {
            mActivity = activity;
        }

        @Override
        protected void onPreExecute() {
            super.onPreExecute();
            
            // 執行過程中,更新畫面的 進度mActivity.mProgressBar.setVisibility(View.VISIBLE);
            mActivity.mProgressBar.setProgress(0);
        }

        @Override
        protected Void doInBackground(String... urls) {
            for (String url : urls) {
                if (!isCancelled()) {
                    Bitmap bitmap = downloadFile(url);
                    // 把圖片傳送到 UI Thread
                    publishProgress(bitmap);
                }
            }
            return null;
        }


        @Override
        protected void onProgressUpdate(Bitmap... bitmaps) {
            super.onProgressUpdate(bitmaps);
            if (mActivity != null)  {
                mActivity.mProgressBar.setProgress(++mCount);
                ImageView iv = new ImageView(mActivity);
                iv.setImageBitmap(bitmaps[0]);
                mActivity.mLayoutImages.addView(iv);
            }
        }

        @Override
        protected void onPostExecute(Void aVoid) {
            super.onPostExecute(aVoid);
            if (mActivity != null) {
                mActivity.mProgressBar.setVisibility(View.GONE);
            }
        }

        @Override
        protected void onCancelled() {
            super.onCancelled();
            if (mActivity != null) {
                mActivity.mProgressBar.setVisibility(View.GONE);
            }
        }


        private Bitmap downloadFile(String url) {
            Bitmap bitmap = null;
            try {
                bitmap = BitmapFactory
                        .decodeStream((InputStream) new URL(url)
                                .getContent());
            } catch (MalformedURLException e) {
                e.printStackTrace();
            } catch (IOException e) {
                e.printStackTrace();
            }
            return bitmap;
        }

    }
}

討論

  1. AsyncTask 具有全域的特性,使用越多,越有可能造成任務異常
  2. 需要使用 Looper 時,就要改用 HandlerThread
  3. 不需要回報進度的任務,可以改用 HandlerThread 或 Thread 實作
  4. 如果是長時間的任務,要改用 Service

Reference

Android 高效能多執行緒