天天看點

android異步任務asyntask詳解

在android中實作異步任務機制有兩種方式,handler和asynctask。

為了簡化操作,android1.5提供了工具類android.os.asynctask,它使建立異步任務變得更加簡單,不再需要編寫任務線程和handler執行個體即可完成相同的任務。

先來看看asynctask的定義:

public abstract class asynctask<params, progress, result> {  

三種泛型類型分别代表“啟動任務執行的輸入參數”、“背景任務執行的進度”、“背景計算結果的類型”。在特定場合下,并不是所有類型都被使用,如果沒有被使用,可以用java.lang.void類型代替。

一個異步任務的執行一般包括以下幾個步驟:

1.execute(params... params),執行一個異步任務,需要我們在代碼中調用此方法,觸發異步任務的執行。

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)這幾個方法。

4.不能在doinbackground(params... params)中更改ui元件的資訊。

5.一個任務執行個體隻能執行一次,如果執行第二次将會抛出異常。

接下來,我們來看看如何使用asynctask執行異步任務操作,我們先建立一個項目,結構如下:

android異步任務asyntask詳解

結構相對簡單一些,讓我們先看看mainactivity.java的代碼:

package com.scott.async;  

import java.io.bytearrayoutputstream;  

import java.io.inputstream;  

import org.apache.http.httpentity;  

import org.apache.http.httpresponse;  

import org.apache.http.httpstatus;  

import org.apache.http.client.httpclient;  

import org.apache.http.client.methods.httpget;  

import org.apache.http.impl.client.defaulthttpclient;  

import android.app.activity;  

import android.os.asynctask;  

import android.os.bundle;  

import android.util.log;  

import android.view.view;  

import android.widget.button;  

import android.widget.progressbar;  

import android.widget.textview;  

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

        execute.setonclicklistener(new view.onclicklistener() {  

            @override  

            public void onclick(view v) {  

                //注意每次需new一個執行個體,建立的任務隻能執行一次,否則會出現異常  

                mtask = new mytask();  

                mtask.execute("http://www.baidu.com");  

                execute.setenabled(false);  

                cancel.setenabled(true);  

            }  

        });  

        cancel = (button) findviewbyid(r.id.cancel);  

        cancel.setonclicklistener(new view.onclicklistener() {  

                //取消一個正在執行的任務,oncancelled方法将會被調用  

                mtask.cancel(true);  

        progressbar = (progressbar) findviewbyid(r.id.progress_bar);  

        textview = (textview) findviewbyid(r.id.text_view);  

    }  

    private class mytask extends asynctask<string, integer, string> {  

        //onpreexecute方法用于在執行背景任務前做一些ui操作  

        @override  

        protected void onpreexecute() {  

            log.i(tag, "onpreexecute() called");  

            textview.settext("loading...");  

        }  

        //doinbackground方法内部執行背景任務,不可在此方法内修改ui  

        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方法用于更新進度資訊  

        protected void onprogressupdate(integer... progresses) {  

            log.i(tag, "onprogressupdate(progress... progresses) called");  

            progressbar.setprogress(progresses[0]);  

            textview.settext("loading..." + progresses[0] + "%");  

        //onpostexecute方法用于在執行完背景任務後更新ui,顯示結果  

        protected void onpostexecute(string result) {  

            log.i(tag, "onpostexecute(result result) called");  

            textview.settext(result);  

            execute.setenabled(true);  

            cancel.setenabled(false);  

        //oncancelled方法用于在取消執行中的任務時更改ui  

        protected void oncancelled() {  

            log.i(tag, "oncancelled() called");  

            textview.settext("cancelled");  

            progressbar.setprogress(0);  

}  

布局檔案main.xml代碼如下:

<?xml version="1.0" encoding="utf-8"?>  

<linearlayout xmlns:android="http://schemas.android.com/apk/res/android"  

    android:orientation="vertical"  

    android:layout_width="fill_parent"  

    android:layout_height="fill_parent">  

    <button  

        android:id="@+id/execute"  

        android:layout_width="fill_parent"  

        android:layout_height="wrap_content"  

        android:text="execute"/>  

        android:id="@+id/cancel"  

        android:enabled="false"  

        android:text="cancel"/>  

    <progressbar   

        android:id="@+id/progress_bar"   

        android:layout_width="fill_parent"   

        android:layout_height="wrap_content"   

        android:progress="0"  

        android:max="100"  

        style="?android:attr/progressbarstylehorizontal"/>  

    <scrollview  

        android:layout_height="wrap_content">  

        <textview  

            android:id="@+id/text_view"  

            android:layout_width="fill_parent"   

            android:layout_height="wrap_content"/>  

    </scrollview>  

</linearlayout>  

因為需要通路網絡,是以我們還需要在androidmanifest.xml中加入通路網絡的權限:

<uses-permission android:name="android.permission.internet"/>  

我們來看一下運作時的界面:

android異步任務asyntask詳解
android異步任務asyntask詳解
android異步任務asyntask詳解
android異步任務asyntask詳解

以上幾個截圖分别是初始界面、執行異步任務時界面、執行成功後界面、取消任務後界面。執行成功後,整個過程日志列印如下:

android異步任務asyntask詳解

如果我們在執行任務時按下了“cancel”按鈕,日志列印如下:

android異步任務asyntask詳解

可以看到oncancelled()方法将會被調用,onpostexecute(result result)方法将不再被調用。

上面介紹了asynctask的基本應用,有些朋友也許會有疑惑,asynctask内部是怎麼執行的呢,它執行的過程跟我們使用handler又有什麼差別呢?答案是:asynctask是對thread+handler良好的封裝,在android.os.asynctask代碼裡仍然可以看到thread和handler的蹤迹。下面就向大家詳細介紹一下asynctask的執行原理。

我們先看一下asynctask的大綱視圖:

android異步任務asyntask詳解

我們可以看到關鍵幾個步驟的方法都在其中,doinbackground(params... params)是一個抽象方法,我們繼承asynctask時必須覆寫此方法;onpreexecute()、onprogressupdate(progress... values)、onpostexecute(result result)、oncancelled()這幾個方法體都是空的,我們需要的時候可以選擇性的覆寫它們;publishprogress(progress... values)是final修飾的,不能覆寫,隻能去調用,我們一般會在doinbackground(params...

params)中調用此方法;另外,我們可以看到有一個status的枚舉類和getstatus()方法,status枚舉類代碼段如下:

        //初始狀态  

    private volatile status mstatus = status.pending;  

    public enum status {  

        /** 

         * indicates that the task has not been executed yet. 

         */  

        pending,  

         * indicates that the task is running. 

        running,  

         * indicates that {@link asynctask#onpostexecute} has finished. 

        finished,  

/** 

     * returns the current status of this task. 

     * 

     * @return the current status. 

     */  

    public final status getstatus() {  

        return mstatus;  

可以看到,asynctask的初始狀态為pending,代表待定狀态,running代表執行狀态,finished代表結束狀态,這幾種狀态在asynctask一次生命周期内的很多地方被使用,非常重要。

介紹完大綱視圖相關内容之後,接下來,我們會從execute(params... params)作為入口,重點分析一下asynctask的執行流程,我們來看一下execute(params... params)方法的代碼段:

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:  

                    //如果該任務已經執行完成則抛出異常  

                            + " 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;  

代碼中涉及到三個陌生的變量:mworker、sexecutor、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<result>接口中的call()方法,代碼如下:

private static abstract class workerrunnable<params, result> implements callable<result> {  

        params[] mparams;  

而mfuture實際上是java.util.concurrent.futuretask的執行個體,下面是它的futuretask類的相關資訊:

 * a cancellable asynchronous computation. 

 * ... 

 */  

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

可以看到futuretask是一個可以中途取消的用于異步計算的類。

下面是mworker和mfuture執行個體在asynctask中的展現:

   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()方法中,如果捕捉到了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);  

               case message_post_cancel:  

                //調用asynctask.oncancelled方法  

                   result.mtask.oncancelled();  

我們看到,在處理消息時,遇到“message_post_result”時,它會調用asynctask中的finish()方法,我們來看一下finish()方法的定義:

private void finish(result result) {  

        if (iscancelled()) result = null;  

        onpostexecute(result);  //調用onpostexecute顯示結果  

        mstatus = status.finished;  //改變狀态為finished  

原來finish()方法是負責調用onpostexecute(result result)方法顯示結果并改變任務狀态的啊。

另外,在mfuture對象的done()方法裡,建構一個消息時,這個消息包含了一個asynctaskresult類型的對象,然後在shandler執行個體對象的handlemessage(message msg)方法裡,使用下面這種方式取得消息中附帶的對象:

asynctaskresult result = (asynctaskresult) msg.obj;  

這個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的執行個體和某種類型的資料集,我們再來看一下建構消息時的代碼:

//發送取消任務的消息  

message = shandler.obtainmessage(message_post_cancel,  

        new asynctaskresult<result>(asynctask.this, (result[]) null));  

message.sendtotarget();  

//發送顯示結果的消息  

message = shandler.obtainmessage(message_post_result,  

         new asynctaskresult<result>(asynctask.this, result));  

在處理消息時是如何使用這個對象呢,我們再來看一下:

result.mtask.finish(result.mdata[0]);  

result.mtask.onprogressupdate(result.mdata);  

概括來說,當我們調用execute(params... params)方法後,execute方法會調用onpreexecute()方法,然後由threadpoolexecutor執行個體sexecutor執行一個futuretask任務,這個過程中doinbackground(params... params)将被調用,如果被開發者覆寫的doinbackground(params... params)方法中調用了publishprogress(progress... values)方法,則通過internalhandler執行個體shandler發送一條message_post_progress消息,更新進度,shandler處理消息時onprogressupdate(progress...

values)方法将被調用;如果遇到異常,則發送一條message_post_cancel的消息,取消任務,shandler處理消息時oncancelled()方法将被調用;如果執行成功,則發送一條message_post_result的消息,顯示結果,shandler處理消息時onpostexecute(result result)方法被調用。

經過上面的介紹,相信朋友們都已經認識到asynctask的本質了,它對thread+handler的良好封裝,減少了開發者處理問題的複雜度,提高了開發效率,希望朋友們能多多體會一下。

繼續閱讀