去五金店買個鑽,不是因為我們需要鑽,我們只需需要孔,既然五金店無法買孔,退而求其次,買打孔的工具。同樣的對於後台線程,我們真正需要的是能夠在UI主線程外進行處理,Android提供一個讓程式員編寫後台操作更為容易和透明AsyncTask。
使用AsyncTask,需要建立AsyncTask的資料,並實現其中的抽象方法以及重寫某些方法。利用AsyncTask我們不需要自己來寫後台線程,無需終結後台線程,例如stop()的方式。AsyncTask的方式對無限迴圈的方式並不太合適,可能更合適使用Runnable或者Thread。
對於初次使用的人,AsyncTask看起來有一些複雜。我們先學些AsyncTask的基本情況,然後給出一個小例子來驗證。
AsyncTask
AsyncTask中有三個參數(例如class MyTask extends AsyncTask<參數1,參數2,參數3>{})
- 參數1:向背景工作的執行方法傳遞參數的類型
- 參數2:在背景工作執行過程中,要求主UI線程處理中間狀態,通常是一些UI處理中傳遞的參數類型
- 參數3:背景工作執行完返回時的參數類型
其中參數1和參數2是一個varags,例如String…,相當於String[]。
對於AsyncTask的使用步驟如下:
- 建立一個AsyncTask的子類,對象建立時帶參數(參數1,參數2,參數3)
- 調用對象的excute()時,將啟動後台進程,執行doInBackground()的代碼。excute()中所傳遞的參數類型在參數1中描述,屬於範式定義
- 如果我們希望在啟動後台進程中,進行某些初始化的處理,可以override onPreExecute()方法,注意這些代碼是在UI線程中執行的
- 在執行完後台進程,我們需要進行某些處理,例如停止某些UI的動態畫面,進度條消失等等,可以重寫onPostExecute()方法,同樣,這些代碼也是在UI主線程中執行。其中將doInBackground()的傳回值傳遞作為onPostExecute()參數中,其類型由參數3描述
- 在執行後台進程中,如果需要需要向UI線程報告某個處理狀態,可以通過publishProgress()來觸發,這樣在UI主線程中將執行重寫後的onProgressUpdate()的代碼,其中傳遞的參數的類型由參數2描述。
一個小例子
有一個ListView的小例子,一開始List中沒有內容,通過一個AsyncTask逐步在List中加入條目。
1)XML檔案:簡單的ListView布局
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout ... ...>
<ListView android:id="@android:id/list"
android:layout_width="fill_parent"
android:layout_height="fill_parent" />
</LinearLayout>
2)例子代碼
public class Chapter15Test3 extends ListActivity{
//這裡是List Item內容,在這個例子中,將在背景工作中逐個加入
private static String[] items={"lorem", "ipsum", "dolor","sit", "amet", "consectetuer","adipiscing", "elit", "morbi","vel", "ligula", "vitae","arcu", "aliquet", "mollis","etiam", "vel", "erat","placerat", "ante","porttitor",
"sodales","pellentesque", "augue","purus"};
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.chapter_8_test2);
//在這個例子中,我們一開始並沒有匯入items的資料,注意item資料為建立的ArrayList,即無內容
setListAdapter(new ArrayAdapter<String>(this,android.R.layout.simple_list_item_1,new ArrayList<String>()));
//步驟5:建立背景工作的對象,並通過execute()啟動後台線程,調用doInBackground()的代碼,execute中的參數類型為參數1,這裡我們不需要傳遞任何內容
new AddStringTask().execute();
}
//步驟1:建立AsyncTask子類,參數1是Void的範式類型,參數2是String的範式類型,參數3是Void 。其中參數1:向背景工作的執行方法傳遞參數的類型;參數2:在背景工作執行過程中,要求主UI線程處理中間狀態,通常是一些UI處理中傳遞的參數類型;參數3:背景工作執行完返回時的參數類型。
private class AddStringTask extends AsyncTask<Void, String,Void>{
//我們加入一個檢測資訊的方法,列印當前在哪個線程執行的資訊
private void printInfo(String info){
Log.d("WEI", info + " : Tread is " + Thread.currentThread().getName());
}
//步驟2:實現抽象方法doInBackground(),代碼將在後台線程中執行,由execute()觸發,由於這個例子並不需要傳遞參數,使用Void...,具體書寫方式為範式書寫
protected Void/*參數3*/ doInBackground(Void...params/*參數1*/) {
for(String item : items){
//步驟3:通知UI主線程執行相關的操作(在onProgressUpdate中定義)
publishProgress(item/*參數2*/);
printInfo("doInBackgound " + item);
SystemClock.sleep(200);
}
return null;
}
//步驟3:定義收到pushProgress()觸發後,在UI主線程執行的內容,在本例,將item加入list中。方法中的參數為範式方式,實質為數組,由於我們只傳遞了item一個String,要擷取,為values[0]
protected void onProgressUpdate(String...
values/*參數2*/) {
printInfo("onProgressUpdate get param " + values[0]);
((ArrayAdapter<String>)getListAdapter()).add(values[0]);
}
//步驟4:定義後台進程執行完後的處理,本例,採用Toast
protected void onPostExecute(Void result/*參數3*/) {
printInfo("onPostExecute");
Toast.makeText(Chapter15Test3.this, "Done!", Toast.LENGTH_SHORT).show();
}
}
}
我們根據printInfo跟蹤各部分代碼在哪裡執行:doInBackground在後台線程執行,onProgressUpdate()和onPostExecute()在UI主線程執行。main就是UI主線程,而AsyncTask #1為後台線程,名字不一樣。
需要注意
雖然Android提供背景工作方便我們處理,是否使用背景工作,以及如何使用背景工作,我們要注意下面的內容:
可能在執行後台線程處理中,使用者和UI之間存在互動,這些交換可能會對背景工作有重要的影響,因此需要通知後台線程,Android提供很多的類來處理,封裝在java.util.concurrent包中,協助與後台線程的安全通訊。可能在執行後台線程處理中,我們的Activity就已經被kill了,例如有一個電話過來,然後發給簡訊,查看號碼本。這時系統可能將你的activity踢走,接著後面我們會學習Activity的生命週期,瞭解相關的情況。在編程中,出現這種情況,只要有可能,需要將後台進程關閉。可能在執行後台線程處理中,出現某種錯誤,例如後台在下載URL,而網路連接中斷了。這種情況下關閉後台進程可能是最好的處理。此外背景工作是消耗CPU和記憶體,是有代價的,我們應該確保它處理的時候更為有效。
相關連結:我的Andriod開發相關文章