在《Android異步處理二:使用AsyncTask異步更新UI界面》/kf/201204/127129.html一文中,我們介紹瞭如何使用AsyncTask實現異步下載圖片,並且更新圖片到UI界面的方法。本篇我們將學習Framework層AsyncTask的實現原理。
概述:AsyncTask的本質是一個線程池,所有提交的異步任務都會在這個線程池中的工作線程內執行,當工作線程需要跟UI線程交互時,工作線程會通過向在UI線程創建的Handler(原理見:《Android異步處理三:Handler+Looper+MessageQueue深入詳解》/kf/201204/127130.html)傳遞消息的方式,調用相關的回調函數,從而實現UI界面的更新。
例子:
本章還是以《Android異步處理二:使用AsyncTask異步更新UI界面》/kf/201204/127129.html中的例子說明AsyncTask的實現原理。
這個例子是在後臺下載CSDN的LOGO,下載完成後在UI界面上顯示出來。
AsyncTask.java
[java]
1. package com.zhuozhuo;
2.
3.
4. import org.apache.http.HttpResponse;
5. import org.apache.http.client.HttpClient;
6. import org.apache.http.client.methods.HttpGet;
7. import org.apache.http.impl.client.DefaultHttpClient;
8.
9. import android.app.Activity;
10. import android.graphics.Bitmap;
11. import android.graphics.BitmapFactory;
12. import android.os.AsyncTask;
13. import android.os.Bundle;
14. import android.view.View;
15. import android.view.View.OnClickListener;
16. import android.widget.Button;
17. import android.widget.ImageView;
18. import android.widget.ProgressBar;
19. import android.widget.Toast;
20.
21. public class AsyncTaskActivity extends Activity {
22.
23. private ImageView mImageView;
24. private Button mButton;
25. private ProgressBar mProgressBar;
26.
27. @Override
28. public void onCreate(Bundle savedInstanceState) {
29. super.onCreate(savedInstanceState);
30. setContentView(R.layout.main);
31.
32. mImageView= (ImageView) findViewById(R.id.imageView);
33. mButton = (Button) findViewById(R.id.button);
34. mProgressBar = (ProgressBar) findViewById(R.id.progressBar);
35. mButton.setOnClickListener(new OnClickListener() {
36.
37. @Override
38. public void onClick(View v) {
39. GetCSDNLogoTask task = new GetCSDNLogoTask();
40. task.execute("/wp-content/images1/20181013/20120412095004782311.gif");
41. }
42. });
43. }
44.
45. class GetCSDNLogoTask extends AsyncTask<String,Integer,Bitmap> {//繼承AsyncTask
46.
47. @Override
48. protected Bitmap doInBackground(String… params) {//處理後臺執行的任務,在後臺線程執行
49. publishProgress(0);//將會調用onProgressUpdate(Integer… progress)方法
50. HttpClient hc = new DefaultHttpClient();
51. publishProgress(30);
52. HttpGet hg = new HttpGet(params[0]);//獲取csdn的logo
53. final Bitmap bm;
54. try {
55. HttpResponse hr = hc.execute(hg);
56. bm = BitmapFactory.decodeStream(hr.getEntity().getContent());
57. } catch (Exception e) {
58.
59. return null;
60. }
61. publishProgress(100);
62. //mImageView.setImageBitmap(result); 不能在後臺線程操作ui
63. return bm;
64. }
65.
66. protected void onProgressUpdate(Integer… progress) {//在調用publishProgress之後被調用,在ui線程執行
67. mProgressBar.setProgress(progress[0]);//更新進度條的進度
68. }
69.
70. protected void onPostExecute(Bitmap result) {//後臺任務執行完之後被調用,在ui線程執行
71. if(result != null) {
72. Toast.makeText(AsyncTaskActivity.this, "成功獲取圖片", Toast.LENGTH_LONG).show();
73. mImageView.setImageBitmap(result);
74. }else {
75. Toast.makeText(AsyncTaskActivity.this, "獲取圖片失敗", Toast.LENGTH_LONG).show();
76. }
77. }
78.
79. protected void onPreExecute () {//在 doInBackground(Params…)之前被調用,在ui線程執行
80. mImageView.setImageBitmap(null);
81. mProgressBar.setProgress(0);//進度條復位
82. }
83.
84. protected void onCancelled () {//在ui線程執行
85. mProgressBar.setProgress(0);//進度條復位
86. }
87.
88. }
89.
90.
91. }
main.xml
[html]
1. <?xml version="1.0" encoding="utf-8"?>
2. <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
3. android:orientation="vertical" android:layout_width="fill_parent"
4. android:layout_height="fill_parent">
5. <ProgressBar android:layout_width="fill_parent" android:layout_height="wrap_content" android:id="@+id/progressBar" style="?android:attr/progressBarStyleHorizontal"></ProgressBar>
6. <Button android:id="@+id/button" android:text="下載圖片" android:layout_width="wrap_content" android:layout_height="wrap_content"></Button>
7. <ImageView android:id="@+id/imageView" android:layout_height="wrap_content"
8. android:layout_width="wrap_content" />
9. </LinearLayout>
mainifest.xml
[html]
1. <?xml version="1.0" encoding="utf-8"?>
2. <manifest xmlns:android="http://schemas.android.com/apk/res/android"
3. package="com.zhuozhuo"
4. android:versionCode="1"
5. android:versionName="1.0">
6. <uses-sdk android:minSdkVersion="10" />
7. <uses-permission android:name="android.permission.INTERNET"></uses-permission>
8. <application android:icon="@drawable/icon" android:label="@string/app_name">
9. <activity android:name=".AsyncTaskActivity"
10. android:label="@string/app_name">
11. <intent-filter>
12. <action android:name="android.intent.action.MAIN" />
13. <category android:name="android.intent.category.LAUNCHER" />
14. </intent-filter>
15. </activity>
16.
17. </application>
18. </manifest>
運行結果:
分析:
在分析實現流程之前,我們先瞭解一下AsyncTask有哪些成員變量。
[java]
1. private static final int CORE_POOL_SIZE =5;//5個核心工作線程
2. private static final int MAXIMUM_POOL_SIZE = 128;//最多128個工作線程
3. private static final int KEEP_ALIVE = 1;//空閑線程的超時時間為1秒
4.
5. private static final BlockingQueue<Runnable> sWorkQueue =
6. new LinkedBlockingQueue<Runnable>(10);//等待隊列
7.
8. private static final ThreadPoolExecutorsExecutor = new ThreadPoolExecutor(CORE_POOL_SIZE,
9. MAXIMUM_POOL_SIZE, KEEP_ALIVE, TimeUnit.SECONDS, sWorkQueue,sThreadFactory);//線程池是靜態變量,所有的異步任務都會放到這個線程池的工作線程內執行。
回到例子中,點擊按鈕之後會新建一個GetCSDNLogoTask對象:
[java]
1. GetCSDNLogoTask task = new GetCSDNLogoTask();
此時會調用父類AsyncTask的構造函數:
AsyncTask.java
[java]
1. public AsyncTask() {
2. mWorker = new WorkerRunnable<Params, Result>() {
3. public Result call() throws Exception {
4. Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);
5. return doInBackground(mParams);
6. }
7. };
8.
9. mFuture = new FutureTask<Result>(mWorker) {
10. @Override
11. protected void done() {
12. Message message;
13. Result result = null;
14.
15. try {
16. result = get();
17. } catch (InterruptedException e) {
18. android.util.Log.w(LOG_TAG, e);
19. } catch (ExecutionException e) {
20. throw new RuntimeException("An error occured while executing doInBackground()",
21. e.getCause());
22. } catch (CancellationException e) {
23. message = sHandler.obtainMessage(MESSAGE_POST_CANCEL,
24. new AsyncTaskResult<Result>(AsyncTask.this, (Result[]) null));
25. message.sendToTarget();//取消任務,發送MESSAGE_POST_CANCEL消息
26. return;
27. } catch (Throwable t) {
28. throw new RuntimeException("An error occured while executing "
29. + "doInBackground()", t);
30. }
31.
32. message = sHandler.obtainMessage(MESSAGE_POST_RESULT,
33. new AsyncTaskResult<Result>(AsyncTask.this, result));//完成任務,發送MESSAGE_POST_RESULT消息並傳遞result對象
34. message.sendToTarget();
35. }
36. };
37. }
WorkerRunnable類實現瞭callable接口的call()方法,該函數會調用我們在AsyncTask子類中實現的doInBackground(mParams)方法,由此可見,WorkerRunnable封裝瞭我們要執行的異步任務。FutureTask中的protected void done() {}方法實現瞭異步任務狀態改變後的操作。當異步任務被取消,會向UI線程傳遞MESSAGE_POST_CANCEL消息,當任務成功執行,會向UI線程傳遞MESSAGE_POST_RESULT消息,並把執行結果傳遞到UI線程。
由此可知,AsyncTask在構造的時候已經定義好要異步執行的方法doInBackground(mParams)和任務狀態變化後的操作(包括失敗和成功)。
當創建完GetCSDNLogoTask對象後,執行
[java]
1. task.execute("/wp-content/images1/20181013/20120412095004782311.gif");
此時會調用AsyncTask的execute(Params…params)方法
AsyncTask.java
[java]
1. public final AsyncTask<Params,Progress, Result> execute(Params… params) {
2. if (mStatus != Status.PENDING) {
3. switch (mStatus) {
4. case RUNNING:
5. throw newIllegalStateException("Cannot execute task:"
6. + " the taskis already running.");
7. case FINISHED:
8. throw newIllegalStateException("Cannot execute task:"
9. + " the taskhas already been executed "
10. + "(a task canbe executed only once)");
11. }
12. }
13.
14. mStatus = Status.RUNNING;
15.
16. onPreExecute();//運行在ui線程,在提交任務到線程池之前執行
17.
18. mWorker.mParams = params;
19. sExecutor.execute(mFuture);//提交任務到線程池
20.
21. return this;
22. }
當任務正在執行或者已經完成,會拋出IllegalStateException,由此可知我們不能夠重復調用execute(Params…params)方法。在提交任務到線程池之前,調用瞭onPreExecute()方法。然後才執行sExecutor.execute(mFuture)是任務提交到線程池。
前面我們說到,當任務的狀態發生改變時(1、執行成功2、取消執行3、進度更新),工作線程會向UI線程的Handler傳遞消息。在《Android異步處理三:Handler+Looper+MessageQueue深入詳解》/kf/201204/127130.html一文中我們提到,Handler要處理其他線程傳遞過來的消息。在AsyncTask中,InternalHandler是在UI線程上創建的,它接收來自工作線程的消息,實現代碼如下:
AsyncTask.java
[java]
1. private static class InternalHandler extends Handler {
2. @SuppressWarnings({"unchecked","RawUseOfParameterizedType"})
3. @Override
4. public voidhandleMessage(Message msg) {
5. AsyncTaskResult result =(AsyncTaskResult) msg.obj;
6. switch (msg.what) {
7. caseMESSAGE_POST_RESULT:
8. // There is onlyone result
9. result.mTask.finish(result.mData[0]);//執行任務成功
10. break;
11. caseMESSAGE_POST_PROGRESS:
12. result.mTask.onProgressUpdate(result.mData);//進度更新
13. break;
14. caseMESSAGE_POST_CANCEL:
15. result.mTask.onCancelled();//取消任務
16. break;
17. }
18. }
19. }
當接收到消息之後,AsyncTask會調用自身相應的回調方法。
總結:
1、 AsyncTask的本質是一個靜態的線程池,AsyncTask派生出的子類可以實現不同的異步任務,這些任務都是提交到靜態的線程池中執行。
2、線程池中的工作線程執行doInBackground(mParams)方法執行異步任務
3、當任務狀態改變之後,工作線程會向UI線程發送消息,AsyncTask內部的InternalHandler響應這些消息,並調用相關的回調函數
摘自 lzc的專欄