AsyncTask在面試中應(yīng)該比較經(jīng)常會(huì)問到快毛,雖然我們現(xiàn)在開發(fā)中可能用的并不多。
它的使用還是比較簡單的闯两。
使用AsyncTask
下面以一個(gè)保存短信示例來演示一下AsyncTask的使用笔诵。
public class MainActivity extends Activity {
private TextView tv_desc;
private ProgressBar progressBar;
private Button btn_backup;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
tv_desc = (TextView) findViewById(R.id.tv_desc);
progressBar = (ProgressBar) findViewById(R.id.progressBar);
btn_backup = (Button) findViewById(R.id.btn_backup);
btn_backup.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
// new AsyncTask<輸入值, 進(jìn)度, 處理結(jié)果>;
new AsyncTask<Void, Integer, Void>() {
private int processCount = 0;
//在子線程運(yùn)行的方法
@Override
protected Void doInBackground(Void... params) {
for (int i = 0; i < 20; i++) {
SystemClock.sleep(500);
processCount++;
//傳遞給onProgressUpdate
//當(dāng)然也可以直接用全局變量processCount
publishProgress(processCount);
}
return null;
}
@Override
protected void onPreExecute() {
//初始化數(shù)據(jù)
progressBar.setVisibility(View.VISIBLE);
progressBar.setMax(100);
progressBar.setProgress(0);
tv_desc.setText("正在備份0/20條短信");
btn_backup.setEnabled(false);
}
@Override
protected void onPostExecute(Void aVoid) {
progressBar.setVisibility(View.GONE);
tv_desc.setText("共備份了20條短信");
btn_backup.setEnabled(true);
}
@Override
protected void onProgressUpdate(Integer... values) {
System.out.println(values[0]);
int num = values[0];
progressBar.setProgress(num * 100 / 20);
tv_desc.setText("正在備份" + num + "/20條短信");
}
}.execute();
}
});
}
}
AsyncTask需要注意的地方
AsyncTask算是應(yīng)用了模板方法設(shè)計(jì)模式,具體的調(diào)用流程都已經(jīng)封裝好了萧恕,子類只需要重寫一些必須的回調(diào)方法就可以輕松的完成任務(wù)了刚梭。
AsyncTask有四個(gè)比較重要的方法doInBackground、onPreExecute票唆、onPostExecute望浩、onProgressUpdate,雖然這些方法都標(biāo)了有注解惰说,比如WorkerThread是在工作線程調(diào)用磨德,MainThread是在主線程中調(diào)用,但是這個(gè)實(shí)際上只是一個(gè)規(guī)則吆视,如果你不按照這個(gè)規(guī)則來典挑,程序運(yùn)行也不會(huì)crash。這4個(gè)方法都是protected的啦吧,因此不能直接在AsyncTask的外部直接調(diào)用您觉,doInBackground方法是必須要重寫的,而其它3個(gè)方法需要結(jié)合實(shí)際看是都需要重寫授滓。另外如果你需要在AsyncTask中回調(diào)onProgressUpdate方法琳水,需要自己在doInBackground中自己主動(dòng)調(diào)用。
doInBackground // WorkerThread般堆,抽象方法在孝,需要重寫
onPreExecute // MainThread
onPostExecute // MainThread
onProgressUpdate // MainThread
publishProgress // 開發(fā)者在doInBackground中主動(dòng)調(diào)用,然后AsyncTask的onProgressUpdate方法會(huì)被回調(diào)淮摔。
AsyncTask在每個(gè)版本都會(huì)有一些關(guān)鍵地方的變化私沮,所以需要注意一些事項(xiàng):
我們使用AsyncTask,一般是想在子線程中做耗時(shí)操作和橙,然后把結(jié)果發(fā)送到主線程中仔燕,既然涉及到線程切換造垛,那必須有Handler機(jī)制來保證。而且要必須保證Handler所關(guān)聯(lián)的Looper對象已經(jīng)創(chuàng)建晰搀,而且這個(gè)Looper是在主線程中創(chuàng)建五辽。
這也就引出了AsyncTask類的加載實(shí)際和對象的初始化問題。
- AsyncTask類的加載線程問題外恕。AsyncTask 類中有一個(gè)InternalHandler(也就是一個(gè)Handler)類型的類成員 sHandler奔脐,AsyncTask是使用sHandler對象來做線程切換。 在6.0以前(包括6.0)吁讨,InternalHandler只有一個(gè)無參構(gòu)造髓迎,因此sHandler引用所指向的InternalHandler對象必須在主線程中創(chuàng)建,因?yàn)橹挥兄骶€程默認(rèn)情況下已經(jīng)創(chuàng)建了Looper對象建丧。從Andorid4.1到Android5.0排龄,sHandler的創(chuàng)建時(shí)餓漢式,也就是說當(dāng)AsyncTask類加載的時(shí)候就會(huì)創(chuàng)建InternalHandler對象翎朱,為了保證InternalHandler是在主線程中創(chuàng)建橄维,必須保證AsyncTask是在主線程中加載。當(dāng)一個(gè)App進(jìn)程啟動(dòng)的時(shí)候拴曲,在ActivityThread的main方法中會(huì)調(diào)用AsyncTask的的類方法init争舞,這會(huì)觸發(fā)AsyncTask類的加載,也就保證了AsyncTask是在主線程中加載的澈灼,繼而保證了在主線程中創(chuàng)建InternalHandler對象竞川,進(jìn)而保證任務(wù)結(jié)果正常的切換到主線程。
6.0的時(shí)候已經(jīng)沒有init方法了叁熔,但是有一個(gè)靜態(tài)方法getHandler委乌,這個(gè)時(shí)候InternalHandler的創(chuàng)建時(shí)屬于懶漢式。此時(shí)InternalHandler同樣能保證關(guān)聯(lián)的Looper是主線程荣回,因?yàn)镮nternalHandler在無參構(gòu)造方法中遭贸,調(diào)用了super,傳遞了Looper.getMainLooper()心软。所以在6.0的時(shí)候沒有必要再去管是不是在主線程中加載了壕吹。
private static Handler getHandler() {
synchronized (AsyncTask.class) {
if (sHandler == null) {
sHandler = new InternalHandler();
}
return sHandler;
}
}
到8.0之后,AysncTask的構(gòu)造方法變?yōu)?個(gè)删铃,不過對于開發(fā)者來說只能看到1個(gè)耳贬,另外兩個(gè)是隱藏的API。所以可以認(rèn)為和6.0泳姐、7.0的使用情況是一樣的效拭。InternalHandler還是懶加載創(chuàng)建。
public AsyncTask() {
this((Looper) null);
}
/**
* Creates a new asynchronous task. This constructor must be invoked on the UI thread.
*
* @hide
*/
public AsyncTask(@Nullable Handler handler) {
this(handler != null ? handler.getLooper() : null);
}
/**
* Creates a new asynchronous task. This constructor must be invoked on the UI thread.
*
* @hide
*/
public AsyncTask(@Nullable Looper callbackLooper) {
...
一些博客或者書籍里說胖秒,AsyncTask對象必須是在主線程中創(chuàng)建缎患,并在主線程中調(diào)用其execute方法。這個(gè)說法并不完全準(zhǔn)確阎肝。你真要是想在子線程中去創(chuàng)建AsyncTask對象并在子線程中去調(diào)用其execute方法挤渔,這個(gè)也沒什么問題的。只不過通常來說风题,我們是為了讓AsyncTask在子線程中做耗時(shí)操作判导,在主線程中回調(diào)進(jìn)度方法并且收到結(jié)果回調(diào),而且通常還有一個(gè)onPreExecute方法來做一些初始化的工作沛硅,這個(gè)onPreExecute方法是在execute方法中調(diào)用的眼刃,通常我們需要onPreExecute在主線程中回調(diào),從這個(gè)角度上來說摇肌,AsyncTask對象應(yīng)該是在主線程中創(chuàng)建并且在主線程中執(zhí)行其execute方法擂红。
不要在AsyncTask之外直接調(diào)用doInBackground、onPreExecute围小、onPostExecute 昵骤、onProgressUpdate ,這幾個(gè)方法是有AsyncTask對象自己內(nèi)部回調(diào)的肯适。
AsyncTask只能執(zhí)行一次变秦,其內(nèi)部有一個(gè)枚舉類型的Status用于維護(hù)執(zhí)行狀態(tài):PENDING、RUNNING框舔、FINISHED蹦玫。默認(rèn)情況下是PENDING,表示可以執(zhí)行刘绣,當(dāng)調(diào)用execute方法之后钳垮,會(huì)檢查其狀態(tài)是否是PENDING,如果不是的話就會(huì)拋出異常额港。
在1.6之前AsyncTask是串行執(zhí)行任務(wù)的饺窿,1.6的時(shí)候改為線程池并行執(zhí)行任務(wù),3.0之后為了防止并發(fā)執(zhí)行錯(cuò)誤移斩,又改為了默認(rèn)情況是的串行執(zhí)行任務(wù)肚医,不過添加了executeOnExecutor方法,通過由開發(fā)者自己提供Executor來實(shí)現(xiàn)并行執(zhí)行任務(wù)向瓷。這種方式算是依賴注入的方式肠套,符合設(shè)計(jì)模式中的里氏替換原則(Liskov Substitution principle),從這我們也可以看出依賴注入的好處猖任,當(dāng)目前的實(shí)現(xiàn)不滿足開發(fā)者使用的時(shí)候可以由開發(fā)者自己來實(shí)現(xiàn)你稚,解耦而且擴(kuò)展性好。