AsyncTask 源碼分析

AsyncTask 簡介

在 Android 中執(zhí)行耗時任務時吐辙,我們一般不直接自己 new 一個 Thread,而且在 Android Studio 中也會給出提示信姓,不建議使用傳統(tǒng)的 Thread,那么有哪些方式呢宽菜?

  • HandlerThread 是一個 Thread葵姥,內(nèi)部使用 Handler,它與普通 Thread 的區(qū)別是通過 handler 向消息隊列中添加消息紊浩,優(yōu)勢是可以利用 Message 做相關控制旭等,而且 HandlerThread 不調用 quit()酌呆,線程一直存在

  • IntentService 是對 HandlerThread 的一個應用,同時也是 Service搔耕,優(yōu)先級更高隙袁,在后臺執(zhí)行時,不容易被殺死弃榨,執(zhí)行完畢自動退出

  • AsyncTask 是一種輕量級的異步任務菩收,底層執(zhí)行利用線程池,同時也利用 Handler 在執(zhí)行任務時可以在 UI 線程更新進度鲸睛,執(zhí)行結束后坛梁,也可以在 UI 線程更新結果,對于特別耗時的任務腊凶,不建議使用

  • 線程池 特別耗時任務時建議使用線程,多線程執(zhí)行效率更高,充分利用 CPU

HandlerThread钧萍、IntentService褐缠、AsyncTask 中都用到了 Handler。如果對消息機制還不是很熟悉的話风瘦,可以看看我之前寫的兩篇關于 Handler 的文章

消息機制 - Handler 使用

深入理解 Handler 消息機制

對于 HandlerThread 和 IntentService 的使用及源碼分析队魏,可以看看之前的文章

異步線程之 HandlerThread 和 IntentService

不同的線程有不同的特點及使用場景,那么 AsyncTask 的使用場景是怎么樣的万搔?怎么評估呢胡桨?主要是從兩點出發(fā),一個是在后臺執(zhí)行過程中瞬雹,需要有進度顯示昧谊,類似于下載進度條,執(zhí)行后更新結果酗捌;另一個是輕量級呢诬,特別耗時的任務不太適合,任務特別耗時胖缤,建議使用線程池尚镰。

下面就來看看 AsyncTask 的使用以及 AsyncTask 的主要源碼

AsyncTask 使用

這里使用 AsyncTask 完成一個下載的示例,后臺下載哪廓,顯示進度狗唉。

AsyncTask 是一個抽象的泛型類,具有 3 個泛型參數(shù)涡真,Params, Progress, Result分俯,其中 Params 表示參數(shù)類型,執(zhí)行時综膀,傳遞給 doInBackground() 方法澳迫,Progress 表示后臺任務執(zhí)行進度的類型,Result 表示后臺任務執(zhí)行完是返回的結果類型剧劝,當然未執(zhí)行完時也會返回該類型的結果橄登,但不一定是最終我們期望的結果。如果不需要參數(shù)時讥此,可以使用 Void 類型代替拢锹。

(1) 繼承 AsyncTask,重寫 onPreExecute()萄喳、onPostExecute(String s)卒稳、onProgressUpdate(Float... values)、doInBackground(String... strings) 四個方法他巨。

這里沒有將 MyAsyncTask 寫在 MainActivity 中充坑,而是單拿出來减江,所以需要有回調接口 TaskCallBack,進行進度更新和結果返回捻爷。

public class MyAsyncTask extends AsyncTask<String, Float, String> {

    private static final int COUNT = 1000;
    private TaskCallBack mCallBack;

    public static MyAsyncTask newTask() {
        return new MyAsyncTask();
    }

    private MyAsyncTask() {
        super();
    }

    @Override
    protected void onPreExecute() {
        super.onPreExecute();
        mCallBack.onPrepare();
    }

    @Override
    protected void onPostExecute(String s) {
        super.onPostExecute(s);
        mCallBack.onFinished(s);
    }

    @Override
    protected void onProgressUpdate(Float... values) {
        super.onProgressUpdate(values);
        mCallBack.onUpdate(values);
    }

    @Override
    protected void onCancelled(String s) {
        super.onCancelled(s);
        mCallBack.onCancelled(s);
    }

    @Override
    protected String doInBackground(String... strings) {
        int i = 1;
        while (i < COUNT) {
            publishProgress(i * 1.0f / COUNT);
            // 每完成 10%辈灼,睡眠 2 秒
            if (i % 100 == 0) {
                try {
                    Thread.sleep(2000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                    return "未下載完成 - interrupt - " + i * 1.0f / COUNT;
                }
            }
            i++;
            if (isCancelled()) {
                return "未下載完成 - " + i * 1.0f / COUNT;
            }
        }
        return "下載完成";
    }

    public void setCallBack(TaskCallBack callBack) {
        mCallBack = callBack;
    }

    public interface TaskCallBack {

        void onPrepare();

        void onUpdate(Float[] values);

        void onCancelled(String s);

        void onFinished(String s);
    }
}

(2)設置顯示進度對話框

private void initDialog() {
    mDownLoadDialog = new DownLoadDialog(this);
    mDownLoadDialog.setCancelable(false);
    mDownLoadDialog.updateProgress(0);
    mDownLoadDialog.setOnDismissListener(new DialogInterface.OnDismissListener() {
        @Override
        public void onDismiss(DialogInterface dialog) {
            mAsyncTask.cancel(true);
        }
    });
}

(3)創(chuàng)建 AsyncTask,設置監(jiān)聽回調

/**
 * 主線程中開啟 AsyncTask
 */
private void startNewTask() {
    mAsyncTask = MyAsyncTask.newTask();
    mAsyncTask.setCallBack(new MyAsyncTask.TaskCallBack(){
        @Override
        public void onPrepare() {
            mDownLoadDialog.show();
        }

        @Override
        public void onUpdate(Float[] values) {
            mDownLoadDialog.updateProgress(values[0]);
        }

        @Override
        public void onCancelled(String s) {
            Toast.makeText(MainActivity.this, "已經(jīng)取消下載 - " + s, Toast.LENGTH_SHORT).show();
        }

        @Override
        public void onFinished(String s) {
            if (mDownLoadDialog.isShowing()) {
                mDownLoadDialog.dismiss();
            }
            Toast.makeText(MainActivity.this, "下載完成 - " + s, Toast.LENGTH_SHORT).show();
        }
    });
    mAsyncTask.execute("開始下載");
}

(4)設置點擊事件也榄,開啟任務

findViewById(R.id.start_btn).setOnClickListener(new View.OnClickListener(){
    @Override
    public void onClick(View view) {
        initDialog();
        startNewTask();
    }
})
asynctask_demo.gif

以上就是模擬下載進度的一個小例子巡莹,中間執(zhí)行過程中,每完成 10%甜紫,睡眠 2 秒鐘降宅,模擬停頓一下。onPreExecute()囚霸、onPostExecute(String s)腰根、onProgressUpdate(Float... values) 這 3 個方法是在主線程中執(zhí)行的,其中 onPreExecute()在子線程開啟前已經(jīng)執(zhí)行邮辽,沒有用到 Handler唠雕,onProgressUpdate 和 onPostExecute 方法是通過 Handler 切換到主線程中的,所以更新 UI 也不會出現(xiàn)問題吨述,doInBackground(String... strings) 執(zhí)行耗時任務岩睁,在子線程中執(zhí)行。

有一點需要注意:
例子中的 doInBackground 方法每完成 10%揣云,睡眠 2 秒鐘捕儒,這過程中如果取消任務,會大概率拋出一個 InterruptedException邓夕,為什么是大概率呢刘莹?如果在睡眠時,被打斷焚刚,就會拋出 InterruptedException点弯,此時如果不返回,線程會繼續(xù)執(zhí)行矿咕;如果不是在睡眠過程中被打斷抢肛,那么就不會拋出 InterruptedException,但是通過 isCancelled() 判斷碳柱,線程處于 interrupt 狀態(tài)捡絮,執(zhí)行返回操作,如果不做返回操作莲镣,會繼續(xù)執(zhí)行福稳,這里涉及線程的中斷的知識,不做詳細講解瑞侮。大概率拋出 InterruptedException 異常的圆,因為在 while循環(huán)中鼓拧,其他操作耗時相對于睡眠的 2 秒鐘來說,基本可以忽略略板,所以取消任務是毁枯,幾乎都會中斷睡眠,拋出 InterruptedException 異常叮称。

AsyncTask 源碼分析

在 AsyncTask 的注釋中,提到使用 AsyncTask 的使用注意事項:

  • (1)AsyncTask 類需要 UI 線程中加載藐鹤,在 Android 4.1 以上版本瓤檐,這個加載過程已經(jīng)由系統(tǒng)來完成了,在 ActivityThread 中使用到了 AsyncTask娱节,自然也就完成了加載過程挠蛉。

  • (2)AsyncTask 實例需要 UI 線程中創(chuàng)建

  • (3)execute 方法必須在 UI 線程中被調用

  • (4)不要直接調用 AsyncTask 的四個復寫方法:onPreExecute、onPostExecute肄满、onProgressUpdate谴古、doInBackground

  • (5)一個 AsyncTask 實例,只能調用一次,重復調用會報錯,也就是說撰糠,如果想再次調用 AsyncTask参萄,需要重新創(chuàng)建實例。

我們根據(jù)這幾個問題來進行源碼分析典蜕。首先來看 AsyncTask 的執(zhí)行過程,從 execute(Params... params) 方法開始。

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

接著調用 executeOnExecutor(sDefaultExecutor, params)勺疼,sDefaultExecutor 是 AsyncTask 默認的線程執(zhí)行器,一個進程中都會使用這個執(zhí)行器捏鱼。

@MainThread
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 運行狀態(tài)用一個 Status 的枚舉來表示执庐,有三種狀態(tài) PENDING --> RUNNING --> FINISHED。當創(chuàng)建 AsyncTask 后导梆,表示運行狀態(tài)的變量 mStatus 初始化為 Status.PENDING轨淌,執(zhí)行開始后為 RUNNING,執(zhí)行結束后更改為 FINISHED问潭,中間過程不能修改為之前的狀態(tài)猿诸。

一旦調用過 execute() 方法一次,狀態(tài)就會變成 RUNNING 或者 FINISHED狡忙,所以第二次調用后梳虽,就會拋出異常,也就上面的兩種異常灾茁,這也驗證了上述使用注意事項 (5),execute 方法只能調用一次窜觉。

線程開始執(zhí)行前 onPreExecute() 被調用谷炸,onPreExecute() 是用來在子線程執(zhí)行前可以做一些 UI 提示,如顯示進度提示框禀挫,所以 onPreExecute() 應該在主線程中被調用旬陡,也就是說 execute() 要在主線程中執(zhí)行,這也驗證了注意事項: (3) execute 方法必須在 UI 線程中被調用, 這里留一個疑問语婴,一定是必須要在 UI 線程么描孟,如果不在主線程,會出現(xiàn)什么情況砰左,有沒有什么方式可以解決匿醒?后面會對這個問題驗證。

接著是開始執(zhí)行子線程任務缠导,這里有兩個變量廉羔,mWorker 和 mFuture,其中 mWorker 是對 Callable 的一個封裝僻造,主要是為了添加參數(shù) mParams憋他,能夠在線程執(zhí)行時將參數(shù)傳遞進去,也就是 Params 類型的參數(shù)髓削,傳遞到 doInBackground(Params... params) 方法中竹挡。

mWorker 和 mFuture 是在 AsyncTask 構建時創(chuàng)建的,下面看一下線程執(zhí)行時通過這兩個完成了哪些任務蔬螟。

public AsyncTask(@Nullable Looper callbackLooper) {
        // Handler 創(chuàng)建
        mHandler = callbackLooper == null || callbackLooper == Looper.getMainLooper()
            ? getMainHandler()
            : new Handler(callbackLooper);

        // mWorker 創(chuàng)建
        mWorker = new WorkerRunnable<Params, Result>() {
            public Result call() throws Exception {
                // 表示 call() 方法已經(jīng)被調用
                mTaskInvoked.set(true);
                Result result = null;
                try {
                   // 設置線程優(yōu)先級此迅,設置為后臺線程 Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);
                    //noinspection unchecked
                    // 后臺執(zhí)行方法 doInBackground
                    result = doInBackground(mParams);
                    Binder.flushPendingCommands();
                } catch (Throwable tr) {
                    mCancelled.set(true);
                    throw tr;
                } finally {
                    // 返回結果給 UI 線程
                    postResult(result);
                }
                return result;
            }
        };
        // mFuture 創(chuàng)建
        mFuture = new FutureTask<Result>(mWorker) {
            @Override
            protected void done() {
                try {
                    // 如果 mWorker 的 call() 方法未執(zhí)行,會走這里
                    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 的 call 方法是線程中執(zhí)行的具體內(nèi)容旧巾,在執(zhí)行開始時耸序,會通過 mTaskInvoked.set(true);來表示 call 方法已經(jīng)被調用,指定的具體內(nèi)容是我們自己重寫的 doInBackground(mParams); 方法鲁猩,無論是否被取消坎怪,最終都會調用 postResult(result); 方法,將結果發(fā)送給 UI 線程廓握。

mFuture 對 mWorker 又進行包裝了一層搅窿,mFuture 同時實現(xiàn)了 Runnable 和 Future,它能夠在線程執(zhí)行完獲取結果隙券,同時也能夠知道線程是否被取消男应。這里對 mWorker 包裝的意義在于如果 mWorker 的 call 方法因為異常沒有被執(zhí)行時(即 mTaskInvoked.get() 為 false 時),也能夠通過 postResult(result); 通知到主線程娱仔。

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

到這里線程的執(zhí)行過程我們清楚了沐飘,還有兩個地方?jīng)]有串聯(lián)起來,一個是執(zhí)行器 exec.execute(mFuture);如何管理任務;一個是 執(zhí)行時和執(zhí)行后如何切換線程耐朴,通知主線程

先來看下線程執(zhí)行器借卧。

通過上面代碼我們知道執(zhí)行過程中使用的 AsyncTask 的默認執(zhí)行器 sDefaultExecutor,它是一個 static volatile 變量筛峭,通過指向 SerialExecutor 實例铐刘,意味著一個進程中加載 AsyncTask 后已經(jīng)初始化,所有的 AsyncTask 的任務都是通過它來執(zhí)行的影晓。

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

當 exec.execute(mFuture); 方法執(zhí)行時镰吵,SerialExecutor 會將任務 mFuture 添加到 mTasks 中,mTasks 是一個 ArrayDeque挂签,意味著任務的執(zhí)行是按照加入到隊列中的順序執(zhí)行的捡遍,也就是串行的。SerialExecutor 的作用首先是將任務加入到 mTasks 中竹握,然后找到一個可執(zhí)行的任務 mActive 交給 THREAD_POOL_EXECUTOR 來執(zhí)行,所以任務的真正執(zhí)行是通過 THREAD_POOL_EXECUTOR 這個線程池來完成的辆飘。執(zhí)行過程中就會調用 mWorker 的 call 方法啦辐,結束后通過 postResult(result); 將結果傳遞給主線程,另外蜈项,在 doInBackground 方法中芹关,通過調用 publishProgress 可以將進度傳遞給 UI 線程,其原理和 postResult(result); 方法是一樣的紧卒,都是通過其內(nèi)部的 InternalHandler 來完成的侥衬,這里我們就只看一下 postResult(result); 方法。

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

通過 Handler 創(chuàng)建一個 Message跑芳,Message 中含有 result 信息和標識信息 MESSAGE_POST_RESULT轴总,然后發(fā)送到消息隊列中,當處理消息時博个,會調用 Handler 的handleMessage 方法怀樟。

private static class InternalHandler extends Handler {
    public InternalHandler(Looper looper) {
        super(looper);
    }
    @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 方法中,根據(jù) msg.what 標識來執(zhí)行對應的方法盆佣,如 MESSAGE_POST_RESULT往堡,會執(zhí)行 result.mTask.finish(result.mData[0]); 方法,result.mTask 就是 AsyncTask 本身共耍,也就是會執(zhí)行 AsyncTask 的 finish 方法虑灰,最終會調用 onPostExecute(result); 也就是我們自己重寫的方法。

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

AsyncTask 的四個需要重寫的方法 onPreExecute()痹兜、onPostExecute(String s)穆咐、onProgressUpdate(Float... values)、doInBackground(String... strings) 在執(zhí)行過程中 AsyncTask 會自動在合適的時機調用佃蚜,我們不需要自己去調用庸娱,就像 Activity 的生命周期一樣着绊。

既然通過 InternalHandler 來切換到 UI 線程,那么 InternalHandler 的 構造中需要的 Looper 應該是主線程的 Looper熟尉,即 sMainLooper归露。那么 InternalHandler 是在什么時候構造的呢?在創(chuàng)建 AsyncTask 時斤儿。

public AsyncTask(@Nullable Looper callbackLooper) {
    mHandler = callbackLooper == null || callbackLooper == Looper.getMainLooper()
        ? getMainHandler()
        : new Handler(callbackLooper);
        
        ....
}

我們在創(chuàng)建 AsyncTask 時剧包,并沒有傳遞 Looper,所以 callbackLooper 為 null往果,所以會調用 getMainHandler() 方法疆液。

該方法為 AsyncTask.class 線程安全做了同步,保證一個進程中所有 AsyncTask 實例只有一個 Handler 實例陕贮,且只有應用在第一次實例化 AsyncTask 時才會創(chuàng)建 Handler 對象堕油。

創(chuàng)建 InternalHandler 傳入了主線程的 Looper 對象,即無論我們在哪個線程實例化 AsyncTask 實例肮之,AsyncTask 中的 Handler 都是用主線程的 Looper 來實例化的掉缺,所以也就會切換到了 UI 線程。

private static Handler getMainHandler() {
    synchronized (AsyncTask.class) {
        if (sHandler == null) {
            sHandler = new InternalHandler(Looper.getMainLooper());
        }
        return sHandler;
    }
}

通過上述分析戈擒,可以知道注意事項中 AsyncTask 類需要 UI 線程中加載AsyncTask 實例需要 UI 線程中創(chuàng)建 可以不按照這個要求來眶明。

/**
 * 子線程中開啟 AsyncTask
 */
private void startTaskInNewThread() {
    Thread asyncThread = new Thread(new Runnable() {
        @Override
        public void run() {
            mAsyncTask = MyAsyncTask.newTask();
            mAsyncTask.setCallBack(new MyAsyncTask.TaskCallBack() {
                @Override
                public void onPrepare() {
                    mDownLoadDialog.show();
                }

                @Override
                public void onUpdate(Float[] values) {
                    mDownLoadDialog.updateProgress(values[0]);
                }

                @Override
                public void onCancelled(String s) {
                    Toast.makeText(MainActivity.this, "已經(jīng)取消下載 - " + s, Toast.LENGTH_SHORT).show();
                }

                @Override
                public void onFinished(String s) {
                    if (mDownLoadDialog.isShowing()) {
                        mDownLoadDialog.dismiss();
                    }
                    Toast.makeText(MainActivity.this, "下載完成 - " + s, Toast.LENGTH_SHORT).show();
                }
            });
        }
    });
    asyncThread.start();
    // 等待mAsyncTask創(chuàng)建完畢,否則 mAsyncTask.execute("開始下載"); 無法執(zhí)行筐高,空指針
    try {
        asyncThread.join();
    } catch (InterruptedException e) {
        e.printStackTrace();
    }
    mAsyncTask.execute("開始下載");
}

這樣創(chuàng)建 AsyncTask 一樣可以正常執(zhí)行搜囱,為什么會這樣的? 我這里分析的 AsyncTask 的源碼是基于 android-28 中的 AsyncTask,在 android 26 起 AsyncTask 就已經(jīng)能夠保證即使不在主線程加載 AsyncTask 和創(chuàng)建 AsyncTask 一樣可以正常運行柑土。通過上述代碼可以知道蜀肘, AsyncTask 中的 InternalHandler 也能夠獲取主線程的 Looper,自然也就可以切換到 UI 線程冰单。但是在 android 26 之前幌缝,InternalHandler 在 AsyncTask 加載時就已經(jīng)創(chuàng)建,而且沒有傳入 Looper,這時候會獲取當前線程的 Looper诫欠,如果子線程沒有調用 Looper.prepare()涵卵,就會報錯,而且即使子線程創(chuàng)建了 Looper荒叼,也不能切換到 UI 線程轿偎,因為不是主線程的 Looper。

private static final InternalHandler sHandler = new InternalHandler();

private static class InternalHandler extends Handler {  
    @SuppressWarnings({unchecked, RawUseOfParameterizedType})  
    @Override  
    public void handleMessage(Message msg) {  
        ...
    }  
}

上面我們驗證 AsyncTask 類可以不在 UI 線程中加載被廓,實例也不一定要在 UI 線程中創(chuàng)建坏晦,但是 execute 方法最好還是在 UI 線程中調用,如果不在主線程,會出現(xiàn)什么情況昆婿,有沒有什么方式可以解決球碉?

下面就是在一個子線程中創(chuàng)建 AsyncTask,在子線程中執(zhí)行 execute 方法

    private void startNewTask1() {
        new Thread(new Runnable() {
            @Override
            public void run() {
//                Looper.prepare();
                mAsyncTask = MyAsyncTask.newTask();
                mAsyncTask.setCallBack(new MyAsyncTask.TaskCallBack() {
                    @Override
                    public void onPrepare() {
                        // 子線程執(zhí)行會報錯
//                        mDownLoadDialog.show();
                        // mDownLoadDialog.show(); 換成 button.setText("123");
//                        button.setText("123");
                        // 切換到 UI 線程
                        runOnUiThread(new Runnable() {
                            @Override
                            public void run() {
                                mDownLoadDialog.show();
                            }
                        });
                    }

                    @Override
                    public void onUpdate(final Float[] values) {
                        runOnUiThread(new Runnable() {
                            @Override
                            public void run() {
                                mDownLoadDialog.updateProgress(values[0]);
                            }
                        });
                    }

                    @Override
                    public void onCancelled(String s) {
                        Toast.makeText(MainActivity.this, "已經(jīng)取消下載 - " + s, Toast.LENGTH_SHORT).show();
                    }

                    @Override
                    public void onFinished(String s) {
                        runOnUiThread(new Runnable() {
                            @Override
                            public void run() {
                                if (mDownLoadDialog.isShowing()) {
                                    mDownLoadDialog.dismiss();
                                }
                            }
                        });
                        Toast.makeText(MainActivity.this, "下載完成 - " + s, Toast.LENGTH_SHORT).show();
                    }
                });
                mAsyncTask.execute("開始下載");
//                Looper.loop();
            }
        }).start();
    }

由于例子中使用的 Dialog 顯示進度仓蛆,Dialog 的顯示過程需要 ViewRootImpl 中初始化 ViewRootHandler睁冬,需要獲取一個 Looper,但是在子線程中沒有 調用 Looper.prepare() 和 Looper.loop(),自然沒有 Looper看疙,所以會報錯豆拨。

java.lang.RuntimeException: Can't create handler inside thread that has not called Looper.prepare()
        at android.os.Handler.<init>(Handler.java:204)
        at android.os.Handler.<init>(Handler.java:118)
        at android.view.ViewRootImpl$ViewRootHandler.<init>(ViewRootImpl.java:3665)
        at android.view.ViewRootImpl.<init>(ViewRootImpl.java:3998)
        at android.view.WindowManagerGlobal.addView(WindowManagerGlobal.java:346)
        at android.view.WindowManagerImpl.addView(WindowManagerImpl.java:93)
        at android.app.Dialog.show(Dialog.java:330)

在注釋中,給出另外一個更加清晰辨別的方法能庆,更新 Button 的文字施禾,也是需要在主線程中執(zhí)行,所以會報下面的錯誤搁胆,因為不是在主線程中更新 UI弥搞。對上面 Dialog 的顯示,如果在 run 方法中加上 Looper.prepare() 和 Looper.loop()渠旁,同樣會報下面的錯誤拓巧。

 android.view.ViewRootImpl$CalledFromWrongThreadException: Only the original thread that created a view hierarchy can touch its views.

所以不在 UI 線程中調用 execute 方法,更新 UI 操作會報錯一死,那么有沒有什么解決方法呢?方法就是將更新 UI 的操作切換到主線程即可傻唾⊥洞龋可以采用 runOnUiThread 方法。

上面一系列操作看似有點非常規(guī)冠骄,但是對于我們理解 AsyncTask 的使用很有幫助伪煤,正常情況下,我們按照 AsyncTask 使用注意事項中的建議使用凛辣,不會引出一些不必要的麻煩抱既。

總結

AsyncTask 是利用線程池和 Handler 來完操作,線程池在后臺子線程執(zhí)行耗時任務扁誓,更新進度和結果返回時防泵,通過 Handler 來進行更新 UI 操作,需要理解兩個任務變量 mWorker 和 mFuture,以及兩個執(zhí)行器 sDefaultExecutor 和 THREAD_POOL_EXECUTOR 各自的作用蝗敢。此外捷泞,AsyncTask 的 execute 方法是串行執(zhí)行的,如果想并行寿谴,可以使用 executeOnExecutor 方法锁右,使用自定義的執(zhí)行器或者直接使用 AsyncTask 中的 THREAD_POOL_EXECUTOR,能夠達到并行處理的目的。

代碼地址

AsyncTask 練習

參考

程序媛說源碼:AsyncTask在子線程創(chuàng)建與調用的那些事兒

?著作權歸作者所有,轉載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末咏瑟,一起剝皮案震驚了整個濱河市拂到,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌码泞,老刑警劉巖兄旬,帶你破解...
    沈念sama閱讀 222,627評論 6 517
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異浦夷,居然都是意外死亡辖试,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 95,180評論 3 399
  • 文/潘曉璐 我一進店門劈狐,熙熙樓的掌柜王于貴愁眉苦臉地迎上來罐孝,“玉大人,你說我怎么就攤上這事肥缔×ぃ” “怎么了?”我有些...
    開封第一講書人閱讀 169,346評論 0 362
  • 文/不壞的土叔 我叫張陵续膳,是天一觀的道長改艇。 經(jīng)常有香客問我,道長坟岔,這世上最難降的妖魔是什么谒兄? 我笑而不...
    開封第一講書人閱讀 60,097評論 1 300
  • 正文 為了忘掉前任,我火速辦了婚禮社付,結果婚禮上承疲,老公的妹妹穿的比我還像新娘。我一直安慰自己鸥咖,他們只是感情好燕鸽,可當我...
    茶點故事閱讀 69,100評論 6 398
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著啼辣,像睡著了一般啊研。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上鸥拧,一...
    開封第一講書人閱讀 52,696評論 1 312
  • 那天党远,我揣著相機與錄音,去河邊找鬼富弦。 笑死麸锉,一個胖子當著我的面吹牛,可吹牛的內(nèi)容都是我干的舆声。 我是一名探鬼主播花沉,決...
    沈念sama閱讀 41,165評論 3 422
  • 文/蒼蘭香墨 我猛地睜開眼柳爽,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了碱屁?” 一聲冷哼從身側響起磷脯,我...
    開封第一講書人閱讀 40,108評論 0 277
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎娩脾,沒想到半個月后赵誓,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 46,646評論 1 319
  • 正文 獨居荒郊野嶺守林人離奇死亡柿赊,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 38,709評論 3 342
  • 正文 我和宋清朗相戀三年俩功,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片碰声。...
    茶點故事閱讀 40,861評論 1 353
  • 序言:一個原本活蹦亂跳的男人離奇死亡诡蜓,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出胰挑,到底是詐尸還是另有隱情蔓罚,我是刑警寧澤,帶...
    沈念sama閱讀 36,527評論 5 351
  • 正文 年R本政府宣布瞻颂,位于F島的核電站豺谈,受9級特大地震影響,放射性物質發(fā)生泄漏贡这。R本人自食惡果不足惜茬末,卻給世界環(huán)境...
    茶點故事閱讀 42,196評論 3 336
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望盖矫。 院中可真熱鬧团南,春花似錦、人聲如沸炼彪。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,698評論 0 25
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽辐马。三九已至,卻和暖如春局义,著一層夾襖步出監(jiān)牢的瞬間喜爷,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,804評論 1 274
  • 我被黑心中介騙來泰國打工萄唇, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留檩帐,地道東北人。 一個月前我還...
    沈念sama閱讀 49,287評論 3 379
  • 正文 我出身青樓另萤,卻偏偏與公主長得像湃密,于是被迫代替她去往敵國和親诅挑。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 45,860評論 2 361

推薦閱讀更多精彩內(nèi)容

  • 使用AsyncTask的一般步驟是: 定義一個類繼承自AsyncTask泛源,實現(xiàn)抽象方法 new 一個AsyncTa...
    yk_looper閱讀 391評論 0 2
  • 簡介:AsyncTask是Android中用于異步操作的類拔妥。盡管現(xiàn)在已經(jīng)逐漸被強調舍棄,不過就源碼來說,Async...
    非墨Zero閱讀 260評論 1 0
  • ### AsyncTask源碼分析及個人理解 #### PS.閱讀源碼是開發(fā)者必備的能力,既然是能力那么就可以不斷...
    大寶京湘玉閱讀 267評論 0 0
  • 源碼基于api23AsyncTask是對Handler和Thread的封裝類达箍。來完成異步任務没龙,使用線程池執(zhí)行后臺任...
    wang_zd閱讀 262評論 0 0
  • 親愛的孩子: 試著走過去 勇敢的走過去。 放下期待缎玫。 只為迎接硬纤。 你是安全的。 因為你的身后赃磨。 有他們筝家。
    釋然心理閱讀 277評論 0 0