在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執行異步任務操作,我們先建立一個項目,結構如下:
結構相對簡單一些,讓我們先看看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"/>
我們來看一下運作時的界面:
以上幾個截圖分别是初始界面、執行異步任務時界面、執行成功後界面、取消任務後界面。執行成功後,整個過程日志列印如下:
如果我們在執行任務時按下了“cancel”按鈕,日志列印如下:
可以看到oncancelled()方法将會被調用,onpostexecute(result result)方法将不再被調用。
上面介紹了asynctask的基本應用,有些朋友也許會有疑惑,asynctask内部是怎麼執行的呢,它執行的過程跟我們使用handler又有什麼差別呢?答案是:asynctask是對thread+handler良好的封裝,在android.os.asynctask代碼裡仍然可以看到thread和handler的蹤迹。下面就向大家詳細介紹一下asynctask的執行原理。
我們先看一下asynctask的大綱視圖:
我們可以看到關鍵幾個步驟的方法都在其中,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的良好封裝,減少了開發者處理問題的複雜度,提高了開發效率,希望朋友們能多多體會一下。