AsyncTask 內部實現機制

在 Android 應用開發的過程中,我們需要時刻注意保證應用程序的穩定和 UI 操作響應及時,因爲不穩定或響應緩慢的應用將給應用帶來不好的印象,嚴重的用戶卸載你的APP,這樣你的努力就沒有體現的價值了。本文試圖從 AsyncTask 的作用說起,進一步的講解一下內部的實現機制。如果有一些開發經驗的人,讀完之後應該對使用 AsnycTask 過程中的一些問題豁然開朗,開發經驗不豐富的也可以從中找到使用過程中的注意點。

爲何引入 AsyncTask?

在 Android 程序開始運行的時候會單獨啓動一個進程,默認情況下所有這個程序操作都在這個進程中進行。一個 Android 程序默認情況下只有一個進程,但是一個進程卻是可以有許線程的。

在這些線程中,有一個線程叫做 UI 線程,也叫做 Main Thread,除了 Main Thread 之外的線程都可稱爲 Worker Thread。Main Thread 主要負責控制 UI 頁面的顯示、更新、交互等。因此所有在 UI 線程中的操作要求越短越好,只有這樣用戶纔會覺得操作比較流暢。一個比較好的做法是把一些比較耗時的操作,例如網絡請求、數據庫操作、複雜計算等邏輯都封裝到單獨的線程,這樣就可以避免阻塞主線程。爲此,有人寫了如下的代碼:

private TextView textView;
	public void onCreate(Bundle bundle){
		super.onCreate(bundle);
		setContentView(R.layout.thread_on_ui);
		textView = (TextView) findViewById(R.id.tvTest);
		new Thread(new Runnable() {
			@Override
			public void run() {
				try {
					HttpGet httpGet = new HttpGet("http://www.baidu.com");
					HttpClient httpClient = new DefaultHttpClient();
					HttpResponse httpResp = httpClient.execute(httpGet);
					if (httpResp.getStatusLine().getStatusCode() == 200) {
						String result = EntityUtils.toString(httpResp.getEntity(), "UTF-8");
						textView.setText("請求返回正常,結果是:" + result);
					} else {
						textView.setText("請求返回異常!");
					}
				}catch (IOException e){
				   e.printStackTrace();
				}
			}
		}).start();
	}

運行,不出所料,異常信息如下:

android.view.ViewRootImpl$CalledFromWrongThreadException: Only the original thread that created a view hierarchy can touch its views.

怎麼破?可以在主線程創建 Handler 對象,把 textView.setText 地方替換爲用handler 把返回值發回到 handler 所在的線程處理,也就是主線程。這個處理方法稍顯複雜,Android 爲我們考慮到了這個情況,給我們提供了一個輕量級的異步類可以直接繼承 AsyncTask,在類中實現異步操作,並提供接口反饋當前異步執行的結果以及執行進度,這些接口中有直接運行在主線程中的,例如 onPostExecute,onPreExecute 等方法。

也就是說,Android 的程序運行時是多線程的,爲了更方便的處理子線程和UI線程的交互,引入了 AsyncTask。

AsyncTask 內部機制

AsyncTask 內部邏輯主要有二個部分:

1、與主線的交互

它內部實例化了一個靜態的自定義類 InternalHandler,這個類是繼承自 Handler 的,在這個自定義類中綁定了一個叫做 AsyncTaskResult 的對象,每次子線程需要通知主線程,就調用 sendToTarget 發送消息給 handler。然後在 handler 的 handleMessage 中 AsyncTaskResult 根據消息的類型不同(例如 MESSAGE_POST_PROGRESS 會更新進度條,MESSAGE_POST_CANCEL 取消任務)而做不同的操作,值得一提的是,這些操作都是在UI線程進行的,意味着,從子線程一旦需要和 UI 線程交互,內部自動調用了 handler 對象把消息放在了主線程了。 源碼地址

   mFuture = new FutureTask<Result>(mWorker) {
		   @Override
			protected void More ...done() {
				Message message;
			   Result result = null;
				try {
					result = get();
			   } catch (InterruptedException e) {
					android.util.Log.w(LOG_TAG, e);
			   } catch (ExecutionException e) {
					throw new RuntimeException("An error occured while executing doInBackground()",
							e.getCause());
				} catch (CancellationException e) {
					message = sHandler.obtainMessage(MESSAGE_POST_CANCEL,
						   new AsyncTaskResult<Result>(AsyncTask.this, (Result[]) null));
					message.sendToTarget();
					return;
				} catch (Throwable t) {
					throw new RuntimeException("An error occured while executing "
						   + "doInBackground()", t);
				}
				message = sHandler.obtainMessage(MESSAGE_POST_RESULT,
						new AsyncTaskResult<Result>(AsyncTask.this, result));
				message.sendToTarget();
		   }
		};
  private static class InternalHandler extends Handler {
		@SuppressWarnings({"unchecked", "RawUseOfParameterizedType"})
		@Override
		public void More ...handleMessage(Message msg) {
			AsyncTaskResult result = (AsyncTaskResult) msg.obj;
			switch (msg.what) {
				case MESSAGE_POST_RESULT:
					// There is only one result
					result.mTask.finish(result.mData[0]);
					break;
				case MESSAGE_POST_PROGRESS:
					result.mTask.onProgressUpdate(result.mData);
					break;
				case MESSAGE_POST_CANCEL:
					result.mTask.onCancelled();
					break;
			}
		}
	}

2、AsyncTask 內部調度

cutor 都是 static 的,這麼定義的變量屬於類的,是進程範圍內共享的,所以 AsyncTask 控制着進程範圍內所有的子類實例,而且該類的所有實例都共用一個線程池和 Handler。代碼如下:

public abstract class AsyncTask<Params, Progress, Result> {
	private static final String LOG_TAG = "AsyncTask";
	private static final int CORE_POOL_SIZE = 5;
	private static final int MAXIMUM_POOL_SIZE = 128;
	private static final int KEEP_ALIVE = 1;
	private static final BlockingQueue<Runnable> sWorkQueue =
			new LinkedBlockingQueue<Runnable>(10);
	private static final ThreadFactory sThreadFactory = new ThreadFactory() {
		private final AtomicInteger mCount = new AtomicInteger(1);
		public Thread More ...newThread(Runnable r) {
			return new Thread(r, "AsyncTask #" + mCount.getAndIncrement());
		}
	};
	private static final ThreadPoolExecutor sExecutor = new ThreadPoolExecutor(CORE_POOL_SIZE,
			MAXIMUM_POOL_SIZE, KEEP_ALIVE, TimeUnit.SECONDS, sWorkQueue, sThreadFactory);
	private static final int MESSAGE_POST_RESULT = 0x1;
	private static final int MESSAGE_POST_PROGRESS = 0x2;
	private static final int MESSAGE_POST_CANCEL = 0x3;

從代碼還可以看出,默認核心線程池的大小是5,緩存任務隊列是10。意味着,如果線程池的線程數量小於5,這個時候新添加一個異步任務則會新建一個線程;如果線程池的數量大於等於5,這個時候新建一個異步任務這個任務會被放入緩存隊列中等待執行。限制一個 APP 內 AsyncTask 併發的線程的數量看似是有必要的,但也帶來了一個問題,假如有人就是需要同時運行10個而不是5個,或者不對線程的多少做限制,例如有些 APP 的瀑布流頁面中的N多圖片的加載。

另一方面,同時運行的任務多,線程也就多,如果這些任務是去訪問網絡的,會導致短時間內手機那可憐的帶寬被佔完了,這樣總體的表現是誰都很難很快加載完全,因爲他們是競爭關係。所以,把選擇權交給開發者吧。

事實上,大概從 Android 從3.0開始,每次新建異步任務的時候AsnycTask內部默認規則是按提交的先後順序每次只運行一個異步任務。當然了你也可以自己指定自己的線程池。

可以看出,AsyncTask 使用過程中需要注意的地方不少

  • 由於 Handler 需要和主線程交互,而 Handler 又是內置於 AsyncTask 中的,所以,AsyncTask 的創建必須在主線程。
  • AsyncTaskResult 的 doInBackground(mParams)方法執行異步任務運行在子線程中,其他方法運行在主線程中,可以操作 UI 組件。
  • 不要手動的去調用 AsyncTask 的 onPreExecute, doInBackground, publishProgress, onProgressUpdate, onPostExecute 方法,這些都是由Android 系統自動調用的
  • 一個任務 AsyncTask 任務只能被執行一次。
  • 運行中可以隨時調用 cancel(boolean) 方法取消任務,如果成功調用isCancelled() 會返回 true,並且不會執行 onPostExecute() 方法了,取而代之的是調用 onCancelled() 方法。而且從源碼看,如果這個任務已經執行了這個時候調用cancel是不會真正的把task結束,而是繼續執行,只不過改變的是執行之後的回調方法是 onPostExecute 還是 onCancelled。

AsyncTask 和 Activity OnConfiguration

上面提到了那麼多的注意點,還有其他需要注意的嗎?當然有!我們開發 App 過程中使用 AsyncTask 請求網絡數據的時候,一般都是習慣在 onPreExecute 顯示進度條,在數據請求完成之後的 onPostExecute 關閉進度條。這樣做看似完美,但是如果您的 App 沒有明確指定屏幕方向和 configChanges 時,當用戶旋轉屏幕的時候 Activity 就會重新啓動,而這個時候您的異步加載數據的線程可能正在請求網絡。當一個新的 Activity 被重新創建之後,可能由重新啓動了一個新的任務去請求網絡,這樣之前的一個異步任務不經意間就泄露了,假設你還在 onPostExecute 寫了一些其他邏輯,這個時候就會發生意想不到異常。

一般簡單的數據類型的,對付 configChanges 我們很好處理,我們直接可以通過 onSaveInstanceState() 和 onRestoreInstanceState() 進行保存與恢復。Android 會在銷燬你的 Activity 之前調用 onSaveInstanceState() 方法,於是,你可以在此方法中存儲關於應用狀態的數據。然後你可以在 onCreate() 或 onRestoreInstanceState() 方法中恢復。

但是,對於 AsyncTask 怎麼辦?問題產生的根源在於 Activity 銷燬重新創建的過程中 AsyncTask 和之前的 Activity 失聯,最終導致一些問題。那麼解決問題的思路也可以朝着這個方向發展。 Android 官方文檔 也有一些解決問題的線索。

這裏介紹另外一種使用事件總線的解決方案,是國外一個安卓大牛寫的。中間用到了Square 開源的 EventBus 類庫 http://square.github.io/otto/ 。首先自定義一個 AsyncTask 的子類,在 onPostExecute 方法中,把返回結果拋給事件總線,代碼如下:

 @Override 
	protected String doInBackground(Void... params) {
		Random random = new Random();
		final long sleep = random.nextInt(10);
		try {
			Thread.sleep(10 * 6000);
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
		return "Slept for " + sleep + " seconds";
	}
	@Override
	protected void onPostExecute(String result) {
		MyBus.getInstance().post(new AsyncTaskResultEvent(result));
	}

在 Activity 的 onCreate 中註冊這個事件總線,這樣異步線程的消息就會被 otta 分發到當前註冊的 activity,這個時候返回結果就在當前 activity 的 onAsyncTaskResult 中了,代碼如下:

 @Override
	protected void onCreate(Bundle savedInstanceState) {
		super.onCreate(savedInstanceState);
		setContentView(R.layout.otto_layout);
		findViewById(R.id.button).setOnClickListener(new View.OnClickListener() {
			@Override public void onClick(View v) {
				new MyAsyncTask().execute();
			}
		});
		MyBus.getInstance().register(this);
	}
	@Override
	protected void onDestroy() {
		MyBus.getInstance().unregister(this);
		super.onDestroy();
	}
	@Subscribe
	public void onAsyncTaskResult(AsyncTaskResultEvent event) {
		Toast.makeText(this, event.getResult(), Toast.LENGTH_LONG).show();
	}

個人覺的這個方法相當好,當然更簡單的你也可以不用 otta 這個庫,自己單獨的用接口回調的方式估計也能實現,大家可以試試。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章