當android應用程序啓動的時候,android爲應用程序開啓了一個線程,稱爲主線程也稱UI線程.這個線程負責分發用戶響應並與用戶進行交互.
如果在應用程序中只有主線程一個線程運行,在有些情況下會出現問題:比如請求網絡數據,與數據庫進行交互等一些比較耗時的操作進行時,此時,主線程被阻塞,任何消息都不能發送出去.從用戶的角度上來看,應用程序被掛起了.更糟糕的是,如果UI線程被阻塞5秒以上就會出現ANR現象.
在這種情況下,我們可以考慮開啓額外的線程,在線程裏面處理耗時操作.考慮下面的代碼:
public void onClick(View v) {
new Thread(new Runnable() {
public void run() {
Bitmap b = loadImageFromNetwork();
mImageView.setImageBitmap(b);
}
}).start();
}
在onClick方法裏面下載網絡圖片,並設置到本地的ImageView.
這看起來是一個不錯的處理方案,並沒有阻塞UI線程,但是由於在UI線程裏面修改主線程裏面的資源,這是線程不安全的.
android提供了以下幾種方法在其他線程中訪問主線程:
Activity.runOnUiThread(Runnable)
View.post(Runnable)
View.postDelayed(Runnable, long)
Handler
以上代碼可以修改成:
public void onClick(View v) {
new Thread(new Runnable() {
public void run() {
final Bitmap b = loadImageFromNetwork();
mImageView.post(new Runnable() {
public void run() {
mImageView.setImageBitmap(b);
}
});
}
}).start();
}
但不幸的是,這些方法和類會降低代碼的可讀性.並且當我們需要進行復雜的操作和頻繁進行UI更新時,這樣的方法會變得更糟糕.
爲了彌補這樣的缺陷,android1.5版本之後,提供了AsyncTask類,這個類提供了一個長時間運行的任務並,其中提供了與用戶進行交互的接口.
AsyncTask 可以替你進行線程管理.我們可以把上面的例子進行以下改寫:
public void onClick(View v) {
new DownloadImageTask().execute("http://example.com/image.png");
}
private class DownloadImageTask extends AsyncTask<String, Void, Bitmap> {
protected Bitmap doInBackground(String... urls) {
return loadImageFromNetwork(urls[0]);
}
protected void onPostExecute(Bitmap result) {
mImageView.setImageBitmap(result);
}
}
AsyncTask 必須要通過繼承後才能使用.以下幾點需要注意
1.一個AsyncTask實例必須要創建在UI線程上,並僅僅只執行一次,
2.doInBackground方法是自動在用戶線程被執行的,而onPreExecute(), onPostExecute()和 onProgressUpdate() 回調方法被 UI 線程調用.
3.doInBackground()的返回值被髮送到 onPostExecute()
4.任意時刻在doInBackground()裏,我們可以通過調用所有的publishProgress() 使UI線程執行onProgressUpdate()方法
5.你可以在任意時間任意線程結束任務
一個小例子:
首先定義類DownloadFilesTask 繼承自AsyncTask,AsyncTask中傳入的三個參數類型URL, Integer, Long分別與三個回調方法的參數類型對應.
private class DownloadFilesTask extends AsyncTask<URL, Integer, Long> {
protected Long doInBackground(URL... urls) {
int count = urls.length;
long totalSize = 0;
for (int i = 0; i < count; i++) {
totalSize += Downloader.downloadFile(urls[i]);
publishProgress((int) ((i / (float) count) * 100));
}
return totalSize;
}
protected void onProgressUpdate(Integer... progress) {
setProgressPercent(progress[0]);
}
protected void onPostExecute(Long result) {
showDialog("Downloaded " + result + " bytes");
}
}
在主線程中執行任務(必須在主線程中執行):
new DownloadFilesTask().execute(url1, url2, url3);
此時傳入的三個參數被doInBackground獲取到,它會調用publishProgress,這個方法裏面可以傳多個參數,publishProgress會觸發onProgressUpdate,該方法取出了第一個參數.doInBackground方法會返回一個Long 類型的totalSize,這個作爲參數傳入onPostExecute.
1.任務執行後,onPreExecute()方法馬上被主線程喚醒,
2.onPreExecute()方法執行完後,doInBackground(Params...)方法馬上被其所在的線程喚醒,
3.在後臺線程計算結束後,onPostExecute(Result)方法立即被主線程喚醒.
4.
cancel(boolean)方法可以在任意時間結束任務,該方法可以導致
isCancelled()
返回true.此時,doInBackground(Object[])調用結束後會調用onCancelled(Object)方法,爲了保證任務儘快的結束,需要週期性的在doInBackground內部檢查isCancelled()的返回值,如果可能的話,使用looper