亚洲香蕉成人av网站在线观看_欧美精品成人91久久久久久久_久久久久久久久久久亚洲_热久久视久久精品18亚洲精品_国产精自产拍久久久久久_亚洲色图国产精品_91精品国产网站_中文字幕欧美日韩精品_国产精品久久久久久亚洲调教_国产精品久久一区_性夜试看影院91社区_97在线观看视频国产_68精品久久久久久欧美_欧美精品在线观看_国产精品一区二区久久精品_欧美老女人bb

首頁 > 系統 > Android > 正文

Android AsyncTask完全解析 帶你從源碼的角度徹底理解

2019-12-12 06:41:22
字體:
來源:轉載
供稿:網友

我們都知道,Android UI是線程不安全的,如果想要在子線程里進行UI操作,就需要借助Android的異步消息處理機制。之前我也寫過了一篇文章從源碼層面分析了Android的異步消息處理機制。

不過為了更加方便我們在子線程中更新UI元素,Android從1.5版本就引入了一個AsyncTask類,使用它就可以非常靈活方便地從子線程切換到UI線程,我們本篇文章的主角也就正是它了。
AsyncTask很早就出現在Android的API里了,所以我相信大多數朋友對它的用法都已經非常熟悉。不過今天我還是準備從AsyncTask的基本用法開始講起,然后我們再來一起分析下AsyncTask源碼,看看它是如何實現的,最后我會介紹一些關于AsyncTask你所不知道的秘密。
AsyncTask的基本用法
首先來看一下AsyncTask的基本用法,由于AsyncTask是一個抽象類,所以如果我們想使用它,就必須要創建一個子類去繼承它。在繼承時我們可以為AsyncTask類指定三個泛型參數,這三個參數的用途如下:
1. Params
在執行AsyncTask時需要傳入的參數,可用于在后臺任務中使用。
2. Progress
后臺任務執行時,如果需要在界面上顯示當前的進度,則使用這里指定的泛型作為進度單位。
3. Result
當任務執行完畢后,如果需要對結果進行返回,則使用這里指定的泛型作為返回值類型。
因此,一個最簡單的自定義AsyncTask就可以寫成如下方式:

class DownloadTask extends AsyncTask<Void, Integer, Boolean> {   …… } 

這里我們把AsyncTask的第一個泛型參數指定為Void,表示在執行AsyncTask的時候不需要傳入參數給后臺任務。第二個泛型參數指定為Integer,表示使用整型數據來作為進度顯示單位。第三個泛型參數指定為Boolean,則表示使用布爾型數據來反饋執行結果。
當然,目前我們自定義的DownloadTask還是一個空任務,并不能進行任何實際的操作,我們還需要去重寫AsyncTask中的幾個方法才能完成對任務的定制。經常需要去重寫的方法有以下四個:
1. onPreExecute()
這個方法會在后臺任務開始執行之間調用,用于進行一些界面上的初始化操作,比如顯示一個進度條對話框等。
2. doInBackground(Params...)
這個方法中的所有代碼都會在子線程中運行,我們應該在這里去處理所有的耗時任務。任務一旦完成就可以通過return語句來將任務的執行結果進行返回,如果AsyncTask的第三個泛型參數指定的是Void,就可以不返回任務執行結果。注意,在這個方法中是不可以進行UI操作的,如果需要更新UI元素,比如說反饋當前任務的執行進度,可以調用publishProgress(Progress...)方法來完成。
3. onProgressUpdate(Progress...)
當在后臺任務中調用了publishProgress(Progress...)方法后,這個方法就很快會被調用,方法中攜帶的參數就是在后臺任務中傳遞過來的。在這個方法中可以對UI進行操作,利用參數中的數值就可以對界面元素進行相應的更新。
4. onPostExecute(Result)
當后臺任務執行完畢并通過return語句進行返回時,這個方法就很快會被調用。返回的數據會作為參數傳遞到此方法中,可以利用返回的數據來進行一些UI操作,比如說提醒任務執行的結果,以及關閉掉進度條對話框等。
因此,一個比較完整的自定義AsyncTask就可以寫成如下方式:

class DownloadTask extends AsyncTask<Void, Integer, Boolean> {    @Override   protected void onPreExecute() {     progressDialog.show();   }    @Override   protected Boolean doInBackground(Void... params) {     try {       while (true) {         int downloadPercent = doDownload();         publishProgress(downloadPercent);         if (downloadPercent >= 100) {           break;         }       }     } catch (Exception e) {       return false;     }     return true;   }    @Override   protected void onProgressUpdate(Integer... values) {     progressDialog.setMessage("當前下載進度:" + values[0] + "%");   }    @Override   protected void onPostExecute(Boolean result) {     progressDialog.dismiss();     if (result) {       Toast.makeText(context, "下載成功", Toast.LENGTH_SHORT).show();     } else {       Toast.makeText(context, "下載失敗", Toast.LENGTH_SHORT).show();     }   } } 

這里我們模擬了一個下載任務,在doInBackground()方法中去執行具體的下載邏輯,在onProgressUpdate()方法中顯示當前的下載進度,在onPostExecute()方法中來提示任務的執行結果。如果想要啟動這個任務,只需要簡單地調用以下代碼即可:
new DownloadTask().execute(); 
以上就是AsyncTask的基本用法,怎么樣,是不是感覺在子線程和UI線程之間進行切換變得靈活了很多?我們并不需求去考慮什么異步消息處理機制,也不需要專門使用一個Handler來發送和接收消息,只需要調用一下publishProgress()方法就可以輕松地從子線程切換到UI線程了。
分析AsyncTask的源碼
雖然AsyncTask這么簡單好用,但你知道它是怎樣實現的嗎?那么接下來,我們就來分析一下AsyncTask的源碼,對它的實現原理一探究竟。注意這里我選用的是Android 4.0的源碼,如果你查看的是其它版本的源碼,可能會有一些出入。
從之前DownloadTask的代碼就可以看出,在啟動某一個任務之前,要先new出它的實例,因此,我們就先來看一看AsyncTask構造函數中的源碼,如下所示:

public AsyncTask() {   mWorker = new WorkerRunnable<Params, Result>() {     public Result call() throws Exception {       mTaskInvoked.set(true);       Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);       return postResult(doInBackground(mParams));     }   };   mFuture = new FutureTask<Result>(mWorker) {     @Override     protected void done() {       try {         final Result result = get();         postResultIfNotInvoked(result);       } 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);       } catch (Throwable t) {         throw new RuntimeException("An error occured while executing "             + "doInBackground()", t);       }     }   }; } 

這段代碼雖然看起來有點長,但實際上并沒有任何具體的邏輯會得到執行,只是初始化了兩個變量,mWorker和mFuture,并在初始化mFuture的時候將mWorker作為參數傳入。mWorker是一個Callable對象,mFuture是一個FutureTask對象,這兩個變量會暫時保存在內存中,稍后才會用到它們。
接著如果想要啟動某一個任務,就需要調用該任務的execute()方法,因此現在我們來看一看execute()方法的源碼,如下所示:

public final AsyncTask<Params, Progress, Result> execute(Params... params) {   return executeOnExecutor(sDefaultExecutor, params); } 

簡單的有點過分了,只有一行代碼,僅是調用了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; } 

果然,這里的代碼看上去才正常點。可以看到,在第15行調用了onPreExecute()方法,因此證明了onPreExecute()方法會第一個得到執行??墒墙酉聛淼拇a就看不明白了,怎么沒見到哪里有調用doInBackground()方法呢?別著急,慢慢找總會找到的,我們看到,在第17行調用了Executor的execute()方法,并將前面初始化的mFuture對象傳了進去,那么這個Executor對象又是什么呢?查看上面的execute()方法,原來是傳入了一個sDefaultExecutor變量,接著找一下這個sDefaultExecutor變量是在哪里定義的,源碼如下所示:

public static final Executor SERIAL_EXECUTOR = new SerialExecutor(); …… private static volatile Executor sDefaultExecutor = SERIAL_EXECUTOR; 

可以看到,這里先new出了一個SERIAL_EXECUTOR常量,然后將sDefaultExecutor的值賦值為這個常量,也就是說明,剛才在executeOnExecutor()方法中調用的execute()方法,其實也就是調用的SerialExecutor類中的execute()方法。那么我們自然要去看看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類中也有一個execute()方法,這個方法里的所有邏輯就是在子線程中執行的了,注意這個方法有一個Runnable參數,那么目前這個參數的值是什么呢?當然就是mFuture對象了,也就是說在第9行我們要調用的是FutureTask類的run()方法,而在這個方法里又會去調用Sync內部類的innerRun()方法,因此我們直接來看innerRun()方法的源碼:

void innerRun() {   if (!compareAndSetState(READY, RUNNING))     return;   runner = Thread.currentThread();   if (getState() == RUNNING) { // recheck after setting thread     V result;     try {       result = callable.call();     } catch (Throwable ex) {       setException(ex);       return;     }     set(result);   } else {     releaseShared(0); // cancel   } } 

可以看到,在第8行調用了callable的call()方法,那么這個callable對象是什么呢?其實就是在初始化mFuture對象時傳入的mWorker對象了,此時調用的call()方法,也就是一開始在AsyncTask的構造函數中指定的,我們把它單獨拿出來看一下,代碼如下所示:

public Result call() throws Exception {   mTaskInvoked.set(true);   Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);   return postResult(doInBackground(mParams)); } 

在postResult()方法的參數里面,我們終于找到了doInBackground()方法的調用處,雖然經過了很多周轉,但目前的代碼仍然是運行在子線程當中的,所以這也就是為什么我們可以在doInBackground()方法中去處理耗時的邏輯。接著將doInBackground()方法返回的結果傳遞給了postResult()方法,這個方法的源碼如下所示:

private Result postResult(Result result) {   Message message = sHandler.obtainMessage(MESSAGE_POST_RESULT,       new AsyncTaskResult<Result>(this, result));   message.sendToTarget();   return result; } 

如果你已經熟悉了異步消息處理機制,這段代碼對你來說一定非常簡單吧。這里使用sHandler對象發出了一條消息,消息中攜帶了MESSAGE_POST_RESULT常量和一個表示任務執行結果的AsyncTaskResult對象。這個sHandler對象是InternalHandler類的一個實例,那么稍后這條消息肯定會在InternalHandler的handleMessage()方法中被處理。InternalHandler的源碼如下所示:

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;     }   } } 

這里對消息的類型進行了判斷,如果這是一條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()方法,這樣當前任務的執行就全部結束了。
我們注意到,在剛才InternalHandler的handleMessage()方法里,還有一種MESSAGE_POST_PROGRESS的消息類型,這種消息是用于當前進度的,調用的正是onProgressUpdate()方法,那么什么時候才會發出這樣一條消息呢?相信你已經猜到了,查看publishProgress()方法的源碼,如下所示:

protected final void publishProgress(Progress... values) {   if (!isCancelled()) {     sHandler.obtainMessage(MESSAGE_POST_PROGRESS,         new AsyncTaskResult<Progress>(this, values)).sendToTarget();   } } 

非常清晰了吧!正因如此,在doInBackground()方法中調用publishProgress()方法才可以從子線程切換到UI線程,從而完成對UI元素的更新操作。其實也沒有什么神秘的,因為說到底,AsyncTask也是使用的異步消息處理機制,只是做了非常好的封裝而已。
讀到這里,相信你對AsyncTask中的每個回調方法的作用、原理、以及何時會被調用都已經搞明白了吧。
關于AsyncTask你所不知道的秘密
不得不說,剛才我們在分析SerialExecutor的時候,其實并沒有分析的很仔細,僅僅只是關注了它會調用mFuture中的run()方法,但是至于什么時候會調用我們并沒有進一步地研究。其實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個任務。
好了,到這里我們就已經把關于AsyncTask的所有重要內容深入淺出地理解了一遍,相信在將來使用它的時候能夠更加得心應手。

發表評論 共有條評論
用戶名: 密碼:
驗證碼: 匿名發表
亚洲香蕉成人av网站在线观看_欧美精品成人91久久久久久久_久久久久久久久久久亚洲_热久久视久久精品18亚洲精品_国产精自产拍久久久久久_亚洲色图国产精品_91精品国产网站_中文字幕欧美日韩精品_国产精品久久久久久亚洲调教_国产精品久久一区_性夜试看影院91社区_97在线观看视频国产_68精品久久久久久欧美_欧美精品在线观看_国产精品一区二区久久精品_欧美老女人bb
美日韩丰满少妇在线观看| 成人国产精品久久久久久亚洲| 欧美激情欧美激情在线五月| 国产成人+综合亚洲+天堂| 午夜精品一区二区三区在线播放| 国产精品成人va在线观看| 国产精品久久久久久久久久久久久| 96精品久久久久中文字幕| 国产第一区电影| 中文字幕欧美精品日韩中文字幕| 91色p视频在线| 日韩久久精品电影| 亚洲欧美日韩一区二区在线| 国产suv精品一区二区三区88区| 91国产中文字幕| 免费97视频在线精品国自产拍| 久久成人精品一区二区三区| 欧美一级视频一区二区| 91在线视频免费| 伊人精品在线观看| 欧美一区二区三区四区在线| 日韩在线欧美在线国产在线| 国产亚洲福利一区| 欧美成人免费播放| 97视频在线观看免费高清完整版在线观看| 一本一本久久a久久精品综合小说| 亚洲在线免费看| 欧美资源在线观看| 亚洲欧美一区二区三区久久| 国产精品十八以下禁看| 亚洲精品一区二区在线| 国产成人精品日本亚洲专区61| 国产精品高清在线| 亚洲综合在线中文字幕| 综合欧美国产视频二区| 国产一区视频在线| 国产精品一区二区性色av| 国产一区二区精品丝袜| 国产日韩在线看片| 亚洲欧美日韩视频一区| 国产精品夫妻激情| 日韩精品免费在线视频观看| 欧美激情国产日韩精品一区18| 欧美激情2020午夜免费观看| 久久精品一区中文字幕| 国产精品视频区1| 夜夜嗨av一区二区三区四区| 中文字幕日韩电影| 欧美wwwwww| 青草热久免费精品视频| 美日韩精品免费观看视频| 日韩中文字幕在线视频| 国产成人综合精品| 69av在线播放| 色综合五月天导航| 亚洲va久久久噜噜噜| 亚洲成avwww人| 日韩麻豆第一页| 国产91在线播放精品91| 日本国产一区二区三区| 中文字幕九色91在线| 欧美电影免费观看| 日韩麻豆第一页| 黑丝美女久久久| 4k岛国日韩精品**专区| 久久久免费观看| 亚洲乱码av中文一区二区| 国产成人在线亚洲欧美| 成人午夜在线观看| 在线观看成人黄色| 91精品国产高清自在线看超| 久久久久久91| 欧美怡春院一区二区三区| 日韩欧美一区二区三区| 日韩精品中文字幕视频在线| 中文国产亚洲喷潮| 一本色道久久88综合日韩精品| 久久韩国免费视频| 亚洲xxxxx电影| 国产精品成人av在线| 亚洲第一精品夜夜躁人人爽| 亚洲午夜性刺激影院| 国产91色在线|免| 久久精品国产清自在天天线| 国产主播喷水一区二区| 国产欧美va欧美va香蕉在线| 欧美在线激情视频| 福利视频导航一区| 久久精品国产亚洲| 成人深夜直播免费观看| 亚洲最新av在线| 日韩精品视频在线观看免费| 亚洲自拍高清视频网站| 中文字幕无线精品亚洲乱码一区| 欧美精品成人在线| 一二美女精品欧洲| 国产成人精品av| 成人在线观看视频网站| 亚洲精品wwwww| 97国产精品视频人人做人人爱| 97国产一区二区精品久久呦| 中文字幕亚洲无线码a| 在线日韩精品视频| 亚洲欧洲xxxx| 国产精品久久久久久婷婷天堂| 日韩中文字幕精品| 欧美高清视频在线观看| 欧美老女人www| 国产精品久久久久久av福利软件| 欧美另类xxx| 亚洲精品国产精品国自产在线| 欧美日韩亚洲精品内裤| 亚洲综合av影视| 久久精品视频99| 草民午夜欧美限制a级福利片| 全球成人中文在线| 成人中文字幕在线观看| 色偷偷噜噜噜亚洲男人| 久久久久亚洲精品国产| 国产成人精品免费视频| 日韩美女视频免费在线观看| 亚洲人成在线观看| 亚洲人在线观看| 色偷偷噜噜噜亚洲男人的天堂| 国产精品美女在线| 日韩av免费一区| 51精品国产黑色丝袜高跟鞋| 国产亚洲欧美日韩精品| 欧美成在线视频| 91精品国产综合久久香蕉的用户体验| 亚洲人成网7777777国产| 久久精品亚洲精品| 川上优av一区二区线观看| 亚洲aaaaaa| 国产免费成人av| 8x拔播拔播x8国产精品| 日韩中文字幕网站| 精品久久中文字幕久久av| 日韩成人在线免费观看| 午夜精品99久久免费| 亚洲综合小说区| 欧美性色视频在线| 欧美极品欧美精品欧美视频| 日韩av免费在线| 欧美在线视频免费播放| 神马久久桃色视频| 色av中文字幕一区| 91九色蝌蚪国产| 久久久久久午夜| 久久视频国产精品免费视频在线| 亚洲黄色www网站| 日韩电影免费在线观看| 亲爱的老师9免费观看全集电视剧| 国产视频观看一区| 欧美大片免费看| 欧美日韩国产成人高清视频| 亚洲国产97在线精品一区| 欧美日韩综合视频网址| 久久精品99国产精品酒店日本| 欧美日韩国产一区二区三区| 97不卡在线视频| 免费99精品国产自在在线| 欧美黄色小视频| 日韩欧美aⅴ综合网站发布|