Android開發----AsyncTask的使用以及源碼解析

01AsyncTask使用介紹


AsyncTask封裝了Thread和Handler,通過AsyncTask可以很方便地在執行完後臺任務後更新UI。。

 

1.1  AsyncTask實例使用


下面是一個使用AsyncTask的實例,利用網絡下載某URL裏的字符串,以模擬耗時任務。在下載過程中,會通過進度條對話框向用戶展示進度。在完成任務後將字符串展示在TextView上。具體實現細節後面會加以講述,順便引出AsyncTask的知識。


public class MainActivity extends Activity{  

    private TextView show;  

    @Override  

    public void onCreate(Bundle savedInstanceState){  

        super.onCreate(savedInstanceState);  

        setContentView(R.layout.main);  

        show = (TextView) findViewById(R.id.show);  

    }  

    //按鈕事件響應方法  URL可自定義  

    public void download(View source) throws Exception{  

        DownTask task = new DownTask(this);  

        task.execute(new URL(URL));  

    }  

    class DownTask extends AsyncTask<URL, Integer, String>{ //自定義Task類繼承AsyncTask  

        ProgressDialog pdialog;  

        int hasRead = 0;  

        Context mContext;  

        public DownTask(Context ctx){  

            mContext = ctx;  

        }  

        @Override  

        protected String doInBackground(URL... params){  //doInBackground方法在子線程執行耗時任務  

            StringBuilder sb = new StringBuilder();  

            try{  

                URLConnection conn = params[0].openConnection();  

                BufferedReader br = new BufferedReader(  

                    new InputStreamReader(conn.getInputStream(), "utf-8"));  

                String line = null;  

                while ((line = br.readLine()) != null){  

                    sb.append(line + "\n");  

                    hasRead++;  

                    publishProgress(hasRead);  

                }  

                return sb.toString();  

            }  

            catch (Exception e){  

                e.printStackTrace();  

            }  

            return null;  

        }  

        @Override  

        protected void onPostExecute(String result){ //主線程執行  

            // 展示下載下來的字符串 並將進度條對話框dismiss  

            show.setText(result);  

            pdialog.dismiss();  

        }  

        @Override  

        protected void onPreExecute(){ //主線程執行  

            pdialog = new ProgressDialog(mContext);  

            pdialog.setTitle("任務正在執行中");  

            pdialog.setMessage("請等待...");  

            // 設置對話框不能用“取消”按鈕關閉  

            pdialog.setCancelable(false);  

            pdialog.setMax(MAX);  

            // 設置對話框的進度條風格  

            pdialog.setProgressStyle(ProgressDialog.STYLE_HORIZONTAL);  

            // 設置對話框的進度條是否顯示進度  

            pdialog.setIndeterminate(false);  

            pdialog.show();  

        }  

        @Override  

        protected void onProgressUpdate(Integer... values){ //主線程執行  

            // 更新進度  

            show.setText("已經讀取了" + values[0] + "行");  

            pdialog.setProgress(values[0]);  

        }  

    }  

}  


1.2  AsyncTask參數介紹


看了上面的例子,我們會解釋例子中涉及到的AsyncTask知識,首先是參數介紹:


AsyncTask定義了三種泛型類型 Params,Progress和Result。也是可以指定爲空的,如 AsyncTask<Void, Void, Void>

Params:啓動任務執行的輸入參數的類型,本例中爲URL。

Progress:後臺任務執行的進度值類型,這裏爲Integer。

Result:後臺執行任務最終返回的結果類型,這裏爲字符串String。


1.3  AsyncTask回調方法介紹


下面是介紹使用AsyncTask需要了解的方法:


onPreExecute():運行在主線程。調用excute()方法時執行,當任務執行之前調用此方法。通常用來完成一些初始化的準備工作。本例中是顯示一個進度條。

 

doInBackground(Params…):運行在子線程。執行比較耗時的操作。在執行過程中可以調用publishProgress(Progress…values)來更新任務的進度。


doInBackground的參數對應AsyncTask的第一個參數,publishProgress(Progress…)的參數對應AsyncTask的第二個參數,其返回值對應AsyncTask的第三個參數。

 

onPostExecute(Result result) :運行在主線程。相當於Handler 處理UI的方式, doInBackground 執行完畢後回調該方法,參數爲doInBackground的返回值。

 

onProgressUpdate(Progress…)  :運行在主線程。用於顯示任務執行的進度,在doInBackground方法中調用publishProgress更新進度時會回調此方法。

 

onCancelled() :運行在主線程。用戶調用了cancel(true)取消操作。


task.cancle(true);   


1.4 使用AsyncTask的注意事項


(1)AsyncTask必須在主線程中創建,execute方法也必須在UI線程調用,原因後面分析源碼自然會明白。

(2)不要手動的調用onPreExecute(), onPostExecute(Result),doInBackground(Params...),onProgressUpdate(Progress...)這幾個方法。

(3)task只能被執行一次,多次調用時將會出現異常。

(4)Android1.6之前,AsyncTask是串行執行任務的,1.6之後採用線程池處理並行任務,又從3.0開始爲了避免併發錯誤,又採用了一個線程來串行執行任務。


因此在3.0以後就不可以用AsyncTask並行執行任務了嗎,當然不是,我們仍然可以通過executeOnExecutor方法執行並行任務。


下面的源碼解析基於Android4.0版本。


02AsyncTask源碼解析


AsyncTask在Android中是如何實現的,下面進行源碼分析。


2.1 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() {      

            ...    

        }      

    };      

}    


構造函數中創建了WorkerRunnable和FutureTask兩個實例,並把mWorker傳遞給了mFuture。讓我們看一下WorkerRunnable類。


2.2 WorkerRunnable抽象類


private static abstract class WorkerRunnable<Params, Result> implements Callable<Result> {    

        Params[] mParams;    

}    


WorkerRunnable是Callable的子類,且包含一個mParams用於保存我們傳入的參數。在AsyncTask中構造方法中完成了初始化,並且因爲是一個抽象類,在這裏new了一個實現類,並且實現了call方法,call方法中設置mTaskInvoked=true,且最終調用doInBackground(mParams)方法,並返回Result值作爲參數給postResult方法。


2.3 execute方法執行


我們在使用AsyncTask執行一個任務時,會調用execute方法,那讓我們看一下這個方法。


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) {  //switch意味着不能重複執行execute方法  

                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;    

    }    


可以看出onPreExecute()是首先被執行的,然後將參數通過mWorker封裝爲FutureTask對象。接着調用了exec.execute(),從上面的代碼中我們看到exec就是sDefaultExecutor,繼續研究sDefaultExecutor。


2.4 實際上執行的是SerialExecutor的execute()方法


public static final Executor SERIAL_EXECUTOR = new SerialExecutor();      

……      

private static volatile Executor sDefaultExecutor = SERIAL_EXECUTOR;   

  

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()方法。上面也分析過了,我們的參數被封裝爲了FurtherTask對象,並在這裏充當了Runnable的作用。


execute首先將FurtherTask對象插入到任務隊列mTasks中。


第一個任務入隊,調用offer方法將傳入的Runnable對象添加到隊列尾部。判空後進入scheduleNext方法。然後在mActive =mTasks.poll()) != null被取出,從隊列的頭部取值,並且賦值給mActivte,然後交給線程池THREAD_POOL_EXECUTOR去執行(而SerialExecutor用於任務的排隊)。


然後第二個任務入隊,繼續入列,但是此時mActive並不爲null,並不會執行scheduleNext()。所以如果第一個任務比較慢,很多個任務都會進入隊列等待。


真正執行下一個任務的時機是,線程池執行完成第一個任務以後,調用Runnable中的finally代碼塊中的scheduleNext。


這樣就形成了一種鏈狀調用結構,只要mTasks裏面還有任務,就會不斷逐一調用,如果後面有任務進來,就只要添加到mTasks裏面即可。


這裏給execute()傳遞的參數是mFuture,所以會執行到mFuture的run()方法,而run()方法最終會調用callable.call(),而callable就是mWorker,便回到了我們在2.1中看到的代碼。


 public Result call() throws Exception {      

            mTaskInvoked.set(true);      

            Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);      

            return postResult(doInBackground(mParams));      

}      


doInBackground()方法將它的返回值傳給了postResult。繼續查看postResult的實現。


2.4 postResult的實現


private Result postResult(Result result) {      

    Message message = sHandler.obtainMessage(MESSAGE_POST_RESULT,      

            new AsyncTaskResult<Result>(this, result));      

    message.sendToTarget();      

    return result;      

}    


原來是使用了Handler機制發送消息,那我們看下處理消息的地方。不懂Handler機制的可以查看之前寫過的一篇文章Android消息機制詳解。


2.5 消息處理


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:                       

                result.mTask.finish(result.mData[0]);      

                break;      

            case MESSAGE_POST_PROGRESS:      

                result.mTask.onProgressUpdate(result.mData);      

                break;      

        }      

    }      

}    


收到MESSAGE_POST_RESULT就執行finish(),繼而查看finish的實現。


2.6 MESSAGE_POST_RESULT消息的詳細處理


private void finish(Result result) {      

    if (isCancelled()) {      

        onCancelled(result);      

    } else {      

        onPostExecute(result);      

    }      

    mStatus = Status.FINISHED;      

}    


可以看到,如果當前任務被取消掉了,就會調用onCancelled()方法,如果沒有被取消,則調用onPostExecute()方法,這樣當前任務的執行就全部結束了。


我們注意到還有一種MESSAGE_POST_PROGRESS的消息類型,這種消息是用於當前進度的,調用的正是onProgressUpdate()方法,那很自然想到的是publishProgress()方法。查看該方法源碼。


2.7 MESSAGE_POST_PROGRESS消息的發出


protected final void publishProgress(Progress... values) {      

    if (!isCancelled()) {      

        sHandler.obtainMessage(MESSAGE_POST_PROGRESS,      

                new AsyncTaskResult<Progress>(this, values)).sendToTarget();      

    }      

}    


可以看出AsyncTask也是使用的異步消息處理機制,只是做了非常好的封裝而已。


所以在doInBackground()方法中調用publishProgress()方法纔可以從子線程切換到UI線程,從而完成對UI元素的更新操作。


03Android 3.0以前(1.6以後)


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


3.0以前規定同一時刻能夠運行的線程數爲5個,線程池總大小爲128,排隊等待數量10個。也就是說當我們啓動了10個任務時,只有5個任務能夠立刻執行,另外的5個任務則需要等待,當有一個任務執行完畢後,第6個任務纔會啓動,以此類推。而線程池中最大能存放的線程數是128個,當我們嘗試去添加第129個任務時,程序就會崩潰,發出Java.util.concurrent.RejectedExecutionException異常。


上面通過源碼也分析過,3.0之後的AsyncTask同時只能有1個任務在執行。如果不想使用默認的線程池,還可以自由地進行配置。比如使用如下的代碼,不是使用SerialExecutor,允許在同一時刻有12個任務正在執行,並且最多能夠存儲100個任務。


Executor exec = new ThreadPoolExecutor(12, 100, 10,    

        TimeUnit.SECONDS, new LinkedBlockingQueue<Runnable>());    

new DownloadTask().executeOnExecutor(exec);    


到此關於AsyncTask的知識總結完畢。

發佈了74 篇原創文章 · 獲贊 29 · 訪問量 15萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章