Android開發者:你真的會用AsyncTask嗎?
【導讀】在Android應用開發的過程中,我們需要時刻注意保證應用程序的穩定和UI操作響應及時,因為不穩定或響應緩慢的應用將給應用帶來不好的印象,嚴重的用戶卸載你的APP,這樣你的努力就沒有體現的價值了。本文試圖從AsnycTask的作用說起,進一步的講解一下內部的實現機制。如果有一些開發經驗的人,讀完之后應該對使用AsnycTask過程中的一些問題豁然開朗,開發經驗不豐富的也可以從中找到使用過程中的注意點。
為何引入AsnyncTask?
在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。
AsnyncTask內部機制
AsyncTask內部邏輯主要有二個部分:
1、與主線的交互,它內部實例化了一個靜態的自定義類InternalHandler,這個類是繼承自 Handler的,在這個自定義類中綁定了一個叫做AsyncTaskResult的對象,每次子線程需要通知主線程,就調用sendToTarget發送消息給handler。然后在handler的handleMessage中AsyncTaskResult根據消息的類型不同(例如MESSAGEPOSTPROGRESS會更新進度條,MESSAGEPOSTCANCEL取消任務)而做不同的操作,值得一提的是,這些操作都是在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內部調度,雖然可以新建多個AsyncTask的子類的實例,但是AsyncTask的內部Handler和ThreadPoolExecutor都是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又是內置于AsnycTask中的,所以,AsyncTask的創建必須在主線程。
- AsyncTaskResult的doInBackground(mParams)方法執行異步任務運行在子線程中,其他方法運行在主線程中,可以操作UI組件。
- 不要手動的去調用AsyncTask的onPreExecute, doInBackground, publishProgress, onProgressUpdate, onPostExecute方法,這些都是由Android系統自動調用的
- 一個任務AsyncTask任務只能被執行一次。
- 運行中可以隨時調用cancel(boolean)方法取消任務,如果成功調用isCancelled()會返回true,并且不會執行 onPostExecute() 方法了,取而代之的是調用 onCancelled() 方法。而且從源碼看,如果這個任務已經執行了這個時候調用cancel是不會真正的把task結束,而是繼續執行,只不過改變的是執行之后的回調方法是 onPostExecute還是onCancelled。
AsnyncTask和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這個庫,自己單獨的用接口回調的方式估計也能實現,大家可以試試。