##寫在前面 昨天在看Android中的快取機制,順帶把非同步載入任務複習了一遍,尤其是AsyncTask。由於我在一般情況下使用較多的就是Handler方式執行非同步任務,很少用到AsyncTask,所以現在總結一下加深自己的印象,並分享出來與大家交流。後幾天會發一些關於Android中的快取機制。 在本文書寫過程中,我借鑑的資料有《第一行程式碼》、《Android開發藝術探索》、慕課網的一期課程以及相關的博文,連結在文末註明。下面是本文的目錄,比較簡單。
- 非同步訊息處理機制
- 組成部分
- 基本流程
- 初識AsyncTask
- 基本介紹
- 執行順序
- 注意事項
- AsyncTask的使用
- 載入網路圖片
- 模擬進度條
- AsyncTask和Handler兩種非同步方式區別
- 資料來源
- 專案原始碼
##非同步訊息處理機制
非同步訊息處理機制是Android開發中的基礎知識。為了方便後面的AsyncTask的理解,我就先簡單說一下這個機制,但不作為本文的重點。
###組成部分 Android中的非同步訊息主要是由四個部分組成:
- Message:訊息
- MessageQueue:訊息佇列
- Handler:訊息處理者
- Looper:訊息迴圈者
下面我們依次來看:
####1、Message
Message就是非同步訊息中需要傳遞的訊息。它會在內部攜帶一些少量的資訊,用於不同執行緒之間交換資料。它本身有四個欄位,用來處理不同型別的資訊:
- what:訊息的一些標誌,可以根據不同的標誌做不同的處理
- obj:欄位為Object型別,這個不用多解釋了,物件中的大Boss
- arg1和arg2:整型型別,可以存放一些標誌位或不易混淆的整型值
####2、MessageQueue
知道了Message就很好理解這個了,這是一個訊息佇列。佇列中存放著大量的Message,這些Message都是沒有被處理的,所以放在佇列中等待處理。值得注意的是,每個執行緒中有且僅有一個MessageQueue物件。
####3、Handler 這個我們很熟悉,不好翻譯,一般用來處理訊息。我們就叫它訊息處理者吧。它是處理機制中一個比較重要的角色,是用來傳送和處理訊息的。主要是兩個方法:
- sendMessage(Message msg):傳送訊息
- handleMessage(Message msg):處理訊息
也就是說經過Handler傳送的訊息最終會傳遞到handleMessage中,進而被處理。
####4、Looper Looper不是很好理解。《第一行程式碼》中將其看作MessageQueue的管家,比較抽象。而《Android開發藝術探索》中將其定義成訊息迴圈者,這個比較符合邏輯。也就是說,當執行緒中的Looper呼叫自身的loop()方法後,它會進入到一個無限迴圈中,迴圈監測MessageQueue中是否存在訊息,如有存在一條未處理的訊息,Looper就會將其取出,傳遞給Handler的handleMessage方法,讓其處理。
這裡我們需要注意兩點:
- 每個執行緒中有且僅有一個Looper物件。
- 新new出來的Thread是沒有Looper的,也就是隻有UI執行緒預設有Looper。
###基本流程
明白了上述這些基本定義後,我們來看一下非同步訊息處理機制的大體流程。二話不說,先上圖。
從圖中我們可以看出,大體流程如下:
- 首先我們需要在主執行緒中建立一個Handler物件,並且需要重寫handleMessage方法;
- 其次在子執行緒中需要與主執行緒進行通訊的時候,就通過Message物件攜帶一些資訊,並通過Handler物件傳送出去。
- 傳送出去後該訊息會進入MessageQueue等待處理。
- 這個時候Looper就起作用了,它一直迴圈迴圈終於監測到佇列中有一條待處理的訊息,立即取出並分發到handleMessage方法,這樣主執行緒就收到這條訊息並開始處理。
關於這個機制我就簡單介紹到這,如果讀者對這個機制又不懂的地方可以Google相關資料,也有大量優秀博文。文末也有相關博文連結,不再贅述。
##初識AsyncTask
###基本介紹 AsyncTask是一種輕量級的非同步任務類,它與Handler一樣,可以在後臺執行任務。並可以把執行任務的過程及結果返回給主執行緒。這樣主執行緒就可以做一些更新UI的操作。
它的本質是一個抽象的泛型類,所以我們在使用的時候需要繼承這個AsyncTask類:
public abstract class AsyncTask<Params,Progress,Result>
可以看到,類提供了三個引數:
- Params:任務啟動時需要輸入的引數型別
- Progress:後臺任務執行中返回的進度值的型別
- Result:後臺執行任務完成後返回結果的型別
同時,它有四個核心方法,我們依次來看:
- onPreExecute():該方法在主執行緒中執行,通常使用者完成一些準備和初始化的操作;
- doInBackground(Params... params):在子執行緒中執行,執行非同步任務的關鍵方法,必須重寫。此方法中可以呼叫publishProgress方法來更新任務進度,而publishProgress會呼叫onProgressUpdate方法來進行進度更新。注意此方法需要返回值給onPostExecute方法;
- onProgressUpdate(Progress...values):在主執行緒中執行,用於任務進度的更新;
- onPostExecute(Result result):在主執行緒中執行,該方法的引數是doInBackground的返回值。
當然還有其他方法,但是不怎麼常用,這裡不再介紹(其實我也不瞭解)。
當你自定義一個CustomTask繼承AsyncTask複寫這幾個方法後,就需要開啟任務,開啟方式為:
new CustomTask().execute();
當然在這裡我沒有寫引數,在實際開發中要注意填入相應的引數。
###執行順序
當我們自定義Task並複寫這四個核心方法後,來看看這幾個方法的執行順序。測試程式碼如下:
/**
* 自定義非同步任務類
*/
class CustomTask extends AsyncTask<Void, Void, Void> {
@Override
protected void onPreExecute() {
super.onPreExecute();
Log.w("Task", " ----> onPreExecute");
}
@Override
protected Void doInBackground(Void... params) {
publishProgress();
Log.w("Task", " ----> doInBackground");
return null;
}
@Override
protected void onProgressUpdate(Void... values) {
super.onProgressUpdate(values);
Log.w("Task", " ----> onProgressUpdate");
}
@Override
protected void onPostExecute(Void aVoid) {
super.onPostExecute(aVoid);
Log.w("Task", " ----> onPostExecute");
}
}
複製程式碼
來看Log日誌:
可以看出執行順序依次是:
onPreExecute -> doInBackground -> onProgressUpdate -> onPostExecute
###注意事項
基本瞭解了AsyncTask後,我們需要知道一些注意事項和使用過程中的一些條件限制。
- 在四個核心方法中,只有doInBackground方法執行在子執行緒,其他方法都執行在主執行緒;
- AsyncTask類必須在主執行緒中載入,也就是第一次訪問AsyncTask必須發生在主執行緒;
- AsyncTask物件必須在主執行緒中建立;
- AsyncTask的execute方法必須在主執行緒中呼叫;
- 四個核心方法只能系統自動呼叫,而不能主動呼叫;
##AsyncTask的使用
好了,看到這裡相信你已經對AsyncTask有了一個基本的認識了,現在我們就通過兩個例項在看AsyncTask在專案中的具體使用。
###載入網路圖片
首先來看第一個Demo,通過圖片的URL地址載入網路圖片。已經是網路圖片了,當然是耗時任務,所以我們需要開啟非同步任務。當然可以開啟子執行緒來獲取,但是這裡我們採取AsyncTask這樣的方式。完成效果如下:
來看程式碼,程式碼比較簡單,註釋也寫的很清晰,就不再解釋了:
/**
* 非同步任務載入網路圖片
*/
public class NetPicActivity extends AppCompatActivity {
private ImageView netPicImage;
private ProgressBar netPicProgressBar;
private static String picUrl = "http://www.iamxiarui.com/wp-content/uploads/2016/05/桌布.jpg";
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_net_pic);
netPicImage = (ImageView) findViewById(R.id.iv_netpic);
netPicProgressBar = (ProgressBar) findViewById(R.id.pb_netpic);
//通過呼叫execute方法開始處理非同步任務.相當於執行緒中的start方法.
new NetAsyncTask().execute(picUrl);
}
/**
* 自定義網路請求非同步任務
*/
class NetAsyncTask extends AsyncTask<String, Void, Bitmap> {
/**
* onPreExecute用於非同步處理前的操作
*/
@Override
protected void onPreExecute() {
super.onPreExecute();
//此處將progressBar設定為可見.
netPicProgressBar.setVisibility(View.VISIBLE);
}
/**
* 在doInBackground方法中進行非同步任務的處理
*
* @param params 引數為URL
* @return Bitmap物件
*/
@Override
protected Bitmap doInBackground(String... params) {
//獲取傳進來的引數
String url = params[0];
Bitmap bitmap = null;
URLConnection connection;
InputStream is;
try {
connection = new URL(url).openConnection();
is = connection.getInputStream();
//為了更清楚的看到載入圖片的等待操作,將執行緒休眠3秒鐘
Thread.sleep(3000);
BufferedInputStream bis = new BufferedInputStream(is);
//通過decodeStream方法解析輸入流
bitmap = BitmapFactory.decodeStream(bis);
is.close();
bis.close();
} catch (Exception e) {
e.printStackTrace();
}
return bitmap;
}
/**
* onPostExecute用於UI的更新.此方法的引數為doInBackground方法返回的值
*
* @param bitmap 網路圖片
*/
@Override
protected void onPostExecute(Bitmap bitmap) {
super.onPostExecute(bitmap);
//隱藏progressBar
netPicProgressBar.setVisibility(View.GONE);
//更新imageView
netPicImage.setImageBitmap(bitmap);
}
}
}
複製程式碼
對了,一定要記得新增網路許可權,不然沒有效果。
###模擬進度條
載入網路圖片的時候,我們沒有用到onProgressUpdate方法,現在就模擬一個進度條載入的Demo,效果如下:
來看程式碼,這裡我們模擬了進度條的載入,並在doInBackground中呼叫了publishProgress方法,讓進度條更新,具體程式碼如下:
/**
* 用非同步任務模擬進度條的更新
*/
public class ProgressActivity extends AppCompatActivity {
private ProgressBar mainProgressBar;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_progress);
mainProgressBar = (ProgressBar) findViewById(R.id.pb_main);
//開啟非同步任務
new PbAsyncTask().execute();
}
/**
* 自定義非同步任務類
*/
class PbAsyncTask extends AsyncTask<Void, Integer, Void> {
@Override
protected Void doInBackground(Void... params) {
//使用for迴圈來模擬進度條的進度.
for (int i = 0; i < 100; i++) {
//呼叫publishProgress方法將自動觸發onProgressUpdate方法來進行進度條的更新.
publishProgress(i);
try {
//通過執行緒休眠模擬耗時操作
Thread.sleep(300);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
return null;
}
@Override
protected void onProgressUpdate(Integer... values) {
super.onProgressUpdate(values);
//通過publishProgress方法傳過來的值進行進度條的更新.
mainProgressBar.setProgress(values[0]);
}
}
}
複製程式碼
注意,這裡我們來看下面的圖,我們可以發現當第一次進度條沒有全部完成時,返回重新點選開啟進度條。此時進度條並沒有開始執行,而是等待一段時間後,才開始更新進度。
為什麼呢?因為AsyncTask是基於執行緒池進行實現的,當一個執行緒沒有結束時,後面的執行緒是不能執行的。也就是說必須等到第一個task的for迴圈結束後,才能執行第二個task。
那麼如何解決呢?我們知道,當我們點選BACK鍵時會呼叫Activity的onPause()方法,所以我們可以在Activity的onPause()方法中將正在執行的task標記為cancel狀態,在doInBackground方法中進行非同步處理時判斷是否是cancel狀態來決定是否取消之前的task。
更改後的程式碼如下:
/**
* 用非同步任務模擬進度條的更新
*/
public class ProgressActivity extends AppCompatActivity {
private ProgressBar mainProgressBar;
private PbAsyncTask pbAsyncTask;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_progress);
mainProgressBar = (ProgressBar) findViewById(R.id.pb_main);
//開啟非同步任務
pbAsyncTask = new PbAsyncTask();
pbAsyncTask.execute();
}
@Override
protected void onPause() {
super.onPause();
if (pbAsyncTask != null && pbAsyncTask.getStatus() == AsyncTask.Status.RUNNING) {
//cancel方法只是將對應的AsyncTask標記為cancelt狀態,並不是真正的取消執行緒的執行.
pbAsyncTask.cancel(true);
}
}
/**
* 自定義非同步任務類
*/
class PbAsyncTask extends AsyncTask<Void, Integer, Void> {
@Override
protected Void doInBackground(Void... params) {
//使用for迴圈來模擬進度條的進度.
for (int i = 0; i < 100; i++) {
//如果task是cancel狀態,則終止for迴圈,以進行下個task的執行.
if (isCancelled()) {
break;
}
//呼叫publishProgress方法將自動觸發onProgressUpdate方法來進行進度條的更新.
publishProgress(i);
try {
//通過執行緒休眠模擬耗時操作
Thread.sleep(300);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
return null;
}
@Override
protected void onProgressUpdate(Integer... values) {
super.onProgressUpdate(values);
//通過publishProgress方法傳過來的值進行進度條的更新.
mainProgressBar.setProgress(values[0]);
}
}
}
複製程式碼
效果如下:
已經完美解決了這個問題,在這裡我們要注意,cancel方法只是將對應的AsyncTask標記為cancel狀態,並不是真正的取消執行緒的執行,想要真正取消執行緒,還是需要在doInBackground方法中停止。
##AsyncTask和Handler兩種非同步方式區別
已經看完兩個Demo了,應該對AsyncTask有了很深的理解了。但是想到這裡不得不提出一個疑問,這看起來不就是開啟一個子執行緒嘛,有啥不同的。其實AsyncTask和Handler兩種非同步方式還真有很大的區別。
首先來看AsyncTask,它是Android提供的輕量級的非同步類,可以直接繼承AsyncTask。在類中實現非同步操作,並提供介面來反饋當前非同步執行的程度,也就是所謂的進度更新,最後反饋執行的結果給UI主執行緒。這種方法使用起來簡單快捷,過程清晰明瞭而且便於控制。不足的是在在使用多個非同步操作和並需要進行Ui變更時,就變得複雜起來,程式碼也看起來比較臃腫。
其次是Handler,在本文開頭的時候,就說了非同步訊息處理機制。它是通過Handler, Looper, Message,Thread四個物件之間的聯絡來進行處理訊息的。這種方式在功能上比較清晰,有多個後臺任務的時候程式碼看起來比較有序。
所以說在實際開發過程中,根據需要來選擇非同步任務處理方式,就我個人而言,還是Handler方式用的比較多。
好了,本文基本技術了。由於我技術水平有限,如有錯誤或不同意見,歡迎指正與交流。
##優秀資料來源
Android必學之AsyncTask - caobotao
AsyncTask和Handler兩種非同步方式的實現和區別比較
##專案原始碼
Github - AsyncTaskDemo - IamXiaRui
個人部落格:www.iamxiarui.com 原文連結:http://www.iamxiarui.com/?p=699