Android學習筆記11-Service後臺服務(2)
Android學習筆記11-Service後臺服務(二)-非同步訊息處理機制和AsyncTask
一,訊息機制的簡介
在Android中使用訊息機制,首先想到的是Handler,Handler是Android訊息機制的上層介面,Handler的使用方法很簡單,通過它可以把一個任務切換到Handler所在的執行緒中去執行,通常,Handler的使用場景就是更新UI。
如在上一章中所用的例子
public class MainActivity extends AppCompatActivity implements View.OnClickListener {
private TextView text;
public static final int UPDATE_TEXT = 1;
private Handler handler = new Handler() {
@Override
public void handleMessage(Message msg) {
switch (msg.what) {
case UPDATE_TEXT:
//在這裡可以進行UI操作
text. setText("Nice to meet you");
break;
default:break;
}
}
};
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
//宣告控制元件
Button changeText = (Button) findViewById(R.id.change_text);
text = (TextView) findViewById(R.id.text);
//設定點選監聽器
changeText.setOnClickListener(this);
}
/**
* 設定點選事件
*/
@Override
public void onClick(View v) {
switch (v.getId()) {
case R.id.change_text:
Message message = new Message();
message.what = UPDATE_TEXT;
handler.sendMessage(message);
break;
default:break;
}
}
}
在子執行緒中,進行耗時操作,執行完操作後,傳送訊息,通知主執行緒更新UI,這便是Android訊息機制的典型應用場景.
二,訊息機制的模型
訊息機制主要包含
-
Message:需要傳遞的訊息,可以傳遞資料(what,arg1,agr2,obj)
-
MessageQueue: 訊息佇列。但是它的內部實現結構採用的的單鏈表的形式,因為單鏈表在插入和刪除的操作上比較有優勢。它的主要功能是通過MessageQueue.enqueueMessage 方法向訊息池中投遞訊息,和用 MessageQueue.next 方法取走訊息池中的訊息。每個執行緒只有一個MessageQueue物件
-
Handler:訊息輔助類,主要功能是向訊息池 傳送訊息事件(Handler.sendMessage)和處理相應的訊息事件(Handler.handleMessage)
-
Looper:它是每個執行緒中的MessageQueue的管家,呼叫Looper.loop()方法,就會進入無限迴圈中,每當發現MessageQueue中存在訊息,就會將它取出,傳遞到Hanler.handleMessage() 方法中,每個執行緒只有一個Looper物件.
非同步訊息處理的流程:
首先我們在主執行緒中建立一個Handler物件,並重寫它的HandlerMessage方法.當我們需要更改UI時,我們建立一個Message物件,並通過Handler的sendMessage(message) 方法將訊息傳送出去,之後這條訊息會被新增到MessageQueue的佇列中等待被處理,這時Looper會一直嘗試從MessageQueue中取出訊息,最後分發回給handleMessage方法中,由於Handler物件是在主執行緒中建立的,所以handleMessage也會執行在主執行緒中。我們就可以安心的進行UI操作了。
三,使用AsyncTask
AsyncTask是一個抽象類,他是由Android封裝的一個輕量級非同步類(輕量體現在使用方便,程式碼簡潔),它可以線上程池中執行後臺任務,然後把執行的進度和最終的結果傳遞給主執行緒中並在主執行緒中更新UI。
AsyncTask的內部封裝了兩個執行緒池:
- SerialExecutor :用於任務的排隊,讓多個需要執行的耗時任務,按順序排列。
- THREAD_POOL_EXECUTOR :執行任務
還封裝了一個Handler(InternalHandler) 用於從工作執行緒切換到主執行緒。
1,AsyncTask的泛型引數
AsyncTask的類宣告如下
public abstract class AsyncTask<Params, Progress, Result>
AsyncTask是一個泛型類,其中,三個泛型型別的引數的含義如下:
- Params: 開始非同步任務執行時傳入的引數型別。
- Progress: 後臺任務執行時,如果需要在介面上顯示當前的進度,則使用這裡指定的泛型作為單位。
- Result: 當任務執行完畢後,如果需要對結果進行返回,則使用這裡指定的泛型作為返回值型別。
比如自定義一個AsyncTask就可以寫成如下形式:
class DownloadTask extends AsyncTask<Void,Integer,Boolean>
如果AsyncTask確定不需要傳遞具體引數,那麼這三個泛型引數可以用Void代替。
有了這三個引數型別之後,也就控制了這個AsyncTask子類各個階段返回的型別,如果有不同的業務,我們就需要再另寫一個AsyncTask的子類進行處理。
2,AsyncTask的核心方法
-
onPreExecute()
這個方法會在後臺任務開始執行之間呼叫,在主執行緒中呼叫,用於介面上一些初始化的操作,比如要顯示一個進度條對話方塊. -
doInBackground(Params…)
這個方法的所有程式碼都會在子執行緒中執行,我們應該在這個方法中去處理耗時任務。
任務一旦完成就可以通過return語句來將任務的執行結果進行返回,如果AsyncTask的第三個泛型引數指定的是Void,就可以不返回任務執行結果
注意**在這個方法中不可以對UI進行操作,如果需要更新UI元素,比如說反饋當前任務的執行進度,可以呼叫publishProgress(Progress…)**方法。 -
onProgrssUpdate(Progress…)
當在後臺呼叫了 publishProgress(Progress) 方法後,這個方法很快會被呼叫,方法中攜帶的引數是後臺任務中傳遞過來的 在這個方法中可以對UI進行操作,在主執行緒中進行 ,利用引數中的數值就可以對介面元素進行相應的更新。 -
onPostExecute(Result)
當doInBackground(Params…) 執行完畢並通過return語句進行返回時,這個方法就會很快被呼叫,引數中的Result型別與doInBackground方法的返回值有關。 可以利用這個返回的資料進行一些UI操作。 在主執行緒中執行,比如提醒任務的執行結果,以及關閉掉進度條的對話方塊。
上面幾個方法的呼叫順序依次是:
onPreExecute() -> doInBackground(Params…) -> publishProgress(Progress…) -> onProgrssUpdate(Progress…) -> onPostExecute(Result)
如果不需要執行更新進度則為 onPreExecute() -> doInBackground(Params…) -> onPostExecute(Result)
除了上面的方法,AsyncTask還提供了onCancelled() 方法,它同樣在主執行緒中執行,當非同步任務取消時,onCancelled() 會被呼叫,這個時候onPostExecute() 則不會被呼叫,但是要注意的是,AsyncTask中的cancel()方法並不是真正去取消任務,只是設定這個任務為取消狀態 ,我們需要在doInBackground() 判斷終止任務。就好比想要終止一個執行緒,呼叫interrupt()方法,只是進行標記為中斷,需要線上程內部進行標記判斷然後中斷執行緒。
package com.example.chen.androidthread;
import android.os.AsyncTask;
import android.widget.Toast;
public class DownloadTask extends AsyncTask <Void,Integer,Boolean>{
@Override
protected void onPreExecute() {
progressDialog.show();
}
@Override
protected Boolean doInBackground(Void... params) {
try {
while (true) {
//這個download()函式時虛構的一個返回下載進度的函式。
int downloadPercent = doDownload();
publishProgress(downloadPercent);
if (downloadPercent >= 100) {
break;
}
}
} catch (Exception e) {
return false;
}
return true;
}
@Override
protected void onProgressUpdate(Integer... values) {
progressDialog.setMessage("當前下載進度:" + values[0] + " %");
}
@Override
protected void onPostExecute(Boolean result) {
progressDialog.dismiss();
if (result) {
Toast.makeText(context, "下載成功", Toast.LENGTH_SHORT).show();
} else {
Toast.makeText(context, "下載失敗", Toast.LENGTH_SHORT).show();
}
}
}
如果想要啟動這個任務,只需要簡單的呼叫
new DownloadTask().execute()方法。
在AsyncTask中,我們不需要Handler來發送和接收訊息,只需要呼叫一個publishProgress()方法,就能輕鬆的從子執行緒切換到UI執行緒(主執行緒)了.