Android
的消息機制原理是Android
進階必學知識點之一,在Android
面試也是常問問題之一。在Android
中,子線程是不能直接操做View
,須要切換到主線程進行。那麼這個切換動做就涉及到了Android
的消息機制,也就是本文要講的Handler、Looper、MessageQueue、Message它們之間的關係。android
Handler
在消息機制中扮演發送消息和處理消息的角色,也是咱們日常接觸最多的類。面試
下面代碼展現Handler
如何處理消息。新建Handler
對象,並重寫handleMessage,在方法內處理相關邏輯,通常處理和主線程相關的邏輯。Handler有不少的構造器,下面構造器經常使用在主線程。shell
private Handler handler=new Handler(){
@Override
public void handleMessage(Message msg) {
if (msg.what==1){
Toast.makeText(MainActivity.this,"handle message",Toast.LENGTH_LONG).show();
}
}
};
複製代碼
經過下面的代碼能夠了解到,Handler
對象支持發送Message
和Runable
。Runable最終被包裝成Message
的callback
實例變量(Handler
對象處理消息會優先處理callback
的邏輯),和Message
同樣的方式放到消息隊列中。而每一個方法都有相關的變形,支持延遲發送,或者將來的某段時間裏發送等等。bash
//在消息池獲取消息體,能達到消息重用,若是消息池沒有消息,則新建消息
Message msg = handler.obtainMessage();
msg.what = 1;
//發送消息
handler.sendMessage(msg);
//發送空消息,參數會自動被包裝msg.what=1
handler.sendEmptyMessage(1);
//將來的時間裏發送消息
handler.sendEmptyMessageAtTime(1, 1000);
//延遲發送消息
handler.sendEmptyMessageDelayed(1, 1000);
msg = handler.obtainMessage();
msg.what = 2;
//將消息發送消息隊列前面
handler.sendMessageAtFrontOfQueue(msg);
//發送任務,run方法內容將handler被處理。
handler.post(new Runnable() {
@Override
public void run() {
Log.i("Handler", "Runnable");
}
});
複製代碼
若是日常使用,咱們只須要主線程定義Hanlder
處理消息的內容,在子線程發送消息便可達到切換流程。async
Looper
負責循環的從消息隊列中取消息,發送給Handler
處理。由於消息隊列只用來存儲消息,因此須要Looper
不斷的從消息隊列中取消息給Handler
。默認狀況,全部線程並不擁有Looper
。若是在子線程直接執行Looper.loop
方法,就會發生異常。那主線程爲何不會報錯?在App的啓動流程中,建立ActivityThread
時,會調用Looper.prepare
來建立Looper
和MessageQueue
,和Looper.loop
開啓循環。也就是系統爲咱們在主線程建立Looper
和MessageQueue
。因此,在子線建立Handler
前,須要先調用Looper.prepare
方法,建立Looper
和MessageQueue
。IntentService
就是這樣實現的。點擊看IntentService的知識點。ide
MessageQueue內部是以鏈表的形式組織的,主要做用是存儲Message
。在建立Looper
的時候,會自動建立MessageQueue
。函數
Handler
發送消息時會將消息插入到MessageQueue
,而Looper
不斷的從MessageQueue
中取消息分發給Handler
處理。oop
咱們先看一下Handler的構造器代碼。post
public Handler(Callback callback, boolean async) {
if (FIND_POTENTIAL_LEAKS) {
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());
}
}
//分析一
mLooper = Looper.myLooper();
if (mLooper == null) {
throw new RuntimeException(
"Can't create handler inside thread " + Thread.currentThread()
+ " that has not called Looper.prepare()");
}
//分析二
mQueue = mLooper.mQueue;
mCallback = callback;
mAsynchronous = async;
}
複製代碼
Handler
有不少重載的構造器,咱們經常使用在使用默認構造器,最終會調用上面的構造器。ui
分析一
經過Looper.myLooper()
,獲Looper
的實例。而在myLooper
的實現中,是經過ThreadLocal
的get
方法來獲取的。若是ThreadLocal
不存在Looper
,則放回null
。ThreaLocal
這裏能夠簡單理解爲保存當前線程私有獨立的實例,其餘線程不可訪問。若是ThreadLocal
不存在Looper
實例則,返回null
。這也就是前面說的,在子線程建立Handler
前,須要先調用Looper.prepare
方法。不然會拋出RuntimeException
。
public static @Nullable Looper myLooper() {
return sThreadLocal.get();
}
複製代碼
分析二
mQueue
爲Looper
中的消息隊列,mCallBack
定義了一個接口,用於回調消息處理。
public interface Callback {
/**
* @param msg A {@link android.os.Message Message} object
* @return True if no further handling is desired
*/
public boolean handleMessage(Message msg);
}
複製代碼
Handler全部發送消息方法的變體最終都會如下面方法放去到消息隊列中。
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);
}
private boolean enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis) {
msg.target = this;
if (mAsynchronous) {
msg.setAsynchronous(true);
}
return queue.enqueueMessage(msg, uptimeMillis);
}
複製代碼
這裏最重要的就是enqueueMessage
方法中,將當前Handler
對象設置給Message
的target
變量。而後調用隊列queue
的enqueueMessage
方法。
boolean enqueueMessage(Message msg, long when) {
if (msg.target == null) {
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) {
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;
}
msg.markInUse();
msg.when = when;
Message p = mMessages;
boolean needWake;
//若是隊列爲空或者插入message將來處理時間小於當前對頭when
//則將當前消息設爲隊列頭
if (p == null || when == 0 || when < p.when) {
// New head, wake up the event queue if blocked.
msg.next = p;
mMessages = msg;
needWake = mBlocked;
} else {
needWake = mBlocked && p.target == null && msg.isAsynchronous();
Message prev;
for (;;) {
prev = p;
p = p.next;
if (p == null || when < p.when) {
break;
}
if (needWake && p.isAsynchronous()) {
needWake = false;
}
}
msg.next = p; // invariant: p == prev.next
prev.next = msg;
}
// We can assume mPtr != 0 because mQuitting is false.
if (needWake) {
nativeWake(mPtr);
}
}
return true;
}
複製代碼
而在MessageQueue
的enqueueMessage
方法中,會先檢查target
是否null
,message
是否應在使用,當前線程是否退出,死亡狀態。若是是,則拋出異常。若是當前隊列是空或者阻塞,直接當前Message
對象設爲隊列的頭並喚醒線程。若是不是,則根據Message
對象的when
插入到隊列合適的位置。所以能夠看得出,Handler
發送消息時是將消息放到隊列中。
前面講過,子線程使用Handler,須要調用Looper
的靜態prepare
方法。
public static void prepare() {
prepare(true);
}
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就會報錯。若是沒有,new Looper
並保存到ThreadLocal
中。new Looper
很是簡單,只是新建一個MessageQueue
,和持有當前線程。
private Looper(boolean quitAllowed) {
mQueue = new MessageQueue(quitAllowed);
mThread = Thread.currentThread();
}
複製代碼
在調用了Looper.prepare
建立Looper
和MessageQueue
對象後,要調用Loop.loop
的開始循環分發消息隊列中消息。
public static void loop() {
final Looper me = myLooper();
if (me == null) {
throw new RuntimeException("No Looper; Looper.prepare() wasn't called on this thread.");
}
final MessageQueue queue = me.mQueue;
// Make sure the identity of this thread is that of the local process,
// and keep track of what that identity token actually is.
Binder.clearCallingIdentity();
final long ident = Binder.clearCallingIdentity();
// Allow overriding a threshold with a system prop. e.g.
// adb shell 'setprop log.looper.1000.main.slow 1 && stop && start'
final int thresholdOverride =
SystemProperties.getInt("log.looper."
+ Process.myUid() + "."
+ Thread.currentThread().getName()
+ ".slow", 0);
boolean slowDeliveryDetected = false;
//分析一
for (;;) {
Message msg = queue.next(); // might block
if (msg == null) {
// No message indicates that the message queue is quitting.
return;
}
// This must be in a local variable, in case a UI event sets the logger
final Printer logging = me.mLogging;
if (logging != null) {
logging.println(">>>>> Dispatching to " + msg.target + " " +
msg.callback + ": " + msg.what);
}
final long traceTag = me.mTraceTag;
long slowDispatchThresholdMs = me.mSlowDispatchThresholdMs;
long slowDeliveryThresholdMs = me.mSlowDeliveryThresholdMs;
if (thresholdOverride > 0) {
slowDispatchThresholdMs = thresholdOverride;
slowDeliveryThresholdMs = thresholdOverride;
}
final boolean logSlowDelivery = (slowDeliveryThresholdMs > 0) && (msg.when > 0);
final boolean logSlowDispatch = (slowDispatchThresholdMs > 0);
final boolean needStartTime = logSlowDelivery || logSlowDispatch;
final boolean needEndTime = logSlowDispatch;
if (traceTag != 0 && Trace.isTagEnabled(traceTag)) {
Trace.traceBegin(traceTag, msg.target.getTraceName(msg));
}
final long dispatchStart = needStartTime ? SystemClock.uptimeMillis() : 0;
final long dispatchEnd;
try {
//分析二:
msg.target.dispatchMessage(msg);
dispatchEnd = needEndTime ? SystemClock.uptimeMillis() : 0;
} finally {
if (traceTag != 0) {
Trace.traceEnd(traceTag);
}
}
if (logSlowDelivery) {
if (slowDeliveryDetected) {
if ((dispatchStart - msg.when) <= 10) {
Slog.w(TAG, "Drained");
slowDeliveryDetected = false;
}
} else {
if (showSlowLog(slowDeliveryThresholdMs, msg.when, dispatchStart, "delivery",
msg)) {
// Once we write a slow delivery log, suppress until the queue drains.
slowDeliveryDetected = true;
}
}
}
if (logSlowDispatch) {
showSlowLog(slowDispatchThresholdMs, dispatchStart, dispatchEnd, "dispatch", msg);
}
if (logging != null) {
logging.println("<<<<< Finished to " + msg.target + " " + msg.callback);
}
final long newIdent = Binder.clearCallingIdentity();
if (ident != newIdent) {
Log.wtf(TAG, "Thread identity changed from 0x"
+ Long.toHexString(ident) + " to 0x"
+ Long.toHexString(newIdent) + " while dispatching to "
+ msg.target.getClass().getName() + " "
+ msg.callback + " what=" + msg.what);
}
msg.recycleUnchecked();
}
}
複製代碼
分析一: 經過無限制的for
循環,讀取隊列的消息。而MessageQueue
的next
方法內部經過鏈表的形式,根據when
屬性的順序返回message
。
分析二: 調用Message
對象的target
的dipatchMessage
方法。這裏的target
就是發送消息的Handler
對象。而在Handler
對象的dipatchMessage
方法中,優先執行Message
對象的callback
方法,即優先執行咱們發送消息時以Runable
發送的任務,若是有的話。否則檢測Callback
對象的handleMessage
方法,最後纔是咱們重寫Hanlder
對象的handleMessage
方法。由於Handler
不只有默認構造函數,還有能夠傳入Callback
,Looper
等的構造函數。
public void dispatchMessage(Message msg) {
if (msg.callback != null) {
handleCallback(msg);
} else {
if (mCallback != null) {
if (mCallback.handleMessage(msg)) {
return;
}
}
handleMessage(msg);
}
}
複製代碼
經過handler.obtainMessage
而不是new
方式得到消息實例。由於obtainMessage
方法會先檢測消息池是否有能夠複用的消息,沒有再去new
一個消息實例。下面是類Message的obtain
方法。
public static Message obtain() {
synchronized (sPoolSync) {
if (sPool != null) {
Message m = sPool;
sPool = m.next;
m.next = null;
m.flags = 0; // clear in-use flag
sPoolSize--;
return m;
}
}
return new Message();
}
複製代碼
sPool
的類型是Message
,內部經過成員變量next
,維護一個消息池。雖然叫消息池,內部卻經過next
不斷的指向下一個Message
,以鏈表維護的這個消息池,默認大小爲50。在鏈表sPool
不爲空的狀況,取表頭Message
元素,並將相關屬性進行初始化。
那麼Message對象是在何時被放進消息池中的呢?
在Looper
的loop
方法中,最後調用Message
的recycleUnchecked
方法
void recycleUnchecked() {
// Mark the message as in use while it remains in the recycled object pool.
// Clear out all other details.
flags = FLAG_IN_USE;
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++;
}
}
}
複製代碼
在同步代碼塊,能夠看到,將sPool
指向當前要被回收的Message
對象,而Message
的next
指向以前的表頭。
Handler
,須要先調用Looper.prepare
方法,再調用Looper.loop
方法。when
時間依次排列。IntentService
、HandlerTread
、AsyncTack
。知識點分享