標籤:
http://www.cnblogs.com/plokmju/p/android_AsyncTask.html
AsyncTask,非同步任務,可以簡單進行非同步作業,並把執行結果發布到UI主線程。AsyncTask是一個抽象類別,它的內部其實也是結合了Thread和Handler來實現非同步線程操作,但是它形成了一個通用線程架構,更清晰簡單。AsyncTask應該被用於比較簡短的操作(最多幾秒鐘)。如果需要保持長時間啟動並執行線程,可以使用ThreadPooExecutor或者FutureTask
首先來看一下AsyncTask的基本用法,由於AsyncTask是一個抽象類別,所以如果我們想使用它,就必須要建立一個子類去繼承它。在繼承時我們可以為AsyncTask類指定三個泛型參數,這三個參數的用途如下:
1. Params
在執行AsyncTask時需要傳入的參數,可用於在背景工作中使用。
2. Progress
背景工作執行時,如果需要在介面上顯示當前的進度,則使用這裡指定的泛型作為進度單位。
3. Result
當任務執行完畢後,如果需要對結果進行返回,則使用這裡指定的泛型作為傳回值類型。
AsyncTask的使用規則
使用AsyncTask必須遵循以下規則:
- AsyncTask必須聲明在UI線程上。
- AsyncTask必須在UI線程上執行個體化。
- 必須通過execute()方法執行任務。
- 不可以直接調用onPreExecute()、onPostExecute(Resut)、doInBackground(Params...)、onProgressUpdate(Progress...)方法。
- 可以設定任務只執行一次,如果企圖再次執行會報錯。
第一個因為AsyncTask內建了handler,所以在主線程運行最好。不過調用Looper.prepare後似乎也可以在子線程運行
使用方法在第一個連結裡很清楚,下面來看一下源碼:
http://blog.csdn.net/guolin_blog/article/details/11711405
- 可以看到源碼execute時調用了executeOnExecutor()方法,在這裡調用了onPreExecute(),保證了他第一個執行
- 接著調用了Executor的execute()方法,並將前面初始化的mFuture對象傳了進去
- 接著就來到FutureTask類的run()方法,這裡開啟了一個子線程,調用call方法,而call方法調用了doInBackground()方法,這保證了方法在子線程裡執行
- postResult()方法,源碼如下:
private Result postResult(Result result) { Message message = sHandler.obtainMessage(MESSAGE_POST_RESULT, new AsyncTaskResult<Result>(this, result)); message.sendToTarget(); return result; }
可以看到,實際上就是用handler處理返回的結果,回到主線程
private static class InternalHandler extends Handler { @SuppressWarnings({"unchecked", "RawUseOfParameterizedType"}) @Override public void 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; } } }
InernalHandler接受兩種資訊,如果這是一條MESSAGE_POST_RESULT訊息,就會去執行finish()方法,如果這是一條MESSAGE_POST_PROGRESS訊息,就會去執行onProgressUpdate()方法。那麼finish()方法的源碼如下所示:
private void finish(Result result) { if (isCancelled()) { onCancelled(result); } else { onPostExecute(result); } mStatus = Status.FINISHED; }
可以看到,如果當前任務被取消掉了,就會調用onCancelled()方法,如果沒有被取消,則調用onPostExecute()方法,這樣當前任務的執行就全部結束了。
從上面還可以看到,運行中可以隨時調用cancel(boolean)方法取消任務,如果成功調用isCancelled()會返回true,並且不會執行 onPostExecute() 方法了,取而代之的是調用 onCancelled() 方法。
而且從源碼看,如果這個任務已經執行了這個時候調用cancel是不會真正的把task結束,而是繼續執行,只不過改變的是執行之後的回調方法是 onPostExecute還是onCancelled。
onProgressUpdate()方法源碼如下:
protected final void publishProgress(Progress... values) { if (!isCancelled()) { sHandler.obtainMessage(MESSAGE_POST_PROGRESS, new AsyncTaskResult<Progress>(this, values)).sendToTarget(); } }
同樣是發給handler處理,所以是在主線程啟動並執行
SerialExecutor詳細:
其實SerialExecutor也是AsyncTask在3.0版本以後做了最主要的修改的地方,它在AsyncTask中是以常量的形式被使用的,因此在整個應用程式中的所有AsyncTask執行個體都會共用同一個SerialExecutor
private static class SerialExecutor implements Executor { final ArrayDeque<Runnable> mTasks = new ArrayDeque<Runnable>(); Runnable mActive; public synchronized void execute(final Runnable r) { mTasks.offer(new Runnable() { public void run() { try { r.run(); } finally { scheduleNext(); } } }); if (mActive == null) { scheduleNext(); } } protected synchronized void scheduleNext() { if ((mActive = mTasks.poll()) != null) { THREAD_POOL_EXECUTOR.execute(mActive); } } }
可以看到,SerialExecutor是使用ArrayDeque這個隊列來管理Runnable對象的,如果我們一次性啟動了很多個任務,首先在第一次運行execute()方法的時候,會調用ArrayDeque的offer()方法將傳入的Runnable對象添加到隊列的尾部,然後判斷mActive對象是不是等於null,第一次運行當然是等於null了,於是會調用scheduleNext()方法。在這個方法中會從隊列的頭部取值,並賦值給mActive對象,然後調用THREAD_POOL_EXECUTOR去執行取出的取出的Runnable對象。之後如何又有新的任務被執行,同樣還會調用offer()方法將傳入的Runnable添加到隊列的尾部,但是再去給mActive對象做非空檢查的時候就會發現mActive對象已經不再是null了,於是就不會再調用scheduleNext()方法。
那麼後面添加的任務豈不是永遠得不到處理了?當然不是,看一看offer()方法裡傳入的Runnable匿名類,這裡使用了一個try finally代碼塊,並在finally中調用了scheduleNext()方法,保證無論發生什麼情況,這個方法都會被調用。也就是說,每次當一個任務執行完畢後,下一個任務才會得到執行,SerialExecutor模仿的是單一線程池的效果,如果我們快速地啟動了很多任務,同一時刻只會有一個線程正在執行,其餘的均處於等待狀態。Android照片牆應用實現,再多的圖片也不怕崩潰 這篇文章中例子的運行結果也證實了這個結論。
在Android 3.0之前是並沒有SerialExecutor這個類的,那個時候是直接在AsyncTask中構建了一個sExecutor常量,並對線程池總大小,同一時刻能夠啟動並執行線程數做了規定,代碼如下所示:
private static final int CORE_POOL_SIZE = 5; private static final int MAXIMUM_POOL_SIZE = 128; private static final int KEEP_ALIVE = 10; …… private static final ThreadPoolExecutor sExecutor = new ThreadPoolExecutor(CORE_POOL_SIZE, MAXIMUM_POOL_SIZE, KEEP_ALIVE, TimeUnit.SECONDS, sWorkQueue, sThreadFactory);
可以看到,這裡規定同一時刻能夠啟動並執行線程數為5個,線程池總大小為128。也就是說當我們啟動了10個任務時,只有5個任務能夠立刻執行,另外的5個任務則需要等待,當有一個任務執行完畢後,第6個任務才會啟動,以此類推。而線程池中最大能存放的線程數是128個,當我們嘗試去添加第129個任務時,程式就會崩潰。
因此在3.0版本中AsyncTask的改動還是挺大的,在3.0之前的AsyncTask可以同時有5個任務在執行,而3.0之後的AsyncTask同時只能有1個任務在執行。為什麼升級之後可以同時執行的任務數反而變少了呢?這是因為更新後的AsyncTask已變得更加靈活,如果不想使用預設的線程池,還可以自由地進行配置。比如使用如下的代碼來啟動任務:
Executor exec = new ThreadPoolExecutor(15, 200, 10, TimeUnit.SECONDS, new LinkedBlockingQueue<Runnable>()); new DownloadTask().executeOnExecutor(exec);
這樣就可以使用我們自訂的一個Executor來執行任務,而不是使用SerialExecutor。上述代碼的效果允許在同一時刻有15個任務正在執行,並且最多能夠儲存200個任務。
需要注意的地方:
http://www.open-open.com/lib/view/open1434802647364.html
上面提到了那麼多的注意點,還有其他需要注意的嗎?當然有!我們開發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這個庫,自己單獨的用介面回調的方式估計也能實現,大家可以試試。
Android-多線程AsyncTask