【Android學習】訊息機制Handler
0,概念
1)訊息機制
Handler是Android訊息機制的上層介面。
2)Handler、MessaegQueue和Loop
一個執行緒有一個Looper,一個MessageQueue。可以有很多個Handler,傳送各自的Message到這個MessageQueue中。
3)場景
①更新UI
只有主執行緒可以訪問UI。
②處理訊息
③將任務(耗時任務)切換到某個指定的執行緒中執行
2,角色
1)Message(訊息)
分為硬體產生的訊息(例如:按鈕、觸控)和軟體產生的訊息。
2)MessageQueue(訊息佇列)
①概念
Android啟動程式時會建立一個MessageQueue。主要用來向訊息池新增訊息和取走訊息。
②資料結構
單鏈表
③功能
訊息的儲存單元,不能處理訊息。
④工作原理
MessageQueue有兩個操作:插入和讀取。
enqueueMessage(插入)
向佇列中插入一條訊息。
next(讀取)
從訊息佇列讀取資料並移除。
3)Looper(訊息迴圈器)
①概念
一個執行緒可以產生一個Looper物件,由它來管理此執行緒裡的Message Queue(訊息佇列)。主要用來把訊息分發給相應的處理者。
執行緒預設沒有Looper,使用時需要建立Looper。
ActivityThread(主執行緒)被建立時會初始化Looper,故主執行緒中預設可以使用Looper。
以無限迴圈的形式查詢是否有新訊息,如果有就處理,否則一直等待。
注意:Looper執行在建立Handler所在的執行緒中。
②ThreadLocal
不是執行緒,是一個執行緒內部的資料儲存類,作用是可以在每個執行緒中儲存資料。
ThreadLocal可以在不同的執行緒中互不干擾的儲存並提供資料,Handler通過ThreadLocal可以輕鬆獲取每個執行緒的Looper。
即時是用同一個ThreadLocal的get方法,ThreadLocal內部會從各自的執行緒中去取各自的陣列,資料之前是互不干擾的。
使用場景:
一、當某些資料是以執行緒為作用域並且不同執行緒具有不同的資料副本。
二、複雜邏輯下的物件的傳遞,如監聽器的傳遞。
採用ThreadLocal可以讓監聽器作為執行緒內的全域性物件而存在,線上程內部只要通過get方法就可以獲取到監聽器。
③實現
i>建立Looper
一、Looper.prepare(),通過Looper.loop()開啟訊息迴圈。
loop方法是一個死迴圈,唯一跳出方式:MessageQueue的next方法返回null。
二、prepareMainLooper方法,這個方法是給主執行緒建立Looper使用。
通過getMainLooper方法,可以在任何地方獲取到主執行緒的Looper。
ii>退出Looper
建議不需要的時候,終止Looper。
一、quit
直接退出Looper。
二、quitSafely
設定退出標記,然後吧訊息佇列中的已有訊息處理完才安全退出。
④Can’t create handler inside thread that has not called Looper.prepare()
Looper類用來為一個子執行緒開啟一個訊息迴圈。預設情況下Android中新誕生的非主執行緒沒有開啟訊息迴圈(主執行緒誕生時系統會自動為其建立開啟訊息迴圈的Looper物件),對於非主執行緒需要先呼叫Looper.prepare()啟用Looper,然後通過呼叫。
問題來自於在AsyncTask的onPostExecute(Integer result)方法中,即非主執行緒中直接new Handler。
除了啟用Looper,另一種解決方式:
i>定義一個介面
public interface UIListener {
/**
* 獲得字串,更新UI
* @param type UI更新型別
* @param msg UI更新資訊
*/
public void onEvent(int type, String msg);
}
ii>I更新層,實現介面。
在onEvent方法具體實現,傳送訊息給handle,由handle執行toast。
@Override
public void onEvent(int type, String msg) {
switch (type) {
case UIType.showCustomToast:
dismissLoadingDialog();
Message msg_toast = new Message();
msg_toast.what = 8;
msg_toast.obj = msg;
handler.sendMessage(msg_toast);
break;
default:
break;
}
}
iii>在AsyncTask中,通過呼叫介面的方式,將非同步執行緒的執行結果傳到UI更新層。
@Override
protected void onPostExecute(Integer result) {
super.onPostExecute(result);
if (Result.NETWORK_ERROR == result) {
((MainActivity) mContext).onEvent(UIType.showCustomToast, "網路異常,未進行正常更新");
} else if (Result.SUCCESS == result) {
RefreshData();
}
if (Result.ERROR == result) {
((MainActivity) mContext).onEvent(UIType.showCustomToast, "新任務獲取失敗");
}
}
4)Handler(訊息處理器)
Handler是Android訊息機制的上層介面。
Handler的執行需要底層的MessageQueue和Looper的支撐。
主要向訊息佇列傳送各種訊息以及處理各種訊息。
5)工作原理
①Handler建立時
採用當前執行緒的Looper來構建內部的訊息迴圈系統(如果當前執行緒沒有Looper會報錯,需建立Looper)。
②Handler建立完畢後
通過Handler的post方法將一個Runnable投遞到Handler內部的Looper中處理,也可以通過send方法來完成。
使用handler傳送訊息時有兩種方式,都是將指定Runnable(包裝成PostMessage)加入到MessageQueue中,然後Looper不斷從MessageQueue中讀取Message進行處理。
post(Runnable r)
postDelayed(Runnable r, long delayMillis):可以精確傳遞時間但又不阻塞佇列。
postDelayed一個10秒鐘的Runnable A、訊息進隊,MessageQueue呼叫nativePollOnce()阻塞,Looper阻塞;
緊接著post()一個Runnable B、訊息進隊,判斷現在A時間還沒到、正在阻塞,把B插入訊息佇列的頭部(A的前面),然後呼叫nativeWake()方法喚醒執行緒;
MessageQueue.next()方法被喚醒後,重新開始讀取訊息連結串列,第一個訊息B無延時,直接返回給Looper;
Looper處理完這個訊息再次呼叫next()方法,MessageQueue繼續讀取訊息連結串列,第二個訊息A還沒到時間,計算一下剩餘時間(假如還剩9秒)繼續呼叫nativePollOnce()阻塞;
直到阻塞時間到或者下一次有Message進隊;
這樣,基本上就能保證Handler.postDelayed()釋出的訊息能在相對精確的時間被傳遞給Looper進行處理而又不會阻塞隊列了。
③send方法
post方法最終也是通過send方法來完成的。
當Handler的send方法被呼叫時,它會呼叫MessageQueue的enqueueMessage方法將這個訊息放入訊息佇列中,然後Looper發現有新訊息到來時,就會處理這個訊息,最終訊息中的Runnable或者Handler的handleMessage方法就好被呼叫。
6)訊息流程
①Handler通過sendMessage()傳送訊息Message到訊息佇列MessageQueue。
②Looper通過loop()不斷提取觸發條件的Message,並將Message交給對應的target handler來處理。
③target handler呼叫自身的handleMessage()方法來處理Message。
在整個訊息迴圈的流程中,並不只有Java層參與,很多重要的工作都是在C++層來完成的。
注:虛線表示關聯關係(它們發生關聯的橋樑是MessageQueue),實線表示呼叫關係。
7)Demo
package com.luo.activity;
import android.app.Activity;
import android.os.Bundle;
import android.os.Handler;
import android.os.Message;
import android.widget.Toast;
public class MainActivity extends Activity {
private Handler handler;// 獲取資料變更
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
init();
sendMsg(0, 1);
}
private void init() {
handler = new Handler() {
public void handleMessage(Message msg) {
switch (msg.what) {
case 0:
Toast.makeText(MainActivity.this, "this is handler" + msg.arg1, Toast.LENGTH_LONG).show();
break;
default:
break;
}
}
};
}
public void sendMsg(int flag, int value) {
Message message = new Message();
message.what = flag;
message.arg1 = value;
handler.sendMessage(message);
}
}
3,記憶體洩漏
1)原因
使用內部類、匿名類來建立Handler,這樣會造成記憶體洩露!
點選檢視什麼是記憶體洩漏
原因:當使用內部類(包括匿名類)來建立Handler的時候,Handler物件會隱式地持有一個外部類物件(通常是一個Activity)的引用。
而Handler通常會伴隨著一個耗時的後臺執行緒(例如從網路拉取圖片)一起出現,這個後臺執行緒在任務執行完畢(例如圖片下載完畢)之後,通過訊息機制通知Handler,然後Handler把圖片更新到介面。
如果使用者在網路請求過程中關閉了Activity,正常情況下,Activity不再被使用,它就有可能在GC檢查時被回收掉,但由於這時執行緒尚未執行完,而該執行緒持有Handler的引用,這個Handler又持有Activity的引用,就導致該Activity無法被回收(即記憶體洩露),直到網路請求結束(例如圖片下載完畢)。
2)解決方案
①通過程式邏輯來進行保護(推薦)
i>在關閉Activity的時候停掉你的後臺執行緒。執行緒停掉了,就相當於切斷了Handler和外部連線的線,Activity自然會在合適的時候被回收。
ii>如果你的Handler是被delay的Message持有了引用,那麼使用相應的Handler的removeCallbacks()方法,把訊息物件從訊息佇列移除就行了。
②將Handler宣告為靜態類
靜態類不持有外部類的物件,所以Activity可以隨意被回收。由於Handler不再持有外部類物件的引用,導致程式不允許你在Handler中操作Activity中的物件了。所以你需要在Handler中增加一個對Activity的弱引用(WeakReference),具體實現如下:
static class MyHandler extends Handler {
@Override
public void handleMessage(Message msg) {
mImageView.setImageBitmap(mBitmap);
}
}
static class MyHandler extends Handler {
WeakReference<Activity > mActivityReference;
MyHandler(Activity activity) {
mActivityReference= new WeakReference<Activity>(activity);
}
@Override
public void handleMessage(Message msg) {
final Activity activity = mActivityReference.get();
if (activity != null) {
mImageView.setImageBitmap(mBitmap);
}
}
}