關于正確使用Android AsyncTask學習整理

AsyncTask異步任務,用于執行耗時任務并在UI線程中更新結果。

我們都知道,Android UI線程中不能執行耗時的任務,否則就會出現ANR。對于耗時的操作就需要放到子線程中操作,操作完成后需要通知UI線程進行更新等操作,這就需要Android的異步消息機制(創建一個Message對象,使用Handler發送出去,然后在Handler的handleMessage()方法中獲得剛才發送的Message對象,然后在這里進行UI操作)。
不過本文要說的是AsyncTask,其實早在Android 1.5版本就引入這個類,所以我知道大多數人對它的用法都已經非常熟悉了。基本用法在網上搜搜就有很多教程,然而,在使用時,仍需要注意其潛在的問題以及缺陷。

[TOC]

AsyncTask 簡單使用


public class MainActivity extends AppCompatActivity implements View.OnClickListener {

    private static final String TAG = "MainActivity";
    private ProgressDialog mDialog;
    private AsyncTask mAsyncTask;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        mDialog = new ProgressDialog(this);
        mDialog.setMax(100);
        mDialog.setProgressStyle(ProgressDialog.STYLE_HORIZONTAL);
        mDialog.setCancelable(false);
        mAsyncTask = new MyAsyncTask();

        findViewById(R.id.tv).setOnClickListener(this);
    }

    @Override
    public void onClick(View view) {
        mAsyncTask.execute();
    }

    private class MyAsyncTask extends AsyncTask<Void, Integer, Void> {

        @Override
        protected void onPreExecute() {
            mDialog.show();
            Log.e(TAG, Thread.currentThread().getName() + " onPreExecute ");
        }

        @Override
        protected Void doInBackground(Void... params) {

            // 模擬數據的加載,耗時的任務
            for (int i = 0; i < 100; i++) {
                try {
                    Thread.sleep(80);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                publishProgress(i);
            }

            Log.e(TAG, Thread.currentThread().getName() + " doInBackground ");
            return null;
        }

        @Override
        protected void onProgressUpdate(Integer... values) {
            mDialog.setProgress(values[0]);
            Log.e(TAG, Thread.currentThread().getName() + " onProgressUpdate ");
        }

        @Override
        protected void onPostExecute(Void result) {
            // 進行數據加載完成后的UI操作
            mDialog.dismiss();
            Log.e(TAG, Thread.currentThread().getName() + " onPostExecute ");
        }
    }
}

如以上實例中,當UI線程中需求處理耗時的操作時,我們可以放在AsyncTask的doInBackground方法中執行,這個抽象的類,有幾個方法需要我們重新,除了doInBackground,我們可以在onPreExecute中為這個耗時方法進行一些預處理操作,同時我們在onPostExecute中對UI進行更新操作。實例中的publishProgress對應的回調是onProgressUpdate,這樣可以實時更新UI,提供更好的用戶體驗。

AsyncTask 原理

AsyncTask主要有二個部分:一個是與主線的交互,另一個就是線程的管理調度。雖然可能多個AsyncTask的子類的實例,但是AsyncTask的內部Handler和ThreadPoolExecutor都是進程范圍內共享的,其都是static的,也即屬于類的,類的屬性的作用范圍是CLASSPATH,因為一個進程一個VM,所以是AsyncTask控制著進程范圍內所有的子類實例。

  • 與主線程交互
    與主線程交互是通過Handler來進行的,因為本文主要探討AsyncTask在任務調度方面的,所以對于這部分不做細致介紹,感興趣的朋友可以繼續去看AsyncTask的源碼部分。

  • 線程任務的調度
    內部會創建一個進程作用域的線程池來管理要運行的任務,也就就是說當你調用了AsyncTask#execute()后,AsyncTask會把任務交給線程池,由線程池來管理創建Thread和運行Therad。對于內部的線程池不同版本的Android的實現方式是不一樣的:

AsyncTask 發展

接下來我們先簡單的了解一下AsyncTask的歷史

  • 首先在android 3.0之前的版本,ThreadPool的限制是5個,線程的并發量是128個,阻塞隊列長度10,也就是說超過138個則會拋出異常。因此我們在使用的時候,一定要主要這部分限制,正確的使用。
  • 到了在Android 3.0之后的,也許是Google也意識到這個問題,對AsyncTask的API做了調整:
    · execute()提交的任務,按先后順序每次只運行一個也就是說它是按提交的次序,每次只啟動一個線程執行一個任務,完成之后再執行第二個任務,也就是相當于只有一個后臺線程在執行所提交的任務(Executors.newSingleThreadPool())。
    · 新增了接口executeOnExecutor()這個接口允許開發者提供自定義的線程池來運行和調度Thread,如果你想讓所有的任務都能并發同時運行,那就創建一個沒有限制的線程池(Executors.newCachedThreadPool()),并提供給AsyncTask。這樣這個AsyncTask實例就有了自己的線程池而不必使用AsyncTask默認的。
    · 新增了二個預定義的線程池SERIAL_EXECUTORTHREAD_POOL_EXECUTOR。其實THREAD_POOL_EXECUTOR并不是新增的,之前的就有,只不過之前(Android 2.3)它是AsyncTask私有的,未公開而已。THREAD_POOL_EXECUTOR是一個corePoolSize為5的線程池,也就是說最多只有5個線程同時運行,超過5個的就要等待。所以如果使用executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR)就跟2.3版本的AsyncTask.execute()效果是一樣的。而SERIAL_EXECUTOR是新增的,它的作用是保證任務執行的順序,也就是它可以保證提交的任務確實是按照先后順序執行的。它的內部有一個隊列用來保存所提交的任務,保證當前只運行一個,這樣就可以保證任務是完全按照順序執行的,默認的execute()使用的就是這個,也就是executeOnExecutor(AsyncTask.SERIAL_EXECUTOR)與execute()是一樣的。

AsyncTask 源碼簡析

  • 這里我們從AsyncTask的起點開始分析,主要有 execute()executeOnExecutor()
public final AsyncTask<Params, Progress, Result> execute(Params... params) {  
        return executeOnExecutor(sDefaultExecutor, params);  
}  
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;  
    }  
  1. 從代碼中可以看出,execute()其實也是通過執行executeOnExecutor()方法,只是將其中的Executor設置為默認值。
  2. executeOnExecutor()中將當前AsyncTask的狀態為RUNNING,上面的switch也可以看出,每個異步任務在完成前只能執行一次。
  3. 接下來就執行了onPreExecute(),當前依然在UI線程,所以我們可以在其中做一些準備工作。
  4. 將我們傳入的參數賦值給了mWorker.mParams
  5. 最后exec.execute(mFuture)
    相信大家對代碼中出現的mWorker,以及mFuture都會有些困惑。接下來我們來看看mWorker找到這個類:
private static abstract class WorkerRunnable<Params, Result> implements Callable<Result> {  
        Params[] mParams;  
}  

可以看到是Callable的子類,且包含一個mParams用于保存我們傳入的參數,下面看初始化mWorker的代碼:

     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));  
            }  
        };  
    //...
   }  

可以看到mWorker在構造方法中完成了初始化,并且因為是一個抽象類,在這里new了一個實現類,實現了call方法,call方法中設置mTaskInvoked=true,且最終調用doInBackground(mParams)方法,并返回Result值作為參數給postResult方法.可以看到我們的doInBackground出現了,下面繼續看:

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

可以看到postResult中出現了我們熟悉的異步消息機制,傳遞了一個消息message, message.what為MESSAGE_POST_RESULT;message.object= new AsyncTaskResult(this,result);

private static class AsyncTaskResult<Data> {  
       final AsyncTask mTask;  
       final Data[] mData;  
  
       AsyncTaskResult(AsyncTask task, Data... data) {  
           mTask = task;  
           mData = data;  
       }  
   }  

AsyncTaskResult就是一個簡單的攜帶參數的對象。
看到這,我相信大家肯定會想到,在某處肯定存在一個sHandler,且復寫了其handleMessage方法等待消息的傳入,以及消息的處理。

private static final InternalHandler sHandler = new 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;  
            }  
        }  
}  

這里出現了我們的handleMessage,可以看到,在接收到MESSAGE_POST_RESULT消息時,執行了result.mTask.finish(result.mData[0]);其實就是我們的AsyncTask.this.finish(result),于是看finish方法

private void finish(Result result) {  
        if (isCancelled()) {  
            onCancelled(result);  
        } else {  
            onPostExecute(result);  
        }  
        mStatus = Status.FINISHED;  
    }  

可以看到,如果我們調用了cancel()則執行onCancelled回調;正常執行的情況下調用我們的onPostExecute(result);主要這里的調用是在handler的handleMessage中,所以是在UI線程中。最后將狀態置為FINISHED。
mWoker看完了,應該到我們的mFuture了,依然實在構造方法中完成mFuture的初始化,將mWorker作為參數,復寫了其done方法。

public AsyncTask() {  
    ...  
        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);  
                }  
            }  
        };  
}  

任務執行結束會調用:postResultIfNotInvoked(get());get()表示獲取mWorker的call的返回值,即Result.然后看postResultIfNotInvoked方法

private void postResultIfNotInvoked(Result result) {  
        final boolean wasTaskInvoked = mTaskInvoked.get();  
        if (!wasTaskInvoked) {  
            postResult(result);  
        }  
}  

如果mTaskInvoked不為true,則執行postResult;但是在mWorker初始化時就已經將mTaskInvoked為true,所以一般這個postResult執行不到。好了,到了這里,已經介紹完了execute方法中出現了mWorker和mFurture,不過這里一直是初始化這兩個對象的代碼,并沒有真正的執行。下面我們看真正調用執行的地方。execute方法中的:還記得上面的execute中的:exec.execute(mFuture)
exec為executeOnExecutor(sDefaultExecutor, params)中的sDefaultExecutor
下面看這個sDefaultExecutor

private static volatile Executor sDefaultExecutor = SERIAL_EXECUTOR;  
public static final Executor SERIAL_EXECUTOR = new 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);  
            }  
        }  
}  

可以看到sDefaultExecutor其實為SerialExecutor的一個實例,其內部維持一個任務隊列;直接看其execute(Runnable runnable)方法,將runnable放入mTasks隊尾;再判斷當前mActive是否為空,為空則調用scheduleNext。方法scheduleNext,則直接取出任務隊列中的隊首任務,如果不為null則傳入THREAD_POOL_EXECUTOR進行執行。下面看THREAD_POOL_EXECUTOR為何方神圣:

public static final Executor THREAD_POOL_EXECUTOR  
          =new ThreadPoolExecutor(CORE_POOL_SIZE, MAXIMUM_POOL_SIZE, KEEP_ALIVE,  
                    TimeUnit.SECONDS, sPoolWorkQueue, sThreadFactory); 

可以看到就是一個自己設置參數的線程池,參數為:

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 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>(10);  

看到這里,大家可能會認為,背后原來有一個線程池,且最大支持128的線程并發,加上長度為10的阻塞隊列,可能會覺得就是在快速調用138個以內的AsyncTask子類的execute方法不會出現問題,而大于138則會拋出異常。其實不是這樣的,我們再仔細看一下代碼,回顧一下sDefaultExecutor,真正在execute()中調用的為sDefaultExecutor.execute:

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

可以看到,如果此時有10個任務同時調用execute(s synchronized)方法,第一個任務入隊,然后在mActive = mTasks.poll()) != null被取出,并且賦值給mActivte,然后交給線程池去執行。然后第二個任務入隊,但是此時mActive并不為null,并不會執行scheduleNext();所以如果第一個任務比較慢,10個任務都會進入隊列等待;真正執行下一個任務的時機是,線程池執行完成第一個任務以后,調用Runnable中的finally代碼塊中的scheduleNext,所以雖然內部有一個線程池,其實調用的過程還是線性的。一個接著一個的執行,相當于單線程。

總結:

AsyncTask在并發執行多個任務時發生異常。其實還是存在的,在3.0以前的系統中還是會以支持多線程并發的方式執行,支持并發數也是我們上面所計算的128,阻塞隊列可以存放10個;也就是同時執行138個任務是沒有問題的;而超過138會馬上出現java.util.concurrent.RejectedExecutionException;而在在3.0以上包括3.0的系統中會為單線程執行(即我們上面代碼的分析);

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 229,362評論 6 537
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 99,013評論 3 423
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 177,346評論 0 382
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,421評論 1 316
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 72,146評論 6 410
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 55,534評論 1 325
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,585評論 3 444
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,767評論 0 289
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 49,318評論 1 335
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 41,074評論 3 356
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 43,258評論 1 371
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,828評論 5 362
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,486評論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,916評論 0 28
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 36,156評論 1 290
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,993評論 3 395
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 48,234評論 2 375

推薦閱讀更多精彩內容