Android每天一個知識點+Demo—非同步訊息機制實現
一 前言
Handler的由來
(1)首先為什麼需要Handler呢? 因為UI更新只能在UI執行緒。 (2)那為什麼只能在UI執行緒更新UI呢? 因為Android是單執行緒模型。 (3)那為什麼Android是單執行緒模型? 因為如果任意執行緒都可以更新UI的話,執行緒安全問題處理起來會相當麻煩複雜,所以就規定了Android是單執行緒模型,只允許在UI執行緒更新UI操作。
二 非同步訊息的方式與Demo
1 handler+Message+Looper
Android在設計的時候,就封裝了一套訊息建立、傳遞、處理機制,如果不遵循這樣的機制就沒有辦法更新UI資訊,就會丟擲異常。 Handler就是是Android提供的用來更新UI的一套機制,也是一套訊息處理機制,我們可以通過它傳送訊息,也可以通過它處理訊息。
(1) 相關介紹
Handler:
Handler 顧名思義也就是處理者的意思,它主要是用於傳送和處理訊息的。傳送訊息一般是使用 Handler 的 sendMessage()方法,而發出的訊息經過一系列地輾轉處理後,最終會傳遞到 Handler 的 handlerMessage()方法中。
Message:
Message 是線上程之間傳遞的訊息,它可以在內部攜帶少量的資訊,用於在不同執行緒之間交換資料。通常使用 Message 的 what 欄位攜帶命令,除此之外還可以使用 arg1 和arg2 欄位來攜帶一些整形資料,使用 obj 欄位攜帶一個 Object 物件。
MessageQueue:
MessageQueue 是訊息佇列的意思,它主要用於存放所有通過 Handler 傳送的訊息。這部分訊息會一直存在於訊息佇列中,等待被處理。每個執行緒中只會有一個 MessageQueue 物件。
Looper:
Looper 是每個執行緒中的 MessageQueue 的管家,呼叫 Looper 的 loop() 方法後,就會進入到一個無限迴圈當中,然後每當發現 MessageQueue 中存在一條訊息,就會將它取出,並傳遞到 Handler 的 handleMessage() 方法中。每個執行緒中也只會有一個 Looper 物件。
(2) 執行機制流程
首先需要在主執行緒當中建立一個Handler物件,並重寫handleMessage()方法。
然後當子執行緒中需要進行UI操作的時,就建立一個Message物件,並通過Handler將這條訊息傳送出去。
之後這條訊息會被新增到MessageQueue的佇列中等待被處理,而Looper則會一直嘗試從MesssageQueue中取出待處理訊息,最後發回Handler的HandleMessage()方法中。
由於Handler是在主執行緒中建立的,所以此時handleMessage()方法中的內碼表會在主執行緒中執行,於是我們在這裡就可以進行UI操作了。
(3) Demo
package com.demo.handler;
import android.app.Activity;
import android.os.Bundle;
import android.os.Handler;
import android.os.Message;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.Button;
import android.widget.TextView;
public class MainActivity extends Activity implements OnClickListener {
protected static final int UPDATE_TEXT = 1;
private Button button;
private TextView textView;
//建立handler物件,並重寫父類handleMessage方法
private Handler handler = new Handler(){
//處理傳遞過來的訊息
public void handleMessage(android.os.Message msg) {
switch (msg.what) {
case UPDATE_TEXT:
//在主執行緒裡更新UI操作
textView.setText("我要更新了");
break;
}
};
};
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
button = (Button) findViewById(R.id.btn);
textView = (TextView) findViewById(R.id.text);
button.setOnClickListener(this);
}
@Override
public void onClick(View v) {
switch (v.getId()) {
case R.id.btn:
new Thread(new Runnable() {
@Override
public void run() {
Message message = new Message();
message.what = UPDATE_TEXT;
handler.sendMessage(message);//將message物件傳送出去
}
}).start();
break;
}
}
}
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical" >
<Button
android:id="@+id/btn"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="點選修改內容" />
<TextView
android:id="@+id/text"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="我要被改動了" />
</LinearLayout>
(4) 其它
另外除了傳送訊息之外,我們還有以下幾種方法可以在子執行緒中進行UI操作:
Handler的post(Runnable)方法
View的post(Runnable)方法
Activity的runOnUiThread()方法
2 AsyncTask
(1) AsyncTask介紹
AsyncTask是由執行緒池 + 工作執行緒 + Handler進行構建的一個非同步任務工具類。
AsyncTask本質上是一個封裝了執行緒池、工作執行緒和Handler的非同步框架,主要是執行非同步任務,由於AsyncTask裡面封裝了Handler,所以它可以在主執行緒和子執行緒之間靈活的切換。
AsyncTask 背後的實現原理也是基於非同步訊息處理機制的,只是 Android 做了很好的封裝而已。我們並不需要去考慮非同步訊息處理機制,也不需要專門使用一個 Handler 來發送和接收訊息,只需要呼叫一下 publishProgress()方法就可以輕鬆地從子執行緒切換到 UI 執行緒了。
(2) AsyncTask的機制原理
1) 執行緒池:
為了在處理多個任務時,避免建立多個執行緒帶來記憶體開銷,內部維護了一個執行緒池,管理每個執行緒。內部的執行緒池通過ThreadPoolExecutor來動態分配固定大小的核心工作執行緒。這樣所有的非同步任務都會放到執行緒池,並維護每個工作執行緒。
2) 工作執行緒:
該執行緒用於執行一個耗時的操作,內部通過FutureTask接收一個WorkerRunnable介面,並在介面的call方法中,執行非同步任務,此時呼叫AsyncTask類的doInBackground()方法,做耗時操作。由於FutureTask類實現了FutureRunnable介面,通過這個介面,在它的run方法中,執行任務。
3) Handler訊息機制更新UI:
當FutureTask工作執行緒任務執行完成以後,把結果交給Handler,Handler通過傳送訊息,在主執行緒中接收訊息,並呼叫AsyncTask類的onPostExecute(Result result)方法,來處理結果。
(3) AsyncTask 的基本用法
首先來看一下 ,由於 AsyncTask 是一個抽象類,所以如果我們想使用它,就必須建立一個子類去繼承它。在繼承時我們可以為 AsyncTask 類指定三個泛型引數。
三個引數
Params:啟動任務執行的輸入引數,比如HTTP請求的URL。 Progress:後臺任務執行的百分比。 Result:後臺任務執行最終返回的結果,比如String,Integer等。
/**
* AsyncTask泛型引數解釋
* Void 表示在執行AsyncTask的時候不需要傳入引數給後臺任務
* Integer 表示使用整形資料來作為進度顯示單位
* Boolean 表示使用布林型別資料來反饋執行結果
*/
class MyTask extends AsyncTask<Void, Integer, Boolean> {
@Override
protected Boolean doInBackground(Void... params) {
return null;
}
}
四個方法
onPreExecute():一般會在 UI Thread 中執行。用於進行一些介面上的初始化操作,比如顯示一個進度條對話方塊等。
doInBackground(Params…):這個方法中的所有程式碼都會在子執行緒 Worker Thread 中執行,我們應該在這裡去處理所有的耗時任務。任務一旦完成就可以通過return語句來將任務的執行結果進行返回,如果AsyncTask的第三個泛型引數指定的是Void,就可以不返回任務執行結果。注意,在這個方法中是不可以進行UI操作的,如果需要更新UI元素,比如說反饋當前任務的執行進度,可以呼叫下面這個 publishProgress(Progress...) 方法來完成。
onProgressUpdate(Progress…):當在後臺任務中呼叫了publishProgress(Progress…)方法後,這個方法就很快會被呼叫,方法中攜帶的引數就是在後臺任務中傳遞過來的。在這個方法中可以對UI進行操作,利用引數中的數值就可以對介面元素進行相應的更新。
onPostExecute(Result):在 UI Thread 中執行。當後臺任務執行完畢並通過return語句進行返回時,這個方法就很快會被呼叫。返回的資料會作為引數傳遞到此方法中,可以利用返回的資料來進行一些UI操作,比如彈出Toast提醒任務執行的結果,以及關閉掉進度條對話方塊等。
可以知道,使用 AsyncTask ,則可以在onPreExecute()進行一些介面上的初始化操作,在 doInBackground() 方法中去執行具體的耗時任務,在 onProgressUpdate() 方法中進行 UI 操作,在 onPostExecute()方法中執行一些任務的收尾工作。
而如果想要啟動這個任務,則只需要 new MyTask.execute();
(4) AsyncTask注意事項
1)記憶體洩漏(類似handler,只是AsyncTask銷燬方法為cancel())
2)生命週期(不會隨著Activity的銷燬而銷燬,Activity銷燬前若doInBackground()中耗時未進行完會出現問題)
3)並行or序列(一般做序列,並行不穩定)
4)但是AsyncTask並不適合進行特別耗時的後臺任務,對於特別耗時的任務來說,建議使用執行緒池
5)AsyncTask的例項必須在UI thread中建立,啟動例項的execute方法必須在UI thread中呼叫
6)一個AsyncTask例項只能被執行一次,多次呼叫時將會出現異常
(5) Demo
public class MyActivity extends Activity
{
private Button btn;
private TextView tv;
@Override
public void onCreate(Bundle savedInstanceState)
{
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
btn = (Button) findViewById(R.id.start_btn);
tv = (TextView) findViewById(R.id.content);
btn.setOnClickListener(new Button.OnClickListener(){
public void onClick(View v) {
update();
}
});
}
private void update(){
UpdateTextTask updateTextTask = new UpdateTextTask(this);
updateTextTask.execute();
}
class UpdateTextTask extends AsyncTask<Void,Integer,Integer>{
private Context context;
UpdateTextTask(Context context) {
this.context = context;
}
/**
* 執行在UI執行緒中,在呼叫doInBackground()之前執行
*/
@Override
protected void onPreExecute() {
Toast.makeText(context,"開始執行",Toast.LENGTH_SHORT).show();
}
/**
* 後臺執行的方法,可以執行非UI執行緒,可以執行耗時的方法
*/
@Override
protected Integer doInBackground(Void... params) {
int i=0;
while(i<10){
i++;
publishProgress(i);
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
}
}
return null;
}
/**
* 執行在ui執行緒中,在doInBackground()執行完畢後執行
*/
@Override
protected void onPostExecute(Integer integer) {
Toast.makeText(context,"執行完畢",Toast.LENGTH_SHORT).show();
}
/**
* 在publishProgress()被呼叫以後執行,publishProgress()用於更新進度
*/
@Override
protected void onProgressUpdate(Integer... values) {
tv.setText(""+values[0]);
}
}
}
3 HandlerThread
(1) HandlerThread 介紹
HandlerThread是Google幫我們封裝好的框架,可以用來執行多個耗時操作,而不需要多次開啟執行緒。其內部實現為:
Handle + Thread + Loop
HandlerThread 繼承於 Thread,所以它本質就是個 Thread。與普通的 Thread 的差別就在於,它有個 Looper 成員變數。這個 Looper 其實就是對訊息佇列以及佇列處理邏輯的封裝。與普通執行緒不同的地方就是內部有loop。因為我們如果想在子執行緒中使用Handle,就必須在子執行緒中建立loop物件。 使用Looper.prepare();
來建立當前執行緒的loop,接著使用 Looper.loop();
來開啟迴圈,這樣我們才能線上程中建立Handle。而Handle的內部就是這樣實現的。
HandlerThread集合了Thread和Handler,集成了Looper和MessageQueue。當啟動HandlerThread時,會同時生成Looper和MessageQueue,然後等待訊息處理。它只是幫你呼叫了 Looper.prepare()
方法和 Looper.loop()
方法而已。也就是說如果你一個類繼承了 HandlerThread,你可以像在主執行緒那樣使用 Handler。
(2) HandleThread 使用
1) 建立HandlerThread的例項物件
HandlerThread handlerThread = new HandlerThread("myHandlerThread");
2) 啟動我們建立的HandlerThread執行緒
handlerThread.start();
3) 將我們的handlerThread與Handler繫結在一起,就是將執行緒的looper與Handler繫結在一起,程式碼如下:
mThreadHandler = new Handler(mHandlerThread.getLooper()) {
@Override
public void handleMessage(Message msg) {
checkForUpdate();
if(isUpdate){
mThreadHandler.sendEmptyMessage(MSG_UPDATE_INFO);
}
}
};
(3) HandlerThread 使用場景
HandlerThread 所做的就是在新開的子執行緒中建立了 Looper,那它的使用場景就是 Thread + Looper 使用場景的結合,即:在子執行緒中執行耗時的、可能有多個任務的操作。比如說多個網路請求操作,或者多檔案 I/O 等等。使用 HandlerThread 的典型例子就是 IntentService
。
當我們需要一個工作執行緒,而不是把它當作一次性消耗品,用過即廢的話,就可以使用它。
(4) HandlerThread
特點
1)它就是一個幫我們建立 Looper 的執行緒,讓我們可以直接線上程中使用 Handler 來處理非同步任務。HandlerThread 繼承自Thread,內部封裝了Looper。
2)首先Handler和HandlerThread的主要區別是Handler與Activity在同一個執行緒中,HandlerThread與Activity不在同一個執行緒,而是別外新的執行緒中(Handler中不能做耗時的操作)。 3)通過將HandleThread的Loop物件傳遞給Handle物件,可以在handleMessage()中執行非同步任務。 4)與執行緒池併發不同,HandleThread內部只有一個執行緒,是一個序列的佇列。 5)優點是不會堵塞,減少了對效能的消耗。缺點是不能同時進行多工處理,需等待處理,效率較低。
(5) Demo
import android.app.Activity;
import android.content.Intent;
import android.os.Bundle;
import android.os.Handler;
import android.os.HandlerThread;
import android.os.Looper;
import android.os.Message;
/**
* 在主執行緒main中建立handler物件,建立訊息,並用handler向工作執行緒傳送訊息,在工作執行緒中處理訊息;
* 應用環境:比如下載;
* @author gaohong
*
*/
public class HandlerThreadActivity extends Activity {
HandlerThread thread;
/** Called when the activity is first created. */
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
//建立工作執行緒並啟動(工作執行緒帶訊息佇列)
thread=new HandlerThread("workThread");
thread.start();
Looper looper=thread.getLooper();
//建立Handler物件並與工作執行緒的訊息佇列關聯
Handler handler=new Handler(looper){
@Override
public void handleMessage(Message msg) {
// TODO Auto-generated method stub
System.out.println("在"+Thread.currentThread().getName()+"中處理訊息");
System.out.println("msg.obj="+msg.obj);
}
};
//建立Message物件
Message msg=Message.obtain();
msg.obj="在"+Thread.currentThread().getName()+"執行緒中傳送訊息";
handler.sendMessage(msg);
}
@Override
protected void onDestroy() {
// TODO Auto-generated method stub
super.onDestroy();
thread.quit();
}
}
4 IntentService
(1) IntentService介紹
Service是作為後臺服務執行再程式中的。但是Service他依然是執行在主執行緒中的,所以我們依然不能在Service中進行耗時操作。所以當我們在Service處理時,我們需要在Service中開啟一個子執行緒,並且在子執行緒中執行。當然為了簡化我們的操作,在Android中為我們提供了IntentService來進行這一處理。
IntentService具有Service一樣的生命週期,也提供了後臺執行緒中的非同步處理機制。IntentService可以看做是Service和HandlerThread的結合,完成任務後自動停止,適合需要在工作執行緒處理ui無關任務的場景。
IntentServie繼承自Service並處理非同步請求的抽象類,用於執行後臺耗時任務,優先順序比單純的執行緒高很多,適合執行一些高優先順序任務,不容易被系統殺死,內部封裝了HanlerThread和Handler。 封裝的一個work-thread,處理接收到的Intent,它可以處理多個請求,但一次只能處理一個。如果啟動IntentService多次,每一個耗時操作會以工作佇列的方式在inentService的onHandleIntent回撥方法中執行,依次執行,使用序列方式,執行完自動結束。
(2) IntentServie特點 1) IntentService會自動啟動一個WorkThread來處理後臺耗時任務,它可以用於在後臺執行耗時的非同步任務,當任務完成後會自動停止。 2) 後臺耗時任務處理完成以後,會自動關閉Service(這樣只需要啟動IntentService即可,後面就不用關注IntentService的關閉了)
3) 擁有較高的優先順序,不易被系統殺死(繼承自Service的緣故),因此比較適合執行一些高優先順序的非同步任務
4) 內部通過HandlerThread和Handler實現非同步操作建立IntentService時,只需實現onHandleIntent和構造方法,onHandleIntent為非同步方法,可以執行耗時操作。
5) 當然IntentService也有其限制,多個任務會放到任務佇列,順序執行。所以任務會有先後順序的限制,如果要幾個任務同時併發處理,就不適合使用IntentService了
(3) Demo
public class MyIntentService extends IntentService {
private static final String TAG = MyIntentService.class.getSimpleName();
private boolean isRunning;
private int count;
public MyIntentService() {
super(TAG);
}
@Override
public IBinder onBind(Intent intent) {
// TODO: Return the communication channel to the service.
throw new UnsupportedOperationException("Not yet implemented");
}
@Override
protected void onHandleIntent(@Nullable Intent intent) {
String action = intent.getStringExtra("task_action");
Log.e(TAG, "onHandleIntent: " + action);
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
if (action.equals("task_action111")) {
Log.e(TAG, "onHandleIntent: " + action);
}
}
@Override
public void onDestroy() {
Log.e(TAG, "onDestroy: ");
super.onDestroy();
}
}
三 總結
(1) 它們之間的關係
(2) 它們之間的區別
Handler: 它主要有兩個用途,一是為了在將來某個時間點處理一個訊息或者執行一個任務;二是將一個任務放入佇列,以便它可以在另外的執行緒中執行。
AsyncTask: 封裝了執行緒池和Handler,為 UI 執行緒與工作執行緒之間進行快速切換提供一種便捷機制。適用於當下立即需要啟動,但是非同步執行的生命週期短暫的使用場景。這個類的設計目的很明確,就是為了“執行一個較為耗時的非同步任務(最多幾秒鐘),然後更新介面”。
Handler + 執行緒池 + 工作執行緒
HandlerThread: 一個已經擁有了Looper的執行緒類,內部可以直接使用Handler。為某些回撥方法或者等待某些任務的執行設定一個專屬的執行緒,並提供執行緒任務的排程機制。使用場景就是 Thread + Looper 使用場景的結合,即在子執行緒中執行耗時的、可能有多個任務的操作。比如說多個網路請求操作,或者多檔案 I/O 等等。
Handler + Thread + Loop
IntentService: 適合於執行由 UI 觸發的後臺 Service 任務,並可以把後臺任務執行的情況通過一定的機制反饋給 UI。提供了後臺執行緒中的非同步處理機制,IntentService可以看做是Service和HandlerThread的結合,完成任務後自動停止,適合需要在工作執行緒處理ui無關任務的場景。
HandlerThread+ Service