AsyncTask的基本使用及工作原理

public abstract class AsyncTask< Params, Progress, Result > 對於android中的處理異步線程的機制, 能夠更加簡單的處理異步任務. 如下圖是該抽象類的大綱
這裏寫圖片描述

其中,doInBackground(Params… params)是一個抽象方法,我們繼承AsyncTask時必須覆寫此方法;onPreExecute()、onProgressUpdate(Progress… values)、onPostExecute(Result result)、onCancelled()這幾個方法體都是空的,我們需要的時候可以選擇性的覆寫它們;execute((Params… params)是觸發一個實例task任務時調用的;publishProgress(Progress… values)是final修飾的,不能覆寫,只能去調用,我們一般會在doInBackground(Params… params)中調用此方法;另外,我們可以看到有一個Status的枚舉類和getStatus()方法,Status枚舉類中記錄了該AsyncTask任務的三種狀態, AsyncTask的初始狀態爲PENDING,代表待定狀態,RUNNING代表執行狀態,FINISHED代表結束狀態,我們可以通過這幾種狀態,對AsyncTask進行啓動,終止等操作。


第二部分 介紹其基本使用
以打開百度首頁爲例子,介紹task的使用步驟:
第一步: 繼承抽象類AsyncTask構建子類,必須覆寫doInBackground(String… params) 方法,其他方法根據需要調用或者覆寫,在doInBackground(String… params)方法中,觸發異步請求,也就是請求 http://www.baidu.com , 並通過覆寫onPreExecute(), 將異步請求執行之前的UI界面進行處理.

    private class MyTask extends AsyncTask<String, Integer, String> {  
        //onPreExecute方法用於在執行後臺任務前做一些UI操作  
        @Override  
        protected void onPreExecute() {  
            Log.i(TAG, "onPreExecute() called");  
            textView.setText("loading...");  
        }  

        //doInBackground方法內部執行後臺任務,不可在此方法內修改UI  
        @Override  
        protected String doInBackground(String... params) {  
            Log.i(TAG, "doInBackground(Params... params) called");  
            try {  
                HttpClient client = new DefaultHttpClient();  
                HttpGet get = new HttpGet(params[0]);  
                HttpResponse response = client.execute(get);  
                if (response.getStatusLine().getStatusCode() == HttpStatus.SC_OK) {  
                    HttpEntity entity = response.getEntity();  
                    InputStream is = entity.getContent();  
                    long total = entity.getContentLength();  
                    ByteArrayOutputStream baos = new ByteArrayOutputStream();  
                    byte[] buf = new byte[1024];  
                    int count = 0;  
                    int length = -1;  
                    while ((length = is.read(buf)) != -1) {  
                        baos.write(buf, 0, length);  
                        count += length;  
                        //調用publishProgress公佈進度,最後onProgressUpdate方法將被執行  
                        publishProgress((int) ((count / (float) total) * 100));  
                        //爲了演示進度,休眠500毫秒  
                        Thread.sleep(500);  
                    }  
                    return new String(baos.toByteArray(), "gb2312");  
                }  
            } catch (Exception e) {  
                Log.e(TAG, e.getMessage());  
            }  
            return null;  
        }  

        //onProgressUpdate方法用於更新進度信息  
        @Override  
        protected void onProgressUpdate(Integer... progresses) {  
            Log.i(TAG, "onProgressUpdate(Progress... progresses) called");  
            progressBar.setProgress(progresses[0]);  
            textView.setText("loading..." + progresses[0] + "%");  
        }  

        //onPostExecute方法用於在執行完後臺任務後更新UI,顯示結果  
        @Override  
        protected void onPostExecute(String result) {  
            Log.i(TAG, "onPostExecute(Result result) called");  
            textView.setText(result);  
        }  

        //onCancelled方法用於在取消執行中的任務時更改UI  
        @Override  
        protected void onCancelled() {  
            Log.i(TAG, "onCancelled() called");  
            textView.setText("cancelled");  
            progressBar.setProgress(0);   
        }  
    }  
}   

第二步:在UI線程中創建task實例 並觸發
mTask = new MyTask();
mTask.execute(“http://www.baidu.com“);

public class MainActivity extends Activity {  
private static final String TAG = "ASYNC_TASK";  

private Button execute;  
private Button cancel;  
private ProgressBar progressBar;  
private TextView textView;  

private MyTask mTask;  

@Override  
public void onCreate(Bundle savedInstanceState) {  
    super.onCreate(savedInstanceState);  
    setContentView(R.layout.main);  
    execute = (Button) findViewById(R.id.execute);
    cancel = (Button) findViewById(R.id.cancel); 
    progressBar = (ProgressBar) findViewById(R.id.progress_bar);  
    textView = (TextView) findViewById(R.id.text_view);   
    execute.setOnClickListener(new View.OnClickListener() {  
        @Override  
        public void onClick(View v) {  
            //注意每次需new一個實例,新建的任務只能執行一次,否則會出現異常  
            mTask = new MyTask();  
            mTask.execute("http://www.baidu.com");   
        }  
    });  
    cancel.setOnClickListener(new View.OnClickListener() {  
        @Override  
        public void onClick(View v) {  
            //取消一個正在執行的任務,onCancelled方法將會被調用 ,該方法只是將對應的task標記爲cancel狀態 並不是真正的取消線程的執行,線程在後臺仍然繼續執行 另 java中不能直接見到粗暴的結束某一個線程
            mTask.cancel(true);  
        }  
    });  
}     

第三步: 與UI通信 通過onPostExecute(String result) 方法將異步請求的結果傳遞給UI線程,更新UI界面.

 //onProgressUpdate方法用於更新進度信息  
        @Override  
        protected void onProgressUpdate(Integer... progresses) {  
            Log.i(TAG, "onProgressUpdate(Progress... progresses) called");  
            progressBar.setProgress(progresses[0]);  
            textView.setText("loading..." + progresses[0] + "%");  
        }  

        //onPostExecute方法用於在執行完後臺任務後更新UI,顯示結果  
        @Override  
        protected void onPostExecute(String result) {  
            Log.i(TAG, "onPostExecute(Result result) called");  
            textView.setText(result);  
        }  

以該例子對該抽象類public abstract class AsyncTask< Params, Progress, Result > 的三種泛型參數做如下介紹:
Params 是“啓動任務執行的輸入參數” 觸發異步任務執行的時候會調用execute(Params… params),去執行一個異步任務, 該方法將參數傳遞給後臺真正異步線程,onPreExecute(),在execute(Params… params)被調用後立即執行,一般用來在執行後臺任務前對UI做一些標記。
Progress 是“後臺任務執行的進度”,在進行doInBackground的過程中,可以通過調用publishProgress(Progress… values)將異步執行的進度告訴UI線程,UI線程通過onProgressUpdate(Progress… values)將後臺執行的進度公佈在UI界面中
Result 是”後臺計算結果的類型”當doInBackground執行完畢或者出現異常時,會通過onPostExecute(Result result)方法對加載的結果傳遞給UI線程,並更新UI界面.

例子中傳入的url = “http://www.baidu.com“是String類型的,並使用(int) ((count / (float) total) * 100)來表示加載的進度,所以是Integer類型,最後將獲取的結果以new String(baos.toByteArray(), “gb2312”)類型傳遞給UI界面的,
因此例子中的MyTask是以String爲請求參數 以Integer爲進度值,以String爲結果的異步任務去執行的. 所以創建實力的時候是 private class MyTask extends AsyncTask< String, Integer, String >
在特定場合下,並不是所有類型都被使用,如果沒有被使用,可以用java.lang.Void類型代替。

總結:

一個異步任務的執行一般包括以下幾個步驟:
1.execute(Params… params),在主線程通過調用task的該方法執行一個異步任務
2.onPreExecute(),在execute(Params… params)被調用後立即執行,在執行後臺任務前對UI做一些標記, 或者界面的改變。
3.doInBackground(Params… params),在onPreExecute()完成後立即執行,用於執行較爲費時的操作,此方法將接收輸入參數和返回計算結果。在執行過程中可以調用publishProgress(Progress… values)來更新進度信息。
4.onProgressUpdate(Progress… values),在調用publishProgress(Progress… values)時,此方法被執行,直接將進度信息更新到UI組件上。
5.onPostExecute(Result result),當後臺操作結束時,此方法將會被調用,計算結果將做爲參數傳遞到此方法中,直接將結果顯示到UI組件上。

在使用的時候,有幾點需要格外注意:
1.異步任務的實例必須在UI線程中創建。
2.execute(Params… params)方法必須在UI線程中調用。
3.onPreExecute(),doInBackground(Params… params),onProgressUpdate(Progress… values),onPostExecute(Result result)這幾個方法。都是系統調用的,不能手動調用,只要執行execute即可觸發一個異步任務的執行.
4.不能在doInBackground(Params… params)中更改UI組件的信息, 該方法實在其他線程中執行的,onPreExecute(),onProgressUpdate(Progress… values),onPostExecute(Result result)這幾個方法承接了異步線程處理的結果,都是運行在UI線程中,可直接更新UI界面.
5.一個任務實例只能執行一次,如果執行第二次將會拋出RuntimeException異常,也就是例子中mTask.execute(“http://www.baidu.com“) 只能執行一次.


第三部分 介紹其工作原理

從第二部分可知,一個異步任務的觸發是由execute開始的,從這裏作爲入口看看AsyncTask真正的工作流程

public final AsyncTask<Params, Progress, Result> execute(Params... params) {  
    if (mStatus != Status.PENDING) {  
        switch (mStatus) {  
            case RUNNING:  
                //如果該任務正在被執行則拋出異常  
                //值得一提的是,在調用cancel取消任務後,狀態仍未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)");  
        }  
    }  
    //改變狀態爲RUNNING  
    mStatus = Status.RUNNING;  
    //調用onPreExecute方法  
    onPreExecute();  
    mWorker.mParams = params;  
    sExecutor.execute(mFuture);  
    return this;  
}

從execute的代碼段中,可知,對Status.PENDING狀態的task在會首先調用onPreExecute()方法,來標記UI界面的狀態,之後將params傳遞給mWorker,並執行sExecutor.execute(mFuture);
其中 sExecutor是java.util.concurrent.ThreadPoolExecutor的實例,用於管理線程的執行, 也就是在線程池中創建一個新的線程,用於執行後臺任務.

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 BlockingQueue<Runnable> sWorkQueue =  
        new LinkedBlockingQueue<Runnable>(10);  
//新建一個線程工廠  
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 ThreadPoolExecutor sExecutor = new ThreadPoolExecutor(CORE_POOL_SIZE,  
        MAXIMUM_POOL_SIZE, KEEP_ALIVE, TimeUnit.SECONDS, sWorkQueue, sThreadFactory); 

mWorker實際上是AsyncTask的一個的抽象內部類,且實現對象Callable的實例,它實現了Callable< Result >接口中的call()方法,
mFuture實際上是java.util.concurrent.FutureTask的實例,下面是它的FutureTask類的相關信息:

public class FutureTask<V> implements RunnableFuture<V> {  
public interface RunnableFuture<V> extends Runnable, Future<V> {  
    /** 
     * Sets this Future to the result of its computation 
     * unless it has been cancelled. 
     */  
    void run();  
} 

所以從代碼中可以看到 其實mFuture是Runnable的子類


mWorker和mFuture在AsyncTask中的體現如下, 在執行onPreExecute()方法,標記了主線程之後,將參數傳遞給mWorker,再通過ThreadPoolExecutor的實例sExecutor觸發了以mWorker爲參數的mFuture方法,mFuture實例中會調用mWorker做後臺任務,而覆寫call方法的mWorker實例是將該任務設置爲後臺級別,並調用doInBackground方法開始執行後臺任務,後臺任務完成後系統會調用done方法,將後臺結果發送出去.

private final WorkerRunnable<Params, Result> mWorker;  
private final FutureTask<Result> mFuture;  

public AsyncTask() {  
    mWorker = new WorkerRunnable<Params, Result>() {  
        //call方法被調用後,將設置優先級爲後臺級別,然後調用AsyncTask的doInBackground方法  
        public Result call() throws Exception {  
            Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);  
            return doInBackground(mParams);  
        }  
    };  

    //在mFuture實例中,將會調用mWorker做後臺任務,完成後會調用done方法  
    mFuture = new FutureTask<Result>(mWorker) {  
        @Override  
        protected void done() {  
            Message message;  
            Result result = null;  

            try {  
                result = 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) {  
                //發送取消任務的消息  
                message = sHandler.obtainMessage(MESSAGE_POST_CANCEL,  
                        new AsyncTaskResult<Result>(AsyncTask.this, (Result[]) null));  
                message.sendToTarget();  
                return;  
            } catch (Throwable t) {  
                throw new RuntimeException("An error occured while executing "  
                        + "doInBackground()", t);  
            }  

            //發送顯示結果的消息  
            message = sHandler.obtainMessage(MESSAGE_POST_RESULT,  
                    new AsyncTaskResult<Result>(AsyncTask.this, result));  
            message.sendToTarget();  
        }  
    };  
}

mFuture實例對象的done()方法中,通過不通的情況會發送不同的由AsyncTaskResult< Result>()構建的消息,將不同的結果發送出去.如果捕捉到了CancellationException類型的異常,則發送一條“MESSAGE_POST_CANCEL”的消息;如果順利執行,則發送一條“MESSAGE_POST_RESULT”的消息,而消息都與一個sHandler對象關聯。
這個sHandler實例實際上是AsyncTask內部類InternalHandler的實例,而InternalHandler正是繼承了Handler

private static final int MESSAGE_POST_RESULT = 0x1; //顯示結果  
private static final int MESSAGE_POST_PROGRESS = 0x2;   //更新進度  
private static final int MESSAGE_POST_CANCEL = 0x3; //取消任務  

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  
                //調用AsyncTask.finish方法  
                result.mTask.finish(result.mData[0]);  
                break;  
            case MESSAGE_POST_PROGRESS:  
                //調用AsyncTask.onProgressUpdate方法  
                result.mTask.onProgressUpdate(result.mData);  
                break;  
            case MESSAGE_POST_CANCEL:  
                //調用AsyncTask.onCancelled方法  
                result.mTask.onCancelled();  
                break;  
        }  
    }  
}

由sHandler的代碼可知,在sHandler實例對象的handleMessage(Message msg)方法裏,使用AsyncTaskResult result = (AsyncTaskResult) msg.obj方式取得消息中附帶的AsyncTaskResult消息
而AsyncTaskResult其實也是AsyncTask的一個內部類,是用來包裝執行結果的一個類,讓我們來看一下它的代碼結構:

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

可知這個AsyncTaskResult封裝了一個AsyncTask的實例和某種類型的數據集.所以在發送消息時是將AsyncTask實例和後臺結果result發送出去.

sHandler在處理消息時是如何使用這個task對象和數據集呢? 舉例如下
result.mTask.finish(result.mData[0]);
result.mTask.onProgressUpdate(result.mData);

分析一:當InternalHandler類型的實例sHandler收到MESSAGE_POST_RESULT類型的AsyncTaskResult消息時,是通過傳進來的AsyncTask.this來調用它自己的finish方法,代碼如下,從finish方法中可以看到,其實是調動AsyncTask的onPostExecute(result)方法 顯示結果.

private void finish(Result result) {  
    if (isCancelled()) result = null;  
    onPostExecute(result);  //調用onPostExecute顯示結果  
    mStatus = Status.FINISHED;  //改變狀態爲FINISHED  
}

總結

在調用execute(Params… params)方法後,execute方法會調用onPreExecute()方法,然後由ThreadPoolExecutor實例sExecutor執行一個帶WorkerRunnable參數的FutureTask任務,這個過程中doInBackground(Params… params)將被調用,當後臺任務結束以後,會調用FutureTask的done方法,將後臺結果通過InternalHandler實例sHandler發送出去.發送的消息由AsyncTaskResult構建,包含了該異步實例task和後臺任務的結果result. 對不同類型的消息,使用task實例調用不同的方法來完成與UI線程的通信.
1 MESSAGE_POST_PROGRESS消息:result.mTask.onProgressUpdate(result.mData);
如果被開發者覆寫的doInBackground(Params… params)方法中調用了publishProgress(Progress… values)方法,則通過InternalHandler實例sHandler發送一條MESSAGE_POST_PROGRESS消息,更新進度,sHandler處理消息時onProgressUpdate(Progress… values)方法將被調用;
2 MESSAGE_POST_CANCEL消息:result.mTask.onCancelled();
如果遇到異常,則發送一條MESSAGE_POST_CANCEL的消息,取消任務,sHandler處理消息時onCancelled()方法將被調用;
3 MESSAGE_POST_RESULT消息:result.mTask.finish(result.mData[0]);
如果執行成功,則發送一條MESSAGE_POST_RESULT的消息,顯示結果,sHandler處理消息時onPostExecute(Result result)方法被調用。
可知AsyncTask的本質就是對Thread+Handler的良好封裝,(sExecutor,mFuture,mWorker 均屬於Thread類,sHandler屬於Handler類)減少了開發者處理問題的複雜度,提高了開發效率。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章