參考文獻:
- Android異步消息處理機制源碼剖析
- Handler全家桶之 —— Handler 源碼解析
- 你真的懂Handler嗎?Handler問答
- android的消息處理機制(圖+源碼分析)——Looper,Handler,Message
1 概述
主線程不能執行耗時操作,因爲會阻塞,在子線程裏進行耗時操作;子線程不能更新UI,用handler發送一個更新UI的消息,handler分發消息,處理消息。
子線程爲何不能訪問UI?
- 源碼角度:當訪問UI時,ViewRootImpl會調用checkThread()方法檢查當前線程是哪個線程,如果不是UI線程會拋出異常;
- 線程安全角度:訪問UI不是線程安全的;
- 訪問UI爲什麼不加鎖:邏輯複雜、效率低;
Handler作用:
- 線程間通信(例如,子線程通知主線程更新UI);
- 執行計劃任務;
下面源碼分析基於Android 8.0;
2 Message 消息
Message消息,是多線程間通信的實體,是Handler發送和處理的對象。Message對象實現了Parcelable接口,說明Message對象支持序列化/反序列化操作。
2.1 屬性
//msg ID
public int what;
//存儲int類型的數據域
public int arg1;
//存儲int類型的數據域
public int arg2;
//存儲Object類型數據域
public Object obj;
//存儲Bundle類型數據域
/*package*/ Bundle data;
/*package*/ static final int FLAG_IN_USE = 1 << 0;
//消息標識,當消息對象進入消息隊列或回收時設置爲FLAG_IN_USE,msg.obtain時設置爲0
/*package*/ int flags;
//處理消息的時間
/*package*/ long when;
//發送和處理消息的Handler
/*package*/ Handler target;
//post的Runnable
/*package*/ Runnable callback;
// 鏈式結構,指向下一個Message對象,用於維護鏈表結構的消息池(消息隊列)
/*package*/ Message next;
//信號量,消息池的加鎖對象
private static final Object sPoolSync = new Object();
//消息池的表頭,由它維護了一個鏈式消息池,當消息被回收的時候,會加入到這個消息池中
private static Message sPool;
//消息池大小
private static int sPoolSize = 0;
//消息池最大容量50,消息隊列的最大容量是50
private static final int MAX_POOL_SIZE = 50;
Message
可傳輸int , Object ,Bundle類型的數據;- 如果你的message只需要攜帶簡單的int,請優先使用
Message.arg1
和Message.arg2
來傳遞信息,這比用Bundle
更省內存; - 擅用
message.what
來標識信息,以便用不同方式處理message; Message
維護了一個全局的消息池(消息隊列),消息隊列最大容量是50;消息被回收後,會放入到消息池中,並將flag
字段設置爲FLAG_IN_USE
;
2.2 靜態obtain()
方法
public static Message obtain() {
synchronized (sPoolSync) {//對消息池加鎖
if (sPool != null) {
Message m = sPool;
sPool = m.next;
m.next = null;
m.flags = 0; // flags設爲0
sPoolSize--;//從鏈表刪除
return m;
}
}
return new Message();//若消息池爲空,直接new
}
obtain
方法用於獲取一個消息對象,如果當前消息池爲空,直接new,否則從消息池頭部取一個消息對象進行復用;
obtain
方法還有好幾個重載方法,但最終都會調用該該無參方法。
2.3 recycle()
方法
//可手動回收消息
public void recycle() {
if (isInUse()) {
if (gCheckRecycle) {
throw new IllegalStateException("This message cannot be recycled because it "
+ "is still in use.");
}
return;
}
recycleUnchecked();
}
//真正回收Message的方法,looper.loop()在從消息隊列取出並處理消息後調用這個方法
void recycleUnchecked() {
flags = FLAG_IN_USE; //修改標記??
//爲了無差別(handler發送的所有消息)複用消息對象,清空所有域
what = 0; arg1 = 0; arg2 = 0;
obj = null; replyTo = null; sendingUid = -1;
when = 0;target = null;callback = null;data = null;
synchronized (sPoolSync) {//將使用完的消息回收後放入消息池,頭插法
if (sPoolSize < MAX_POOL_SIZE) {
next = sPool;
sPool = this;
sPoolSize++;
}
}
}
在Message對象被處理或從消息隊列移除後,可以手動調用recycle()
方法回收消息對象;當然recycleUnchecked()
方法纔是真正回收消息的方法,looper.loop()
在從消息隊列取出並處理消息後調用這個方法進行消息回收;這個方法首先會將flag標記爲FLAG_IN_USE,並把清空所有屬性;並在消息池沒有達到最大限定值的情況下,把這個對象插入消息池的表頭。同樣,在操作消息池的時候需要先對sPoolSync信號量加鎖。
3 MessageQueue消息隊列
MessageQueue
是一個常量類,不允許被繼承;
消息隊列用來存放Handler發送過來的消息,內部通過單鏈表的數據結構來維護消息列表,等待Looper的抽取。
3.1 消息出隊next()
Message next() {
//...
int pendingIdleHandlerCount = -1; // -1 only during first iteration
int nextPollTimeoutMillis = 0;
//死循環從隊列取Message,直到返回一個Message,或者MessageQueue退出
for (;;) {
//...
synchronized (this) {//消息隊列加鎖
// Try to retrieve the next message. Return if found.
final long now = SystemClock.uptimeMillis();
Message prevMsg = null;
Message msg = mMessages;
//取隊頭消息,若該消息不爲空且者是屏障消息(target爲空),則繼續遍歷,直到取到一個異步消息爲止
//屏障消息:target爲空時是屏障消息;用於區分同步消息和異步消息;如果設置了屏障消息,只執行異步消息,不執行同步消息,直到移除了屏障;如果沒設置屏障消息,同步消息和異步消息都執行
if (msg != null && msg.target == null) {
// Stalled by a barrier. Find the next asynchronous message in the queue.
do {
prevMsg = msg;
msg = msg.next;
} while (msg != null && !msg.isAsynchronous());
}
if (msg != null) {
if (now < msg.when) {//如果消息執行時間未到,繼續循環,等待時間到
// Next message is not ready. Set a timeout to wake up when it is ready.
nextPollTimeoutMillis = (int) Math.min(msg.when - now, Integer.MAX_VALUE);
} else {
// Got a message.
mBlocked = false;
if (prevMsg != null) {
//preMsg不空,說明此時隊列頭結點是一個target爲空的屏障消息,同時msg此時是異步消息。
prevMsg.next = msg.next;//直接從鏈表取下該消息
} else {//此時msg是隊列頭結點,直接刪除隊頭即可
mMessages = msg.next;
}
msg.next = null;//斷開next鏈接
if (DEBUG) Log.v(TAG, "Returning message: " + msg);
msg.markInUse();//修改標記
return msg;//取道非空消息退出
}
} else {//隊列爲空,next方法阻塞,繼續循環,等待新消息到來
// No more messages.
nextPollTimeoutMillis = -1;
}
//若消息隊列已退出,返回true退出死循環
if (mQuitting) {
dispose();
return null; //返回null後Looper.loop()方法也會結束循環
}
//...
if (pendingIdleHandlerCount <= 0) {
// No idle handlers to run. Loop and wait some more.
mBlocked = true;//當隊列爲空時,next()方法會阻塞,繼續循環,直到有新消息到達隊列
continue;
}
//...
}
}
next()
方法用於將隊列頭部消息出隊並返回;- 該方法內部有個死循環,如果消息隊列中沒消息,next方法會阻塞,繼續循環,直到取道新消息;如果消息隊列中有消息,先判斷執行時間是否到了,如果時間沒到則等待,繼續循環;如果時間到了就將消息出隊返回;
- 在循環過程中會對消息隊列加鎖,所以該方法是線程安全的;
3.2 消息入隊enqueueMessage()
boolean enqueueMessage(Message msg, long when) {
if (msg.target == null) {//入隊的消息的target,必須不爲空,否則會拋異常
throw new IllegalArgumentException("Message must have a target.");
}
if (msg.isInUse()) {//消息在回收之前,不可以被重複使用
throw new IllegalStateException(msg + " This message is already in use.");
}
synchronized (this) {
if (mQuitting) {//如果消息隊列在退出狀態 ,則直接回收消息,返回false
IllegalStateException e = new IllegalStateException(
msg.target + " sending message to a Handler on a dead thread");
Log.w(TAG, e.getMessage(), e);
msg.recycle();
return false;
}
//把消息標記爲在使用狀態,設置when
msg.markInUse();
msg.when = when;
Message p = mMessages;//此時p是鏈表頭部
boolean needWake;
if (p == null || when == 0 || when < p.when) {
//如果隊列爲空或者when等於0,或者when小於隊頭Message的when,則直接把消息插入隊頭
msg.next = p;
mMessages = msg;
needWake = mBlocked;
} else {
needWake = mBlocked && p.target == null && msg.isAsynchronous();
Message prev;
for (;;) {
prev = p;//prev是p的前驅節點,依次遍歷
p = p.next;
if (p == null || when < p.when) {
break;//當p已經到隊尾或者找到一個節點msg.when < p.when時退出循環
}
if (needWake && p.isAsynchronous()) {
needWake = false;
}
}
//鏈表插入操作,把msg插入到p節點前邊,並把p的前驅節點的next改爲msg
msg.next = p; // invariant: p == prev.next
prev.next = msg;
}
//...
}
return true;//插入成功,返回true
}
- 該方法用於將消息入隊,其實就是單鏈表的插入操作;
- 該方法對鏈表隊列操作時,依然是進行了加鎖同步,所以是線程安全的;
- 隊列是一個(消息執行時間)when升序鏈表,所以插入也必須找到合適的節點進行插入;如果待插入Message不設置when或when=0,直接插入隊列頭部;否則遍歷隊列結點,直到找到第一個大於when的結點,插入到該結點的前面;
4 Looper消息泵
通過Looper.loop()
不斷地從MessageQueue中抽取Message,將消息分發給目標處理者(Handler);
4.1 主要屬性和構造器
//線程本地變量,每個線程有一個獨立的Looper對象,不存在線程安全問題
//如果不調用prepare()方法,sThreadLocal.get()返回null
static final ThreadLocal<Looper> sThreadLocal = new ThreadLocal<Looper>();
private static Looper sMainLooper; // 主線程的Looper,由Looper.class維護
final MessageQueue mQueue;//looper的MessageQueue
final Thread mThread;//(創建)Looper線程
//私有構造器,不允許外部調用
private Looper(boolean quitAllowed) {
mQueue = new MessageQueue(quitAllowed);
mThread = Thread.currentThread();
}
- Looper的構造器是私有的,不能在Looper類外部new Looper,所以在Looper類外部必須調用prepare()方法來創建一個Looper對象;
- Looper內部有一個MessageQueue屬性;
4.2 創建Looper
用Looper.prepare()
創建Looper,而不是new;
//公有方法,開發者只能調用這個方法爲當前線程創建Looper,允許退出
public static void prepare() {
prepare(true);
}
//私有方法,開發者無法調用,同一個線程只允許調用一次prepare(),否則會拋出異常
private static void prepare(boolean quitAllowed) {
if (sThreadLocal.get() != null) {
throw new RuntimeException("Only one Looper may be created per thread");
}
sThreadLocal.set(new Looper(quitAllowed));//將Looper對象設置爲線程本地變量
}
//主線程的Looper初始化,雖然是公有方法,我們無法調用,
//因爲系統啓動的時候已經調用過了,如果再次調用,會拋異常
public static void prepareMainLooper() {
prepare(false);
synchronized(Looper.class) {
if (sMainLooper != null) {
throw new IllegalStateException("The main Looper has already been prepared.");
}
//把得到的主線程Looper賦值給sMainLooper
sMainLooper = myLooper();
}
}
//獲取當前線程的Looper對象
public static@Nullable Looper myLooper() {
return sThreadLocal.get();//獲取線程本地變量
}
- 調用**
Looper.prepare()
爲當前線程創建Looper對象**,並將Looper對象設置爲線程本地變量; - 調用
Looper.myLooper()
方法來獲取當前線程的Looper對象; - 主線程的Looper允許MessageQueue退出,而其他線程不允許;
- 一個線程只能調用一次
Looper.prepare()
方法,否則會拋出異常,所以在prepare()
創建Looper對象之前,應該先調用Looper.myLooper()
方法判斷是否爲空;同時也說明了一個線程只有一個Looper對象; - 主線程的Looper在ActivityThread中的main()方法中創建的,所以主線程不需要手動創建Looper;
//主線程中不需要自己創建Looper
public static void main(String[] args) {
//...
Looper.prepareMainLooper();//爲主線程創建Looper,該方法內部又調用 Looper.prepare()
//...
Looper.loop();//開啓消息輪詢
//...
}
- 另外可以在任何地方調用Looper.getMainLooper();獲取主線程的Looper;
但是子線程就不一樣了,子線程在創建Handler對象前必須手動調用Looper.prepare()
方法創建Looper對象;
//子線程中創建Looper的標準寫法
new Thread(new Runnable() {
@Override
public void run() {
if(Looper.myLooper()==null){//保證一個線程只有一個Looper
Looper.prepare();//創建Looper
}
Handler handler=new Handler();
Looper.loop();//開啓消息輪詢
}
}).start();
4.3 開啓消息輪詢loop()
//代碼省去打印等其他無關邏輯
public static void loop() {
//獲取當前線程的sThreadLocal變量,即Looper對象
final Looper me = myLooper();
//如果當前線程沒有調用過prepare()方法,則me爲null,拋出異常
if (me == null) {
throw new RuntimeException("No Looper; Looper.prepare() wasn't called on this thread.");
}
//從me裏獲得本線程的MessageQueue對象
final MessageQueue queue = me.mQueue;
Binder.clearCallingIdentity();
final long ident = Binder.clearCallingIdentity();
//開啓消息輪詢
for (;;) {
//從消息隊列取消息
Message msg = queue.next(); // 當消息隊列爲空且未退出時,next方法會阻塞
if (msg == null) {
//next返回null表明消息隊列已退出
return;//結束輪詢,loop方法唯一出口
}
try {
//target是Message的Handler,調用它的dispatchMessage()方法來分發消息
msg.target.dispatchMessage(msg);
} finally {
if (traceTag != 0) {
Trace.traceEnd(traceTag);
}
}
final long newIdent = Binder.clearCallingIdentity();
msg.recycleUnchecked();//當消息被處理完後,回收當前消息
}
}
- 通過調用
Looper.loop()
方法開啓消息輪詢;該方法會調用queue.next()
方法從隊列中取頭部消息; - 該方法會循環調用
msg.target.dispatchMessage(msg);
來進行消息分發,分發給消息的target,也就是消息對應的的Handler對象; - 當消息被處理完後,會調用
msg.recycleUnchecked();
回收消息,當前消息放入消息池,以便以後複用; - handler是在它關聯的looper線程(創建Looper對象的線程)中處理消息的;
5 Handler 消息處理器
5.1 主要屬性和構造器
5.1.1 主要屬性
//是否發現潛在的內存泄漏,默認爲false
private static final boolean FIND_POTENTIAL_LEAKS = false;
private static final String TAG = "Handler";
//靜態全局變量,主線程的Handler
private static Handler MAIN_THREAD_HANDLER = null;
//綁定的Looper對象
final Looper mLooper;
//綁定的MessageQueue消息隊列,通過looper獲取
final MessageQueue mQueue;
//回調接口
final Callback mCallback;
//是否是異步的,如果是異步的,在發送消息的時候,
//會調用Message.setAsynchronous(true)把消息設爲異步消息
final boolean mAsynchronous;
- 由此可見一個Handler持有一個Looper類型的屬性;即一個Handler對應一個唯一的Looper;而對應的MessageQueue消息隊列,通過Looper屬性獲取;
5.1.2 構造器
public Handler(Callback callback, boolean async) {
if (FIND_POTENTIAL_LEAKS) {
//如果爲true,如果Handler實現類是匿名類或內部類或非static類,會給出警告,告知開發者存在內存泄漏的風險
final Class<? extends Handler> klass = getClass();
if ((klass.isAnonymousClass() || klass.isMemberClass() || klass.isLocalClass()) &&
(klass.getModifiers() & Modifier.STATIC) == 0) {
Log.w(TAG, "The following Handler class should be static or leaks might occur: " +
klass.getCanonicalName());
}
}
//獲取當前線程的線程本地變量,即Looper對象;
mLooper = Looper.myLooper();
if (mLooper == null) {//創建Handler對象前要調用Looper.prepare
throw new RuntimeException(
"Can't create handler inside thread that has not called Looper.prepare()");
}
//mQueue直接拿Looper裏的MessageQueue類型的引用對象
mQueue = mLooper.mQueue;
mCallback = callback;
mAsynchronous = async;
}
//該構造器可爲當前Handler指定Looper對象,所以Handler對象和Looper對象不一定是在同一線程創建的
public Handler(Looper looper, Callback callback, boolean async) {
mLooper = looper;
mQueue = looper.mQueue;
mCallback = callback;
mAsynchronous = async;
}
public Handler() {this(null, false);}
public Handler(Callback callback) {this(callback, false);}
public Handler(Looper looper) {this(looper, null, false);}
-
Handler對應的MessageQueue對象來自其關聯的Looper對象;
-
其他構造方法都會調用前面倆構造器中一個;早常用的構造器是
Handler()
和Handler(Callback)
; -
Handler(Looper looper)
用於爲當前Handler指定關聯的Looper對象; -
Handler關聯的Looper對象既可以來自當前線程(創建Handler實例的線程)的本地變量,也可以在構造器裏指定;前面一種情況,Handler和Looper是在同一線程裏實例化的,後面一種情況不一定;
5.1.3obtainMessage
public final Message obtainMessage() {
return Message.obtain(this);
}
使用該方法獲取handler當前處理的Message;Handler中有一系列obtanMessage()重載方法,最後調用的還是該方法;需要注意的是,在Message中,obtain()方法是靜態方法,在Handler中,是非靜態的,需要通過具體的Handler實例對象來獲得,但是禁止子類進行覆寫;
5.2 發送消息
在Handler中,可以發送一個Runnable對象,也可以發送一個Message對象;通過sendMessage(Message)
方式發送一個Message對象;通過post(Runnable)
方式發送一個Runnable對象,這個Runnable對象最終也會被包裝成一個Message對象發送;
5.2.1 post
方式
//立即post一個Runnable對象到MessageQueue中,此時Runnable對象被包裝成Message後入隊(when == 當前系統時間,可能是隊頭,也可能不是隊頭,隊列中已經有when值小於當前時間的Message)
public final boolean post(Runnable r) {
return sendMessageDelayed(getPostMessage(r), 0);
}
//Runnable包裝成Message後加入到MessageQueue中,但此時
//Message.when=uptimeMillis,uptimeMills是消息的執行時間,
public final boolean postAtTime(Runnable r, long uptimeMillis) {
return sendMessageAtTime(getPostMessage(r), uptimeMillis);
}
//同上,只是又傳入了token對象,存儲在Message.obj中
public final boolean postAtTime(Runnable r, Object token, long uptimeMillis) {
return sendMessageAtTime(getPostMessage(r, token), uptimeMillis);
}
//Runnable包裝成Message後加入到MessageQueue中,
//但是Message.when = now + delayMillis,
//表示延遲delayMills後執行
public final boolean postDelayed(Runnable r, long delayMillis) {
return sendMessageDelayed(getPostMessage(r), delayMillis);
}
//Runnable包裝成Message後加入到MessageQueue中,此時when=0,所以一定是在MessageQueue的隊頭
public final boolean postAtFrontOfQueue(Runnable r) {
return sendMessageAtFrontOfQueue(getPostMessage(r));
}
//把Runnable對象包裝成Message對象,可見只是把Runnable對象賦值給了Message的callback域
private static Message getPostMessage(Runnable r) {
Message m = Message.obtain();
m.callback = r;
return m;
}
//上述方法的重載方法,把token賦值給了Message的obj域,可以用這個方法進行傳Object數據
private static Message getPostMessage(Runnable r, Object token) {
Message m = Message.obtain();
m.obj = token;
m.callback = r;
return m;
}
5.2.2 sendMessage
方式
public final boolean sendMessage(Message msg) {
return sendMessageDelayed(msg, 0);
}
public final boolean sendEmptyMessage(int what) {
return sendEmptyMessageDelayed(what, 0);
}
public final boolean sendEmptyMessageDelayed(int what, long delayMillis) {
Message msg = Message.obtain();
msg.what = what;
return sendMessageDelayed(msg, delayMillis);
}
//延遲發送,把當前時間加上延遲時間後調用了sendMessageAtTime()方法
public final boolean sendMessageDelayed(Message msg, long delayMillis) {
if (delayMillis < 0) {
delayMillis = 0;
}
return sendMessageAtTime(msg, SystemClock.uptimeMillis() + delayMillis);
}
//發送消息的方法,對queue判空後,調用enqueueMessage進行實際入隊
public boolean sendMessageAtTime(Message msg, long uptimeMillis) {
MessageQueue queue = mQueue;
if (queue == null) {
RuntimeException e = new RuntimeException(this + " sendMessageAtTime() called with no mQueue");
Log.w("Looper", e.getMessage(), e);
return false;
}
return enqueueMessage(queue, msg, uptimeMillis);
}
//實際對消息入隊的方法,在該方法中,會把Message的target域進行賦值,
//如果mAsynchronous是true,則會調用setter方法把消息設置爲異步消息,
//調用的入隊方法其實是調用的MessageQueue的enqueueMessage方法
private boolean enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis) {
msg.target = this;
if (mAsynchronous) {
msg.setAsynchronous(true);
}
return queue.enqueueMessage(msg, uptimeMillis);
}
//...
//省略其他方法,基本上跟post系列方法是一一對應的
- 幾個時間關係:
when = now + delay
,when 表示分發消息(dispatchMessage
)的時間,now表示當前(相對系統啓動)時間SystemClock.uptimeMillis()
,delay表示延遲時間delayMillis
;
post(runnable)
方式和sendMessage(msg)
方式發送消息的聯繫和區別:
- 聯繫:調用鏈都是
handler.sendMessageAtTime()
->messageQueue.enqueueMessage()
將Message發送到消息隊列; - 區別:
- 消息內容不同:
sendMessage
發送的消息側重於傳數據,而handleCallback
側重於傳任務(Runnable); - 處理消息的方式不同:一般情況,
sendMessage
發送的消息最終會調用handler或callback的handleMessage
方法來處理;而post(runnable)
發送的消息最終會調用handler.handleCallback
方法來處理;
- 消息內容不同:
5.3 處理消息
//消息分發
public void dispatchMessage(Message msg) {
if (msg.callback != null) {//post的Runnable參數
handleCallback(msg);
} else {
if (mCallback != null) {//Handler(Callback)構造器,Handler無需派生子類
if (mCallback.handleMessage(msg)) {//一般爲true,若爲false,還要執行handleMessage
return;
}
}
handleMessage(msg);//優先級最低
}
}
private static void handleCallback(Message message) {//處理消息方式1,優先級最高
message.callback.run();//執行post的Runnable參數地run回調方法,因此Runnable run裏可以有一些更新UI的操作
}
public interface Callback {//優先級次之
public boolean handleMessage(Message msg);//處理消息方式2,在調用Handler(Callback)構造器實例化Handler時實現該方法
}
//Handler子類必須實現這個空方法來接收消息
public void handleMessage(Message msg) {//處理消息方式3,優先級最低
}
- 有兩類發送消息的方式:
sendMessage(msg)
方式和post(Runnable r)
方式; - 處理消息方式有三種:
handleCallback
方式、callback.handleMessage
方式、handler.handleMessage
方式;並且優先級遞減;
6 四要素之間的關係
6.1 四要素之間的關係
- 一個線程中可創建多個Handler對象;但是一個線程中只能創建一個Looper對象(因爲一個線程中只能調用一次
Looper.preapre()
,否則會報異常); - 一個Looper對象可以對應多個線程,比如主線程的mainLooper,供主線程和所屬子線程(
Looper.getMainLooper()
)共同使用; - Looper類中有一個
final MessageQueue mQueue
屬性; - Handler類中有一個屬性
final Looper mLooper
屬性,Handler關聯的消息隊列通過Looper獲取; - 一個消息隊列中有多個Message對象,不同消息的target可以不同,所以消息隊列中的消息可以來自不同的Handler對象;
- Message類有一個
Handler target
屬性,這是消息對象關聯的Handler對象;
6.2 異步消息處理機制的原理(圖非常重要)
以下面應用場景爲例:在主線程裏實例化Looper和Handler;在子線程(工作線程)處理耗時任務,由於要將任務執行結果在UI上展示,需要更新UI;在子線程中創建一個更新UI的Message對象,並使用Handler對象的引用發送該消息;最後在主線程裏處理消息,更新UI;下面是sample代碼:
public class MainActivity extends AppCompatActivity {
@BindView(R.id.textView) TextView textView;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);setContentView(R.layout.activity_main);
ButterKnife.bind(this);
}
@OnClick(R.id.button)
public void getData() {
new Thread(new Runnable() {
@Override
public void run() {
//執行耗時操作...
Message msg=new Message();msg.what=7;msg.obj= "網絡數據";
//處理消息時回調handler.handleMessage
// handler1.sendMessage(msg);
//處理消息時回調callback.handleMessage
// handler2.sendMessage(msg);
//此處使用handler1,2,3 post消息都可以,但是不會執行handleMessage
handler3.post(new Runnable() {
@Override
public void run() {
//不會開啓新線程執行,handleCallback執行run裏的代碼,所以不會報錯
textView.setText("耗時操作處理結果");
}
});
}
}).start();
}
Handler handler3=new Handler();
//匿名內部類向上轉型Handler()方式,派生子類
Handler handler1=new Handler(){
@Override
public void handleMessage(Message msg) {
switch (msg.what){
case 7:
textView.setText(msg.obj.toString());//更新UI
break;
}
}
};
//Handler(Callback)方式,不派生子類
Handler handler2=new Handler(new Handler.Callback() {
@Override
public boolean handleMessage(Message msg) {
textView.setText(msg.obj.toString());//更新UI
return true;//修改爲true
}
});
}
7 常見面試問題
(1) 爲什麼創建 Message 對象推薦使用 Message.obtain()獲取而不是new方式?
Handler 機制在 Android 系統中使用太頻繁,爲了提神效率,爲Message設置了一個靜態的消息池,當消息被處理完或移除後,會放入到消息池;下次需要使用Message時從消息池中取出消息進行複用;
(2) 簡述MessageQueue 如何入隊和出隊?
- 消息入隊:調用
enquueMessage(msg,when)
;如果消息沒設置when或者when是0,直接將消息放到隊列頭部;否則遍歷隊列鏈表,找到第一個大於當前消息when的消息結點,插入到該節點前面;最後會形成一個按when升序的單鏈表; - 消息出隊:調用
next()
方法,直接取出隊列頭部消息並返回;
(3) 發送消息兩種主要方式 sendMessage(msg)
方式和post(Runnable r)
方式的區別?
post(runnable)
方式和sendMessage(msg)
方式發送消息的聯繫和區別:
- 聯繫:調用鏈都是
handler.sendMessageAtTime()
->messageQueue.enqueueMessage()
將Message發送到消息隊列; - 區別:
- 消息內容不同:
sendMessage
發送的消息側重於傳數據,而handleCallback
側重於傳任務(Runnable); - 處理消息的方式不同:一般情況,
sendMessage
發送的消息最終會調用handler或callback的**handleMessage
方法來處理;而post(runnable)
發送的消息最終會調用handler.handleCallback
**方法來處理;
- 消息內容不同:
(4) 處理消息有哪幾種方式,他們之間優先級?
- 處理消息方式有三種:
handler.handleCallback
方式、callback.handleMessage
方式、handler.handleMessage
方式;並且優先級遞減;
(5) Handler發送、處理消息有哪幾種方式?
- 結合有以下三種常見的 Handler發送、處理消息 的方式:
//方式1:send+派生方式
//發送消息sendMessage;構造器Handler();處理消息handler.handleMessage;
//注意這種方式由於存在Handler子類內部類,可能存在內存泄漏的情況,需要處理這種情況
handler1.sendMessage(msg);
//匿名內部類向上轉型Handler()方式,需要派生子類
Handler handler1=new Handler(){
@Override
public void handleMessage(Message msg) {
switch (msg.what){
case 1:
textView.setText(msg.obj.toString());//更新UI
break;
}
}
};
//方式2:send+Callback方式
//發送消息sendMessage;構造器Handler(Callback);處理消息callback.handleMessage;
handler2.sendMessage(msg);
//Handler(Callback)方式,不派生子類
Handler handler2=new Handler(new Handler.Callback() {
@Override
public boolean handleMessage(Message msg) {
textView.setText(msg.obj.toString());//更新UI
return true;//修改爲true
}
});
//方式3:post(Runnable r)方式
//發送消息post(Runnable r);構造器Handler();處理消息handleCallback;
//此處使用handler1,2,3 post消息都可以,但是不會執行handleMessage
handler3.post(new Runnable() {
@Override
public void run() {
//不會開啓新線程,handleCallback執行run裏的代碼,所以不會報錯
textView.setText("耗時操作處理結果");
}
});
Handler handler3=new Handler();
(6) 異步消息處理機制的原理(Handler發送消息、處理消息的流程)?(高頻題也是本章核心和概要,很重要)
- Handler消息處理器 作用:發送消息(任務),處理消息;
- Looper消息泵 作用:調用
Looper.loop()
方法進行消息輪詢; - MessageQueue消息隊列 作用:存放消息的場所,是一個按when值遞增的單鏈表;
queue.enqueue(msg,when)
用於消息入隊;queue.next()
方法用於消息出隊,取隊首消息;
(7) post(Runnable r)
方式是否會開啓新線程?
這種方式不會開啓新線程;
- Runnable對象會包裝成Message對象,r作爲Message對象的callback屬性;
- 然後調用
handler.sendMessageDelay()
->handler.sendMessageAtTime()
->messageQueue.enqueueMessage()
將Message對象發送到消息隊列; - Looper在開啓消息輪詢後,到一定時間會從消息隊列中取出該消息對象,交給對應的target(Handler對象)進行消息分發;
- 然後調用
handler.handleCallback()
方法處理消息,在這個方法裏Runnable任務會得到執行;
(8) 區分兩個callback?區分兩個handleMessage
?
兩個callback:
Message
的Runnable callback
屬性:使用post(Runnable r)
裏的Runnable對象初始化;Handler
的final Callback mCallback
屬性:在Handler(Callback callabck)
構造器進行初始化;
兩個handleMessage
方法,對應處理消息的兩種方式:
handler.handleMessage
:send+派生方式調用該方法來處理消息;callback.handleMessage
:send+Callback方式調用該方法來處理消息;
(9) 爲什麼Handler會造成內存泄漏?如何解決?
(i) 內存泄漏的原因?
- 一般造成內存泄漏的原因:長生命週期對象引用短生命週期對象;
- Handler造成Activity內存泄漏的原因:Handler生命週期比Activity長,非靜態內部類默認持有外部類的引用,導致Activity對象無法回收(Activity對象先回收時,Handler對象可能還在處理消息,此時Handler對象還持有Activity對象的引用,導致Activity對象無法回收)。
(i) 解決辦法?
把Handler子類定義爲靜態(static)內部類;同時用WeakReference包裝外部類的對象activity;
- 爲什麼Handler子類要定義爲靜態(static)內部類?
因爲靜態內部類不持有外部類的引用,所以使用靜態的Handler不會導致Activity內存泄露。 - 爲什麼Handler子類定義爲靜態(static)內部類同時,還要用WeakReference包裝外部類的對象activity ?
因爲我們需要訪問外部類的非靜態成員,可以通過強引用"activity. "訪問,如果直接使用強引用訪問,顯然會導致activity泄露。
MyHandler mHandler=new MyHandler(this);
private static class MyHandler extends Handler{
//static和WeakReference是爲了解決內存泄漏
private WeakReference<MainActivity> weakReference;
public MyHandler(MainActivity activity) {
weakReference = new WeakReference<>(activity);
}
@Override
public void handleMessage(Message msg) {
switch (msg.what){
case 1:
MainActivity activity=weakReference.get();
if(activity != null){//判空是爲了避免空引用異常
activity.textView.setText(msg.obj.toString());
}
break;
}
}
}
(10) 爲何Looper.loop()
死循環不會造成應用卡死?
Looper.loop()
不會造成應用卡死,因爲裏面使用了Linux 的epoll機制;
(11) 創建Handler前要注意什麼?
創建Handler對象前必須調用Looper.prepare()
方法創建一個Looper對象;值得一體的是,子線程中必須手動調用Looper.prepare()
方法,而主線程中可以不調用;因爲主線程ActivityThread
的main方法中默認調用了Looper.prepareMainLooper()
方法,這個方法會調用Looper.prepare()
方法創建一個主線程Looper對象;
(12) 異步消息處理機制是如何保證消息處理器的唯一性(即某條消息的發送者和處理者是同一Handler對象)?
在Handler的enqueueMessage
方法中會把自引用賦值給被髮送的Message的target屬性;而在Looper的loop
方法中會調用msg.target.dispatchMessage(msg)
來分發、處理消息;
//Handler.java
private boolean enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis) {
msg.target = this;
//...
}
//Looper.java
public static void loop() {
//...
msg.target.dispatchMessage(msg);
//...
}
(13) 子線程中是否可以創建Handler對象?
子線程中可以創建Handler對象,但是子線程在創建Handler對象前必須手動調用Looper.prepare()
方法創建Looper對象;
new Thread(new Runnable() {
@Override
public void run() {
if(Looper.myLooper()==null){//保證一個線程只有一個Looper
Looper.prepare();//創建Looper
}
Handler handler1=new Handler();
Looper.loop();//開啓消息輪詢
//主線程Looepr對象早已創建,並早已開啓消息輪詢
Handler handler2=new Handler(Looper.prepareMainLooper());
}
}).start();
ps:如果在主線程中使用子線程中創建的Handler對象的引用發送消息,最後消息是在子線程中處理的;這樣就實現了主線程向子線程發送消息,而在本文6.2節中的sample代碼中實現了子線程向主線程發送消息;所以兩個線程可以通過Handler進行雙向通信;
(14) Handler 與 Looper 是如何關聯的?
- 對於有Looper參數的構造器Handler(looper):直接通過構造器參數設置關聯的Looepr對象;
- 對於無Looper參數的Handler構造器:無論是
Handler()
還是Handler(callback)
都會調用下面的構造器,在這個構造其中會爲當前Handler關聯當前線程的Looper對象;
public Handler(Callback callback, boolean async) {
//...
mLooper = Looper.myLooper();//獲取當前線程的Looepr對象
//...
}
public static @Nullable Looper myLooper() {
return sThreadLocal.get();//獲取當前線程的本地變量
}
(15) Thread 與 Looper 是如何關聯的?
Looper 與 Thread 之間是通過 ThreadLocal
關聯的,這個可以看 Looper.prepare(quitAllowed)
方法:
// Looper.java:93
private static void prepare(boolean quitAllowed) {
//...
sThreadLocal.set(new Looper(quitAllowed));//設置當前線程本地變量
}
Looper 類有一個 ThreadLocal 類型的 sThreadLocal靜態屬性,Looper通過它的 get 和 set 方法來賦值和取值;
由於 ThreadLocal是與當前線程是綁定的,所以我們只要把 Looper 與 ThreadLocal 綁定了,那 Looper 和 Thread 也就關聯上了;
(16) 如何在子線程中獲取當前線程的 Looper?
Looper.myLooper();//獲取當前線程的Looepr對象
內部原理就是sThreadLocal.get()
:
// Looper.java:203
public static @Nullable Looper myLooper() {
return sThreadLocal.get();
}
(16) 如何在任意線程獲取主線程的 Looper?
Looper.getMainLooper();//獲取主線程的Looper對象
這個在我們開發 API時特別有用,畢竟你不知道開發者在使用你的API時會在哪個線程初始化Looper,所以我們在創建 Handler 時每次都通過指定主線程的 Looper 的方式保證API正常運行。所以一般使用主線程Looper來進行異步消息處理;
(17) 如何判斷當前線程是不是主線程?
//方式1
Looper.myLooper() == Looper.getMainLooper();
//方式2
Looper.getMainLooper().getThread() == Thread.currentThread();
//方式3:方式2簡化版
Looper.getMainLooper().isCurrentThread();
(18) Looper.loop()
方法會退出嗎?
不會自動退出,但是我們可以手動調用 looper.quit()
或looper.quitSafely()
方法退出消息輪詢。
這兩個方法都會調用MessageQueue#quit(boolean)
方法讓MessageQueue#mQuitting
屬性置爲true
,標記消息隊列已退出;消息隊列退出後,MessageQueue#next()
方法發現已經調用過 MessageQueue#quit(boolean) 時會 return null ;然後Looper.loop()
方法退出消息輪詢;
如果looper.quit()
,looper.quitSafely()
,MessageQueue#quit(boolean)
都不手動調用並且消息隊列爲空,消息隊列不會退出;next()
方法會一直死循環(有的說法稱爲next方法阻塞),loop()
方法會在Message msg = queue.next();
處阻塞等待新消息到達消息隊列,繼續消息輪詢。所以建議當所有Message都被處理完之後手動調用looper.quit()
或looper.quitSafely()
方法退出消息輪詢,避免loop()
方法一直阻塞等待。
//Looper.java#322
public void quit() {//Looper退出
mQueue.quit(false);
}
//Looper.java#338
public void quitSafely() {
mQueue.quit(true);
}
//Looper.java#137
public static void loop() {//開啓消息輪詢
//...
for (;;) {
Message msg = queue.next(); // 當消息隊列爲空且未退出時,next方法會阻塞
if (msg == null) {
//next返回null表明消息隊列已退出
return;//結束輪詢,loop方法唯一出口
}
//...
msg.target.dispatchMessage(msg);
//...
}
}
//MessageQueue.java#416
void quit(boolean safe) {
//...
mQuitting = true;
//...
}
//MessageQueue.java#310
Message next() {
//...
for (;;) {
synchronized (this) {
//...
Message msg = mMessages;
//...
if (msg != null) {
if (now < msg.when) {
//...
}else{
//...
return msg;//取到非空消息退出
}
}else{
// No more messages.
nextPollTimeoutMillis = -1;
}
//若消息隊列已退出,返回true退出死循環
if (mQuitting) {
dispose();
return null;
}
}
(19) MessageQueue#next()
方法在消息隊列爲空時會阻塞,如何恢復?
使用Handler的sendMessage、post 等一系列方法發送消息,這些發送消息的方法會調用MessageQueue#enqueueMessage
將新消息入隊,從而使得next()
方法不再阻塞;
(20) IdleHandler作用和使用場景?
把頁面啓動時的複雜邏輯交給IdleHandler去處理,這樣可以讓主線程Handler先處理完相關UI邏輯後再去處理複雜邏輯,可以減少頁面啓動白屏時間,從而優化頁面啓動;
(21) 子線程爲何不能訪問UI?
- 源碼角度:當訪問UI時,ViewRootImpl會調用checkThread()方法檢查當前線程是哪個線程,如果不是UI線程會拋出異常;
- 線程安全角度:訪問UI不是線程安全的;
- 訪問UI爲什麼不加鎖:邏輯複雜、效率低;