編輯:Android資訊
Android異步處理機制一直都是Android的一個核心,也是應用工程師面試的一個知識點。前面我們分析了Handler異步機制原理(不了解的可以閱讀我的《Android異步消息處理機制詳解及源碼分析》文章),這裡繼續分析Android的另一個異步機制AsyncTask的原理。
當使用線程和Handler組合實現異步處理時,當每次執行耗時操作都創建一條新線程進行處理,性能開銷會比較大。為了提高性能我們使用AsyncTask實現異步處理(其實也是線程和handler組合實現),因為其內部使用了java提供的線程池技術,有效的降低了線程創建數量及限定了同時運行的線程數,還有一些針對性的對池的優化操作。所以說AsyncTask是Android為我們提供的方便編寫異步任務的工具類。
先看下使用AsyncTask模擬下載的效果圖:
看下代碼,如下:
public class MainActivity extends Activity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); new TestAsyncTask(this).execute(); } static final class TestAsyncTask extends AsyncTask<Void, Integer, Boolean> { //如上三個泛型參數從左到右含義依次為: //1. 在執行AsyncTask時需要傳入的參數,可用於在後台任務中使用。 //2. 後台任務執行時,如果需要在界面上顯示當前的進度,則使用這個。 //3. 當任務執行完畢後,如果需要對結果進行返回,則使用這個。 private Context mContext = null; private ProgressDialog mDialog = null; private int mCount = 0; public TestAsyncTask(Context context) { mContext = context; } //在後台任務開始執行之間調用,用於進行一些界面上的初始化操作 protected void onPreExecute() { super.onPreExecute(); mDialog = new ProgressDialog(mContext); mDialog.setMax(100); mDialog.setProgressStyle(ProgressDialog.STYLE_HORIZONTAL); mDialog.show(); } //這個方法中的所有代碼都會在子線程中運行,我們應該在這裡去處理所有的耗時任務 protected Boolean doInBackground(Void... params) { while (mCount < 100) { publishProgress(mCount); mCount += 20; try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } } return true; } //當在後台任務中調用了publishProgress(Progress...)方法後,這個方法就很快會被調用 protected void onProgressUpdate(Integer... values) { super.onProgressUpdate(values); mDialog.setProgress(values[0]); } //當後台任務執行完畢並通過return語句進行返回時,這個方法就很快會被調用 protected void onPostExecute(Boolean aBoolean) { super.onPostExecute(aBoolean); if (aBoolean && mDialog != null && mDialog.isShowing()) { mDialog.dismiss(); } } } }
可以看見Android幫我們封裝好的AsyncTask還是很方便使用的,咱們不做過多說明。接下來直接分析源碼。
通過源碼可以發現AsyncTask是一個抽象類,所以我們在在上面使用時需要實現它。
那怎麼下手分析呢?很簡單,我們就依據上面示例的流程來分析源碼,具體如下。
/** * Creates a new asynchronous task. This constructor must be invoked on the UI thread. */ public AsyncTask() { mWorker = new WorkerRunnable<Params, Result>() { public Result call() throws Exception { mTaskInvoked.set(true); Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND); //noinspection unchecked return postResult(doInBackground(mParams)); } }; mFuture = new FutureTask<Result>(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 occured while executing doInBackground()", e.getCause()); } catch (CancellationException e) { postResultIfNotInvoked(null); } } }; }
看見注釋沒有,AsyncTask的實例化只能在UI線程中。然後整個構造函數就只初始化了兩個AsyncTask類的成員變量(mWorker和mFuture)。mWorker為匿名內部類的實例對象WorkerRunnable(實現了Callable接口),mFuture為匿名內部類的實例對象FutureTask,傳入了mWorker作為形參(重寫了FutureTask類的done方法)。
正如上面實例一樣,得到AsyncTask實例化對象之後就執行了execute方法,所以看下execute方法的源碼,如下:
public final AsyncTask<Params, Progress, Result> execute(Params... params) { return executeOnExecutor(sDefaultExecutor, params); }
可以看見,execute調運了executeOnExecutor方法,executeOnExecutor方法除過傳入了params形參以外,還傳入了一個static的SerialExecutor對象(SerialExecutor實現了Executor接口)。繼續看下executeOnExecutor源碼,如下:
public final AsyncTask<Params, Progress, Result> executeOnExecutor(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; }
首先判斷AsyncTask異步任務的狀態,當處於RUNNING和FINISHED時就報IllegalStateException非法狀態異常。由此可以看見一個AsyncTask的execute方法只能被調運一次。接著看見17行onPreExecute();沒有?看下這個方法源碼,如下:
/** * Runs on the UI thread before {@link #doInBackground}. * * @see #onPostExecute * @see #doInBackground */ protected void onPreExecute() { }
空方法,而且通過注釋也能看見,這不就是我們AsyncTask中第一個執行的方法嗎?是的。
回過頭繼續往下看,看見20行exec.execute(mFuture);代碼沒?exec就是形參出入的上面定義的static SerialExecutor對象(SerialExecutor實現了Executor接口),所以execute就是SerialExecutor靜態內部類的方法喽,在執行execute方法時還傳入了AsyncTask構造函數中實例化的第二個成員變量mFuture。我們來看下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在AsyncTask中是以常量的形式被使用的,所以在整個應用程序中的所有AsyncTask實例都會共用同一個SerialExecutor對象。
接著可以看見,SerialExecutor是使用ArrayDeque這個隊列來管理Runnable對象的,如果我們一次性啟動了很多個任務,首先在第一次運行execute()方法的時候會調用ArrayDeque的offer()方法將傳入的Runnable對象添加到隊列的最後,然後判斷mActive對象是不是等於null,第一次運行是null,然後調用scheduleNext()方法,在這個方法中會從隊列的頭部取值,並賦值給mActive對象,然後調用THREAD_POOL_EXECUTOR去執行取出的取出的Runnable對象。之後如果再有新的任務被執行時就等待上一個任務執行完畢後才會得到執行,所以說同一時刻只會有一個線程正在執行,其余的均處於等待狀態,這就是SerialExecutor類的核心作用。
我們再來看看上面用到的THREAD_POOL_EXECUTOR與execute,如下:
public abstract class AsyncTask<Params, Progress, Result> { ...... private static final int CPU_COUNT = Runtime.getRuntime().availableProcessors(); private static final int CORE_POOL_SIZE = CPU_COUNT + 1; private static final int MAXIMUM_POOL_SIZE = CPU_COUNT * 2 + 1; private static final int KEEP_ALIVE = 1; private static final ThreadFactory sThreadFactory = new ThreadFactory() { private final AtomicInteger mCount = new AtomicInteger(1); public Thread newThread(Runnable r) { return new Thread(r, "AsyncTask #" + mCount.getAndIncrement()); } }; private static final BlockingQueue<Runnable> sPoolWorkQueue = new LinkedBlockingQueue<Runnable>(128); /** * An {@link Executor} that can be used to execute tasks in parallel. */ public static final Executor THREAD_POOL_EXECUTOR = new ThreadPoolExecutor(CORE_POOL_SIZE, MAXIMUM_POOL_SIZE, KEEP_ALIVE, TimeUnit.SECONDS, sPoolWorkQueue, sThreadFactory); ...... }
看見沒有,實質就是在一個線程池中執行,這個THREAD_POOL_EXECUTOR線程池是一個常量,也就是說整個App中不論有多少AsyncTask都只有這一個線程池。也就是說上面SerialExecutor類中execute()方法的所有邏輯就是在子線程中執行,注意SerialExecutor的execute方法有一個Runnable參數,這個參數就是mFuture對象,所以我們看下FutureTask類的run()方法,如下源碼:
public void run() { if (state != NEW || !UNSAFE.compareAndSwapObject(this, runnerOffset, null, Thread.currentThread())) return; try { Callable<V> c = 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 must be non-null until state is settled to // prevent concurrent calls to run() runner = null; // state must be re-read after nulling runner to prevent // leaked interrupts int s = state; if (s >= INTERRUPTING) handlePossibleCancellationInterrupt(s); } }
看見沒有?第7行的c = callable;其實就是AsyncTask構造函數中實例化FutureTask對象時傳入的參數mWorker。12行看見result = c.call();
沒有?其實就是調運WorkerRunnable類的call方法,所以我們回到AsyncTask構造函數的WorkerRunnable匿名內部內中可以看見如下:
mWorker = new WorkerRunnable<Params, Result>() { public Result call() throws Exception { mTaskInvoked.set(true); Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND); //noinspection unchecked return postResult(doInBackground(mParams)); } };
看見沒有?在postResult()方法的參數裡面,我們可以看見doInBackground()方法。所以這驗證了我們上面例子中使用的AsyncTask,首先在主線程執行onPreExecute方法,接著在子線程執行doInBackground方法,所以這也就是為什麼我們可以在doInBackground()方法中去處理耗時操作的原因了,接著等待doInBackground方法耗時操作執行完畢以後將返回值傳遞給了postResult()方法。所以我們來看下postResult這個方法的源碼,如下:
private Result postResult(Result result) { @SuppressWarnings("unchecked") Message message = getHandler().obtainMessage(MESSAGE_POST_RESULT, new AsyncTaskResult<Result>(this, result)); message.sendToTarget(); return result; }
先看下這個getHandler拿到的是哪個Handler吧,如下:
private static class InternalHandler extends Handler { public InternalHandler() { super(Looper.getMainLooper()); } @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; } } }
看見沒有,拿到的是MainLooper,也就是說在在UI線程中的Handler(不清楚的請閱讀《Android異步消息處理機制詳解及源碼分析》文章)。所以上面的方法其實就是將子線程的數據發送到了UI來處理,也就是通過MESSAGE_POST_RESULT在handleMessage來處理。所以我們繼續看handleMessage中的result.mTask.finish(result.mData[0]);就會發現finish的代碼如下:
private void finish(Result result) { if (isCancelled()) { onCancelled(result); } else { onPostExecute(result); } mStatus = Status.FINISHED; }
看見沒有?依據返回值true與false回調AsyncTask的onPostExecute或者onCancelled方法。
到此是不是會好奇onProgressUpdate方法啥時候調運的呢?繼續往下看可以發現handleMessage方法中的MESSAGE_POST_PROGRESS不就是回調我們UI Thread中的onProgressUpdate方法嗎?那怎麼樣才能讓他回調呢?追蹤MESSAGE_POST_PROGRESS消息你會發現如下:
protected final void publishProgress(Progress... values) { if (!isCancelled()) { getHandler().obtainMessage(MESSAGE_POST_PROGRESS, new AsyncTaskResult<Progress>(this, values)).sendToTarget(); } }
額,沒意思了。這不就是我們上面例子中的在子線程的doInBackground耗時操作中調運通知回調onProgressUpdate的方法麼。
看見沒有,AsyncTask的實質就是Handler異步消息處理機制(不清楚的請閱讀《Android異步消息處理機制詳解及源碼分析》文章),只是對線程做了優化處理和封裝而已。
接觸Android比較久的可能都知道,在Android 3.0之前是並沒有SerialExecutor這個類的(上面有分析)。那些版本的代碼是直接創建了指定大小的線程池常量來執行task的。其中MAXIMUM_POOL_SIZE = 128;,所以那時候如果我們應用中一個界面需要同時創建的AsyncTask線程大於128(批量獲取數據,譬如照片浏覽瀑布流一次加載)程序直接就掛了。所以當時的AsyncTask因為這個原因臭名昭著。
回過頭來看看現在高版本的AsyncTask,是不是沒有這個問題了吧?因為現在是順序執行的。而且更勁爆的是現在的AsyncTask還直接提供了客戶化實現Executor接口功能,使用如下方法執行AsyncTask即可使用自定義Executor,如下:
public final AsyncTask<Params, Progress, Result> executeOnExecutor(Executor exec, Params... params) { ...... return this; }
可以看出,在3.0以上版中AsyncTask已經不存在那個臭名昭著的Bug了,所以可以放心使用了,媽媽再也不用擔心我的AsyncTask出Bug了。
前面文章也分析過Handler了,這裡也分析了AsyncTask,現在把他們兩拽一起來比較比較。具體如下:
到此整個Android的AsyncTask已經分析完畢,相信你現在對於AsyncTask會有一個很深入的理解與認識了。
Android中的TTextView很強大,我們可以不僅可以設置純文本為其內容,還可以設置包含網址和電子郵件地址的內容,並且使得這些點擊可以點擊。但是我們可以捕獲
Android安全加密專題文章索引 Android安全加密:對稱加密 Android安全加密:非對稱加密 Android安全加密:消息摘要Message Dig
由於我們很容易習慣公式化的預置代碼,有時我們會忽略很優雅的細節。LayoutInflater以及它在Fragment的onCreateView()中填充View的
本文由碼農網 – 小峰原創翻譯,轉載請看清文末的轉載要求,歡迎參與我們的付費投稿計劃! 自動化測試時下在產品測試上有著非常重要的作用。實現測試自動化有多種