Android體系課--Handler—按方法進行原始碼解析
Handler系列:
Handler原始碼解析
1.建構函式
java
public Handler(Looper looper, Callback callback, boolean async) {
mLooper = looper;1.傳入的當前執行緒的looper物件
mQueue = looper.mQueue;2.傳入的當前執行緒的looper物件的MessageQueue
mCallback = callback;3.傳入的Handler.CallBack物件,在處理的時候會判斷該物件是否存在還有返回值是否為true
mAsynchronous = async;
}
總結:
1.哪個執行緒執行訊息處理請求,是根據傳入的looper
來確認。
2.獲取Message
java
Handler.obtainMessage
Message.obtain(this, what){
Message m = obtain();分析1:
m.target = h;
m.what = what;
return m;
}
分析1:
public static Message obtain() {
synchronized (sPoolSync) {
if (sPool != null) {sPool指向訊息池的頭節點,如果不為空進入
Message m = sPool; 使用一個臨時變數m=sPool
sPool = m.next; 讓sPool指向m的next
m.next = null; 打斷m到sPool的指標,這樣sPool還是指向連結串列的頭結點,只是這個節點是之前sPool的next節點
m.flags = 0; // clear in-use flag
sPoolSize--訊息池連結串列大小減1
return m; 返回之前從訊息池中取出的頭結點,
}
}
return new Message();如果訊息池沒有訊息,則建立訊息
}
總結:Handler.obtainMessage
方法可以從Message
的訊息池中獲取訊息,取出的是訊息池的頭結點
訊息,如果沒有訊息則建立消
息,這個方法可以避免不必要的訊息建立,重用訊息池的訊息
,減少記憶體開銷
3.傳送sendMessage
```java sendMessageDelayed(msg, 0); sendMessageAtTime(msg, SystemClock.uptimeMillis() + delayMillis);延遲時間加上系統時間組成when MessageQueue queue = mQueue;這個mQueue是在Handler建構函式中賦值 enqueueMessage(queue, msg, uptimeMillis); msg.target = this;將當前Handler賦值給msg.target if (mAsynchronous) {如果是非同步訊息,則設定msg的非同步標誌 msg.setAsynchronous(true); } queue.enqueueMessage(msg, uptimeMillis){呼叫MessageQueue的enqueueMessage方法 if (msg.target == null) {判斷handler是否為空 throw new IllegalArgumentException("Message must have a target."); } if (msg.isInUse()) {判斷msg是否被使用 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為使用狀態,防止msg被重複使用
msg.when = when;設定msg的延遲時間
Message p = mMessages;獲取訊息池的頭節點賦值給臨時變數p
boolean needWake; 是否喚醒looper的next
if (p == null || when == 0 || when < p.when) { 訊息池為空或者延遲時間為0或者延遲時間小余頭節點的延遲時間,則將其插入訊息池的頭節點
// New head, wake up the event queue if blocked.
msg.next = p;
mMessages = msg;
needWake = mBlocked; mBlocked是在訊息處理next中賦值,如果有訊息正在處理則mBlocked=false,如果空閒狀態則mBlocked=true,即需要喚醒
} else {
// Inserted within the middle of the queue. Usually we don't have to wake
// up the event queue unless there is a barrier at the head of the queue
// and the message is the earliest asynchronous message in the queue.
needWake = mBlocked && p.target == null && msg.isAsynchronous();如果是空閒狀態且p.target == null和msg是非同步訊息,則需要喚醒
Message prev;
for (;;) {遍歷連結串列取出當前msg延遲時間小余其延遲時間的msg
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 將msg插入p的前面
prev.next = msg;將msg插入prev後面,即插入prev和p的中間
}
// We can assume mPtr != 0 because mQuitting is false.
if (needWake) {如果需要喚醒,則呼叫nativeWake喚醒next方法中的nativePoolOnce
nativeWake(mPtr);
}
}
return true;
}
``` 總結:
1.訊息插入機制:插入訊息的順序是延遲時間最小的放在訊息池的頭部
2.訊息喚醒時機:
2.1:如果當前訊息插入的是頭節點,則判斷訊息處理是不是空閒狀態,如果是空閒則喚醒
2.2:如果訊息出入中間節點,則首先判斷是不是空閒狀態還有`p.target == null`和`msg`是非同步訊息,這三個條件都成立都可以把needWake置為true,
之後還要判斷當前訊息是不是最早的非同步訊息,如果不是最早的,則needWake 置為 false即不需要喚醒,如果是最早的非同步訊息,則直接喚醒訊息處理迴圈
4.訊息獲取過程:
Looper的loop
方法:
```java public static void loop() { final Looper me = myLooper();獲取當前執行緒的looper物件 final MessageQueue queue = me.mQueue;獲取MessageQueue物件 for (;;) { Message msg = queue.next(); // might block獲取msg if (msg == null) {沒有訊息的時候則退出迴圈,即主執行緒退出,應用退出 // No message indicates that the message queue is quitting. return; } try { msg.target.dispatchMessage(msg);處理msg } msg.recycleUnchecked();回收msg } } 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++;
}
}
} MessageQueue.java: Message next() { // Return here if the message loop has already quit and been disposed. // This can happen if the application tries to restart a looper after quit // which is not supported. final long ptr = mPtr; if (ptr == 0) { return null; }
int pendingIdleHandlerCount = -1; // -1 only during first iteration
int nextPollTimeoutMillis = 0;
for (;;) {
if (nextPollTimeoutMillis != 0) {
Binder.flushPendingCommands();
}
在這裡休眠,如果有訊息並喚醒
nativePollOnce(ptr, nextPollTimeoutMillis);
synchronized (this) {
// Try to retrieve the next message. Return if found.
final long now = SystemClock.uptimeMillis();
Message prevMsg = null;
Message msg = mMessages;
if (msg != null && msg.target == 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) {msg不為空
if (now < msg.when) {當前時間小余msg的延遲時間,則等待:msg.when - now
// 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 {當前時間大於取出的msg的延遲時間
// Got a message.
mBlocked = false;將mBlocked空閒時間置為false
if (prevMsg != null) {
prevMsg.next = msg.next;
} else {
mMessages = msg.next;取出msg後,將msg的next節點置為頭節點
}
msg.next = null;打斷msg到msg next的連結串列連結
if (DEBUG) Log.v(TAG, "Returning message: " + msg);
msg.markInUse();將msg的使用標誌置為true
return msg;返回msg
}
} else {msg為空表示沒有訊息需要處理
// No more messages.
nextPollTimeoutMillis = -1;
}
// Process the quit message now that all pending messages have been handled.
if (mQuitting) {當呼叫了退出方法則返回null給上層
dispose();內部呼叫nativeDestroy(mPtr);
return null;
}
// If first time idle, then get the number of idlers to run.
// Idle handles only run if the queue is empty or if the first message
// in the queue (possibly a barrier) is due to be handled in the future.
下面這些資訊是處理對於設定了空閒訊息處理任務的流程,這個可以用來提高Ui效能,即將主執行緒空閒狀態來處理一些其他事情,充分利用資源
if (pendingIdleHandlerCount < 0
&& (mMessages == null || now < mMessages.when)) {
pendingIdleHandlerCount = mIdleHandlers.size();
}
if (pendingIdleHandlerCount <= 0) {
// No idle handlers to run. Loop and wait some more.
mBlocked = true; 如果沒有任何空閒狀態事情處理後,將mBlocked置為true,表示是真正空閒狀態,無任何處理事務包括空閒事務,這個值在訊息插入的時候對是否喚醒訊息處理有關係
continue;
}
if (mPendingIdleHandlers == null) {
mPendingIdleHandlers = new IdleHandler[Math.max(pendingIdleHandlerCount, 4)];
}
mPendingIdleHandlers = mIdleHandlers.toArray(mPendingIdleHandlers);
}
// Run the idle handlers.
// We only ever reach this code block during the first iteration.
for (int i = 0; i < pendingIdleHandlerCount; i++) {
final IdleHandler idler = mPendingIdleHandlers[i];
mPendingIdleHandlers[i] = null; // release the reference to the handler
boolean keep = false;
try {
keep = idler.queueIdle();
} catch (Throwable t) {
Log.wtf(TAG, "IdleHandler threw exception", t);
}
if (!keep) {
synchronized (this) {
mIdleHandlers.remove(idler);
}
}
}
// Reset the idle handler count to 0 so we do not run them again.
pendingIdleHandlerCount = 0;
// While calling an idle handler, a new message could have been delivered
// so go back and look again for a pending message without waiting.
nextPollTimeoutMillis = 0;
}
}
``` 總結:
訊息取出過程:
首先判斷訊息頭是否是一個同步屏障訊息msg.traget=null,
如果是取出連結串列中第一個非同步訊息進行處理,如果不是則直接取出訊息池中第一個訊息。
如果沒有任何訊息需要處理,則判斷是否有空閒任務需要處理ideHandler,有就去處理空閒任務,沒有就將最終空閒狀態置為true
5.訊息處理:
js
Looper.loop方法中:
msg.target.dispatchMessage(msg);msg.target =Handler
Handler.dispatchMessage(msg){
if (msg.callback != null) {如果msg在建立過程中msg.callback不為null,則直接呼叫handleCallback(msg)---> message.callback.run();
handleCallback(msg);
} else {
if (mCallback != null) {
if (mCallback.handleMessage(msg)) {如果Handler在建立的時候傳入的Handler.CallBack不為空則呼叫CallBack的handleMessage方法
return;如果返回值為true則不會回撥Handler的handleMessage,這裡可以做一個訊息攔截的處理
}
}
handleMessage(msg);呼叫Handler的handleMessage
}
}
6.訊息迴圈處理退出:呼叫Looper的quit方法
```java void quit(boolean safe) { if (!mQuitAllowed) { throw new IllegalStateException("Main thread not allowed to quit."); }
synchronized (this) {
if (mQuitting) {
return;
}
mQuitting = true;將mQuitting標誌置為true
if (safe) {
removeAllFutureMessagesLocked();待處理訊息執行完再清理
} else {
removeAllMessagesLocked();直接清理,可能會有記憶體洩露風險
}
// We can assume mPtr != 0 because mQuitting was previously false.
nativeWake(mPtr);喚醒訊息處理執行緒
}
}
```
7.View的繪製流程中:View繪製會走到scheduleTraversals中
```java / :ViewRootImpl.scheduleTraversals() / void scheduleTraversals() { if (!mTraversalScheduled) { mTraversalScheduled = true; mTraversalBarrier = mHandler.getLooper().getQueue().postSyncBarrier();分析1
// 通過mHandler.post()傳送一個runnable,在run()方法中去處理繪製流程
// 與ActivityThread的Handler訊息傳遞機制相似
// ->>分析7
mChoreographer.postCallback(Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);分析2
}
}
final class TraversalRunnable implements Runnable {
@Override
public void run() {
doTraversal();
}
}
void doTraversal() {
if (mTraversalScheduled) {
mTraversalScheduled = false;
mHandler.getLooper().getQueue().removeSyncBarrier(mTraversalBarrier);分析3
if (mProfile) {
Debug.startMethodTracing("ViewAncestor");
}
performTraversals();
if (mProfile) {
Debug.stopMethodTracing();
mProfile = false;
}
}
}
分析1:MessageQueue->postSyncBarrier
private int postSyncBarrier(long when) {
// Enqueue a new sync barrier token.
// We don't need to wake the queue because the purpose of a barrier is to stall it.
synchronized (this) {
final int token = mNextBarrierToken++;
final Message msg = Message.obtain();去Message的訊息池中獲取中獲取msg
msg.markInUse();設定inuse
msg.when = when;設定延遲時間
msg.arg1 = token;設定token
Message prev = null;
Message p = mMessages;
if (when != 0) {這個判斷內部其實是訊息連結串列mMessages中取出延遲時間比當前msg的延遲時間更大的msg,
while (p != null && p.when <= when) {
prev = p;
p = p.next;
}
}
if (prev != null) { // 這裡面其實是把msg插入訊息連結串列mMessages中
msg.next = p;
prev.next = msg;
} else {
msg.next = p;
mMessages = msg;
}
return token;返回msg的token
}
}
``` 總結:
postSyncBarrier
的作用是去訊息池中獲取一個msg,設定了msg.arg1 = token
是同步屏障訊息的token值,且msg.target
= null;並將這個msg
放入到訊息池mMessages
中,下次處理執行緒被喚醒時會判斷訊息池的第一個msg的target是不是空
,並去後面取第一個非同步任務
,這個非同步任務其實是一個view的繪製流程
。同步屏障實現了view優先繪製
```java 分析2:mChoreographer.postCallback postCallbackDelayed(callbackType, action, token, 0); postCallbackDelayedInternal(callbackType, action, token, delayMillis);{ synchronized (mLock) { final long now = SystemClock.uptimeMillis(); final long dueTime = now + delayMillis; mCallbackQueues[callbackType].addCallbackLocked(dueTime, action, token);
if (dueTime <= now) {
scheduleFrameLocked(now);//分析8
} else {
Message msg = mHandler.obtainMessage(MSG_DO_SCHEDULE_CALLBACK, action);獲取一個msg
msg.arg1 = callbackType;設定arg1引數型別
msg.setAsynchronous(true);設定為非同步訊息
mHandler.sendMessageAtTime(msg, dueTime);傳送訊息
}
}
分析8:scheduleFrameLocked(now);
private void scheduleFrameLocked(long now) {
...
scheduleVsyncLocked();
...
}
private void scheduleVsyncLocked() {
mDisplayEventReceiver.scheduleVsync();//這個mDisplayEventReceiver = FrameDisplayEventReceiver物件
} public void scheduleVsync() { if (mReceiverPtr == 0) { Log.w(TAG, "Attempted to schedule a vertical sync pulse but the display event " + "receiver has already been disposed."); } else { nativeScheduleVsync(mReceiverPtr);//這裡呼叫nativeScheduleVsync註冊了一個Vsync事件接收器,接收者為前面的mDisplayEventReceiver } } private final class FrameDisplayEventReceiver extends DisplayEventReceiver{ @Override public void onVsync(long timestampNanos, int builtInDisplayId, int frame) { Message msg = Message.obtain(mHandler, this); msg.setAsynchronous(true); mHandler.sendMessageAtTime(msg, timestampNanos / TimeUtils.NANOS_PER_MS);//這裡傳送了一個非同步訊號,每16ms接收到一次訊號,並繪製ui }
}
``` 總結:
postCallback
內部主要實現的是獲取一個msg
,並設定msg為非同步訊息
,最後傳送訊息給MessageQueue
註冊了vsync
訊號回撥,每16ms
獲取到vsync
訊號,並更新ui
,所以ondraw
方法是在接收到vsync訊號後才呼叫的,會每16ms
回撥一次ondraw
方法
```java 分析3:mHandler.getLooper().getQueue().removeSyncBarrier(mTraversalBarrier); public void removeSyncBarrier(int token) { // Remove a sync barrier token from the queue. // If the queue is no longer stalled by a barrier then wake it. synchronized (this) { Message prev = null; Message p = mMessages; while (p != null && (p.target != null || p.arg1 != token)) {其實是取出target==null且p.arg1=傳入的token的值,即之前插入訊息連結串列mMessages的msg prev = p; p = p.next; } if (p == null) { throw new IllegalStateException("The specified message queue synchronization " + " barrier token has not been posted or has already been removed."); } final boolean needWake;
if (prev != null) {這個if是將p在連結串列中去除,prev不為null說明p不在表頭
prev.next = p.next;
needWake = false;
} else {為null說明p在表頭。
mMessages = p.next;
needWake = mMessages == null || mMessages.target != null;如果mMessages == null || mMessages.target != null;表頭資料不是同步屏障訊息,或者訊息池資料為空,則喚醒訊息處理執行緒
}
p.recycleUnchecked();回收訊息到訊息池sPool中
// If the loop is quitting then it is already awake.
// We can assume mPtr != 0 when mQuitting is false.
if (needWake && !mQuitting) {
nativeWake(mPtr);
}
}
}
``
總結:根據
token值移除訊息連結串列中的
msg並根據情況
喚醒訊息處理執行緒`
由
分析1和分析2,3可知
:View的繪製流程
其實就是在View繪製流程啟動前,給訊息池傳送一個msg.target為空的訊息
,然後給View的繪製任務的msg設定為非同步訊息
,下次在Handler
取訊息的過程中優先判斷訊息池的msg.target
是不是空,如果是,則去訊息池中取出第一個非同步訊息執行
。執行前先把同步屏障訊息移除
。這就是訊息同步屏障機制
7.ThreadLocal機制:sThreadLocal.set(new Looper(quitAllowed));
```java public void set(T value) { //(1)獲取當前執行緒(呼叫者執行緒) Thread t = Thread.currentThread(); //(2)以當前執行緒作為key值,去查詢對應的執行緒變數,找到對應的map ThreadLocalMap map = getMap(t); //(3)如果map不為null,就直接新增本地變數,key為當前定義的ThreadLocal變數的this引用,值為新增的本地變數值 if (map != null) map.set(this, value); //(4)如果map為null,說明首次新增,需要首先創建出對應的map else createMap(t, value); }
ThreadLocalMap getMap(Thread t) { return t.threadLocals; //獲取執行緒自己的變數threadLocals,並繫結到當前呼叫執行緒的成員變數threadLocals上 }
void createMap(Thread t, T firstValue) { t.threadLocals = new ThreadLocalMap(this, firstValue); }
public T get() { //(1)獲取當前執行緒 Thread t = Thread.currentThread(); //(2)獲取當前執行緒的threadLocals變數 ThreadLocalMap map = getMap(t); //(3)如果threadLocals變數不為null,就可以在map中查詢到本地變數的值 if (map != null) { ThreadLocalMap.Entry e = map.getEntry(this); if (e != null) { @SuppressWarnings("unchecked") T result = (T)e.value; return result; } } //(4)執行到此處,threadLocals為null,呼叫該更改初始化當前執行緒的threadLocals變數 return setInitialValue(); }
private T setInitialValue() { //protected T initialValue() {return null;} T value = initialValue(); //獲取當前執行緒 Thread t = Thread.currentThread(); //以當前執行緒作為key值,去查詢對應的執行緒變數,找到對應的map ThreadLocalMap map = getMap(t); //如果map不為null,就直接新增本地變數,key為當前執行緒,值為新增的本地變數值 if (map != null) map.set(this, value); //如果map為null,說明首次新增,需要首先創建出對應的map else createMap(t, value); return value; } ``` 總結:
ThreadLocal
其實是一種用空間換時間的機制:ThreadLocal
內部的其實都是針對當前執行緒的ThreadLocalMap
做的操作,一個執行緒只有一個Thread
,一個Thread
只有一個ThreadLocalMap
,所以其內部儲存的資料都是執行緒隔離的。 而且在static final ThreadLocal<Looper> sThreadLocal = newThreadLocal<Looper>()
; 可以看到這個sThreadLocal
在所有執行緒中只有一個,所以獲取value
的時候key``都是同一個
,只是這個ThreadLocalMap
是在每個執行緒中有一份
,所以獲取的值是不同執行緒中的value
值sThreadLocal
同一個物件中,相當於一個統一入口,內部操作獲取value
的和設定value
都是針對當前執行緒來操作
的,所以在不用執行緒中獲取的是當前執行緒的值