編輯:關於Android編程
AsyncTask 是Android特有的一個輕量級異步抽象類,在類中通過doInBackground()在子線程執行耗時操作,執行完畢在主線程調用onPostExecute()。
眾所周知,Android視圖的繪制、監聽、事件等都UI線程(主線程,Main Thread)執行,如果執行訪問網絡請求、數據庫等耗時操作,可能會阻塞主線程,若阻塞時間超過5秒,可能會引起系統的ANR異常(Application Not Responding)。所以耗時操作需要放在子線程(也稱為Worker Thread)執行,這樣就避免了主線程的阻塞,然而在線程是不能有更新UI的操作的,比如在子線程調用TextView.setText()就會引發以下錯誤:
Only the original thread that created a view hierarchy can touch its views.
故而可以用 “Handle + Thread”的方式,子線程執行耗時操作,通過Handler通知主線程更新UI。但是這個方式略微麻煩,於是便引入了AsyncTask。
AsyncTask
public void request() { AsyncTasktask = new AsyncTask () { @Override protected void onPreExecute() { super.onPreExecute(); Log.i("AsyncTask", "准備執行後台任務"); } @Override protected Integer doInBackground(String... params) { String url_1 = params[0]; doRequest(url_1); publishProgress(50); String url_2 = params[1]; doRequest(url_2); publishProgress(100); return 1; } @Override protected void onProgressUpdate(Integer... values) { super.onProgressUpdate(values); Log.i("AsyncTask", "當前進度" + values[0] + "%"); } @Override protected void onPostExecute(Integer ret) { super.onPostExecute(ret); Log.i("AsyncTask", "執行完畢,執行結果" + ret); } }; String url_1 = "https://api.github.com/users/smuyyh"; String url_2 = "https://api.github.com/users/smuyyh/followers"; task.execute(url_1, url_2); // 開啟後台任務 }
代碼較為簡單,就不做過多的解釋了。後面著重介紹AsyncTask的內部實現機制。
這一節將從源碼的角度來分析一下AsyncTask。以下源碼基於Android-23.
開啟後台任務之前,首先需要創建AsyncTask的實例,所以還得從構造函數說起。public AsyncTask() { mWorker = new WorkerRunnable() { public Result call() throws Exception { mTaskInvoked.set(true); Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND); Result result = doInBackground(mParams); Binder.flushPendingCommands(); return postResult(result); // 任務的具體實現 } }; mFuture = new FutureTask (mWorker) { @Override protected void done() { try { postResultIfNotInvoked(get()); } catch (InterruptedException e) { android.util.Log.w(LOG_TAG, e); } catch (ExecutionException e) { throw new RuntimeException("An error occurred while executing doInBackground()", e.getCause()); } catch (CancellationException e) { postResultIfNotInvoked(null); } } }; }
在構造函數中,初始化了兩個變量,分別是mWorker與mFuture,mFuture創建的時候傳入了mWorker參數,而mWorker本身是一個Callable對象。那麼,mFutrue是個什麼東西呢?
mFuture是一個FutureTask對象,FutureTask實際上是一個任務的操作類,它並不啟動新線程,並且只負責任務調度。任務的具體實現是構造FutureTask時提供的,實現自Callable接口,也就是剛才的mWorker。
AsyncTask對象穿件完畢之後調用execute(Params...)執行,跟進看看@MainThread public final AsyncTaskexecute(Params... params) { return executeOnExecutor(sDefaultExecutor, params); }
只有一句話,可知是調用executeOnExecutor進行執行。這裡就有個疑問了,sDefaultExecutor是個什麼東西?在說這個之前,需要明確一下一下三個事:
1、Android3.0之前部分代碼
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);
2、在Android3.0之前,AsyncTask執行中最終觸發的是把任務交給線池THREAD_POOL_EXECUTOR來執行,提交的任務並行的在線程池中運行,但這些規則在3.0之後發生了變化,3.0之後提交的任務是默認串行運行的,執行完一個任務才執行下一個!
3、在Android3.0以前線程池裡核心線程有5個,同時存在的線程數最大不能超過128個,線程池裡的線程都是並行運行的,在3.0以後,直接調用execute(params)觸發的是sDefaultExecutor的execute(runnable)方法,而不是原來的THREAD_POOL_EXECUTOR。在Android4.4以後,線程池大小等於 cpu核心數 + 1,最大值為cpu核心數 * 2 + 1。這些變化大家可以自行對比一下。
跟進源碼不難發現,sDefaultExecutor實際上是指向SerialExecutor的一個實例,從名字上看是一個順序執行的executor,並且它在AsyncTask中是以常量的形式存在的,因此在整個應用程序中的所有AsyncTask實例都會共用同一個SerialExecutor。
private static class SerialExecutor implements Executor { final ArrayDequemTasks = new ArrayDeque (); 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對象的。當Executor提交一個任務,執行一次execute(),在這裡向mTasks隊列添加一個Runnable對象。初次添加任務時mActive為null,故接下來會執行scheduleNext(),將mActive指向剛剛添加的runbale,並提交到THREAD_POOL_EXECUTOR中執行。
當AsyncTask不斷提交任務時,那麼此時mActive不為空了,所以後續添加的任務能得到執行的唯一條件,就是前一個任務執行完畢,也就是r.run()。所以這就保證了SerialExecutor的順序執行。這個地方其實也是一個坑,初學者很容易在這裡踩坑,同時提交多個任務,卻無法同步執行。
如果想讓其並行執行怎麼辦?AsyncTask提供了一下兩種方式:
task.setDefaultExecutor(AsyncTask.THREAD_POOL_EXECUTOR); task.executeOnExecutor(executor, params); //可以自己指定線程池繼續跟進executeOnExecutor(Executor exec, Params... params)代碼
@MainThread public final AsyncTaskexecuteOnExecutor(Executor exec, Params... params) { if (mStatus != Status.PENDING) { switch (mStatus) { case RUNNING: throw new IllegalStateException("Cannot execute task:" + " the task is already running."); case FINISHED: throw new IllegalStateException("Cannot execute task:" + " the task has already been executed " + "(a task can be executed only once)"); } } mStatus = Status.RUNNING; onPreExecute(); mWorker.mParams = params; exec.execute(mFuture); return this; }
不難看出,最後是調用Executor的execute(Runnable command)方法啟動mFuture。默認情況下,sDefaultExecutor就是SerialExecutor類,所以為串行執行。當然用戶也可以提供自己的Executor來改變AsyncTask的運行方式。最後在THREAD_POOL_EXECUTOR真正啟動任務執行的Executor。
上面已經提到,Execute執行是調用Runnable的run()方法,也就是mFuture的run方法,繼續跟進代碼
public void run() { if (state != NEW || !U.compareAndSwapObject(this, RUNNER, null, Thread.currentThread())) return; try { Callablec = callable; if (c != null && state == NEW) { V result; boolean ran; try { result = c.call(); ran = true; } catch (Throwable ex) { result = null; ran = false; setException(ex); } if (ran) set(result); } } finally { runner = null; int s = state; if (s >= INTERRUPTING) handlePossibleCancellationInterrupt(s); } }
從第10行代碼可發現,最後是調用callable的call()方法。那麼這個callable是什麼呢?就是初始化mFuture傳入的mWorker對象。在前面的構造函數那邊可以發現call()方法,我們單獨分析一下這個方法
public Result call() throws Exception { mTaskInvoked.set(true); Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND); Result result = doInBackground(mParams); Binder.flushPendingCommands(); return postResult(result); }
看了這麼久,終於發現了doInBackground(),深深松了一口氣。執行完之後得到的結果,傳給postResult(result)。繼續跟進
private Result postResult(Result result) { Message message = getHandler().obtainMessage(MESSAGE_POST_RESULT, new AsyncTaskResult(this, result)); message.sendToTarget(); return result; }
可以發現,最後是通過Handler的方式,把消息發送出去,消息中攜帶了MESSAGE_POST_RESULT常量和一個表示任務執行結果的AsyncTaskResult對象。而getHandler()返回的sHandler是一個InternalHandler對象,InternalHandler源碼如下所示:
private static class InternalHandler extends Handler { public InternalHandler() { super(Looper.getMainLooper()); } @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; } } }
這裡對消息的類型進行了判斷,如果是MESSAGE_POST_RESULT,就執行finish(),如果是MESSAGE_POST_PROGRESS,就onProgressUpdate()方法。那麼什麼時候觸發如果是MESSAGE_POST_PROGRESS消息呢?就是在publishProgress()方法調用的時候,publishProgress()方法用finial標記,說明子類不能重寫他,不過可以手動調用,通知進度更新,這就表明了publishProgress可在子線程執行。
protected final void publishProgress(Progress... values) { if (!isCancelled()) { sHandler.obtainMessage(MESSAGE_POST_PROGRESS, new AsyncTaskResult
然後看一下finish()的代碼。
private void finish(Result result) { if (isCancelled()) { onCancelled(result); } else { onPostExecute(result); } mStatus = Status.FINISHED; }
不難發現,如果當前任務被取消掉了,就會調用onCancelled()方法,如果沒有被取消,則調用onPostExecute()方法,這樣當前任務的執行就全部結束了。並且,當你再次調用execute的時候,這個時候mStatus的狀態為Status.FINISHED,表示已經執行過了,那麼此時就會拋異常,這也就是為什麼一個AsyncTask對象只能執行一次的原因。
switch (mStatus) { case RUNNING: throw new IllegalStateException("Cannot execute task:" + " the task is already running."); case FINISHED: throw new IllegalStateException("Cannot execute task:" + " the task has already been executed " + "(a task can be executed only once)"); }
到這裡,就非常清晰了吧。徹底的了解了AsyncTask內部實現的邏輯。
可以看出,在使用AsyncTask的過程中,有許多需要注意的地方。
由於Handler需要和主線程交互,而Handler又是內置於AsnycTask中的,所以,AsyncTask的創建必須在主線程,execute的執行也應該在主線程。 AsyncTask的doInBackground(Params… Params)方法運行在子線程中,其他方法運行在主線程中,可以操作UI組件。 盡量不要手動的去調用AsyncTask的onPreExecute, doInBackground, publishProgress, onProgressUpdate, onPostExecute 這些方法,避免發生不可預知的問題。 一個任務AsyncTask任務只能被執行一次。否則會拋IllegalStateException異常 在doInBackground()中要檢查isCancelled()的返回值,如果你的異步任務是可以取消的話。cancel()僅僅是給AsyncTask對象設置了一個標識位,雖然運行中可以隨時調用cancel(boolean mayInterruptIfRunning)方法取消任務,如果成功調用isCancelled()會返回true,並且不會執行 onPostExecute() 方法了,取而代之的是調用 onCancelled() 方法。但是!!!值得注意的一點是,如果這個任務在執行之後調用cancel()方法是不會真正的把任務結束的,而是繼續執行,只不過改變的是執行之後的回調方法是 onPostExecute還是onCancelled。可以在doInBackground裡面去判斷isCancle,如果取消了,那就直接return result; 當然,這種方式也並非非常完美。 Asynctask的生命周期和它所在的activity的生命周期並非一致的,當Activity終止時,它會以它自有的方式繼續運行,即使你退出了整個應用程序。另一方面,要注意Android屏幕切換問題,因為這時候Activity會重新創建。多線程下載的原理司馬光砸缸,多開幾個小水管,搶救小朋友。 import java.io.BufferedReader;import java.io.File;i
Long Long ago...已經成為了歷史,我還是要說出一個真相:早年前,那時候,android還不被大眾所認知的時候,當然開發者也沒不像現在那樣趨於飽和狀態。一位大
本文的目的是要實現左右滑動的指引效果。那麼什麼是指引效果呢?現在的應用為了有更好的用戶體驗,一般會在應用開始顯示一些指引幫助頁面,使用戶能更好的理解應用的功能,甚至是一些
Android Emulator 給用戶提供 GPU on 選項,意思是利用 Host ( 就是運行 Emulator 的PC機) 的 GPU. 當然PC機必須把 Op