先來一個本身畫的Handler機制總體流程圖,本文不會帶着你走一遍源碼,只會對重點須要注意的地方以及一些細節的處理作出解釋,讓你更好的瞭解Handler機制總體的運做
。 bash
也能夠說成 Looper 所在的線程,並非建立 Handler 的線程,Handler新建時持有的Looper在哪一個線程,最後Handler.handleMessage()就在哪一個線程執行
;由於Android系統不容許在非UI線程更新UI
,由於若是多個線程同時改變View的狀態會形成最終View狀態的不肯定性,若是給每一個View的操做都上鎖的話那麼勢必會形成性能的損耗,因此乾脆規定只能在UI線程去更新UI,而Handler就是用來進行線程切換操做的。
使用方法框架
class LooperThread extends Thread {
public Handler mHandler;
public void run() {
Looper.prepare();
mHandler = new Handler() {
public void handleMessage(Message msg) {
//TODO 定義消息處理邏輯.
}
};
Looper.loop();
}
}
複製代碼
主線程中可使用Handler的緣由是在ActivityThread中程序的入口main方法中調用了Looper.prepare();和Looper.loop();異步
Looper.prepareMainLooper();
ActivityThread thread = new ActivityThread();
thread.attach(false);
if (sMainThreadHandler == null) {
sMainThreadHandler = thread.getHandler();
}
if (false) {
Looper.myLooper().setMessageLogging(new
LogPrinter(Log.DEBUG, "ActivityThread"));
}
// End of event ActivityThreadMain.
Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
Looper.loop();
複製代碼
private static void prepare(boolean quitAllowed) {
//看當前線程是否已經過TL綁定對應的實例,有的話拋異常,因此prepare方法只容許調用一次
if (sThreadLocal.get() != null) {
throw new RuntimeException("Only one Looper may be created per thread");
}
//建立Looper對象,並經過TL創建與線程的綁定關係
sThreadLocal.set(new Looper(quitAllowed));
}
複製代碼
public void set(T value) {
Thread t = Thread.currentThread();//獲取當前線程
ThreadLocalMap map = getMap(t);//獲取當前線程所屬的ThreadLocalMap實例,鍵值對結構
if (map != null)
map.set(this, value); //以當前ThreadLocal做爲鍵,Looper做爲值創建綁定關係
else
createMap(t, value);
}
}
複製代碼
ThreadLocal.get方法async
public T get() {
Thread t = Thread.currentThread();//獲取當前線程
ThreadLocalMap map = getMap(t);//獲取當前線程所屬的ThreadLocalMap實例,鍵值對結構
if (map != null) {
ThreadLocalMap.Entry e = map.getEntry(this);//經過當前ThreadLocal做爲鍵取出對應的值
if (e != null) {
@SuppressWarnings("unchecked")
T result = (T)e.value;
return result;
}
}
return setInitialValue();
}
複製代碼
由於咱們是要讓每個線程都有且只有一個惟一的Looper實例,這時就可使用ThreadLocal給每一個線程綁定一個惟一實例的特性很方便的創建綁定關係。若是不採用ThreadLocal去實現,那麼只能使用一個LooperManager管理類而後經過其中的Map去統一管理,那麼這樣無疑是很麻煩的
。ThreadLocal只是做爲主鍵,若是是Thread做爲主鍵,那麼很顯然一個線程只能與一個對應的對象創建綁定關係,這顯然是很是不合理的。ide
以下幾點
:
public static void loop() {
final Looper me = myLooper(); //獲取TLS存儲的Looper對象 -->sThreadLocal.get()
final MessageQueue queue = me.mQueue; //獲取Looper對象中的消息隊列
Binder.clearCallingIdentity();
//確保在權限檢查時基於本地進程,而不是調用進程。
final long ident = Binder.clearCallingIdentity();
for (;;) { //進入loop的主循環方法
Message msg = queue.next(); //可能會阻塞
if (msg == null) { //沒有消息則退出循環,調用Looper.quit()方法後返回空的message,隨即退出
return;
}
Printer logging = me.mLogging;
if (logging != null) {
logging.println(">>>>> Dispatching to " + msg.target + " " +
msg.callback + ": " + msg.what);
}
msg.target.dispatchMessage(msg); //用於分發Message
if (logging != null) {
logging.println("<<<<< Finished to " + msg.target + " " + msg.callback);
}
final long newIdent = Binder.clearCallingIdentity();
msg.recycleUnchecked();
}
}
複製代碼
用於終止loop循環,主線程中不容許調用(能調用的話至關於主程序退出了,應用就直接掛掉了),子線程中要退出時須要主動調用,不然會形成子線程中一直處於死循環狀態沒法退出
。調用後會改變MessageQueue中的mQuitting標誌位,next方法中若是檢測到mQuitting爲true則直接返回null,loop方法中檢測到message是null則直接return終止死循環從而結束邏輯使得線程能夠退出。public void quit() {
mQueue.quit(false); //所有消息移除
}
public void quitSafely() {
mQueue.quit(true); //只移除沒有執行的消息
}
複製代碼
會根據Message發送時的時間戳肯定Message在MessageQueue中的位置。函數
msg.when
這個時間戳進行順序的排序,若是非延遲消息則msg.when爲系統當前時間,延遲消息則爲系統當前時間+延遲時間(如延遲發送3秒則爲SystemClock.uptimeMillis() + 3000)當咱們發送消息的時候其實最後都會調用到sendMessageAtTime
這個方法,這個方法其實最終會把你的Handler對象賦值給Message實體,咱們最終發送消息都是發送的Message實體,而後調用MessageQueue的enqueueMessage方法oop
public final boolean sendMessageDelayed(Message msg, long delayMillis)
{
if (delayMillis < 0) {
delayMillis = 0;
}
return sendMessageAtTime(msg, SystemClock.uptimeMillis() + delayMillis);
}
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);
}
複製代碼
boolean enqueueMessage(Message msg, long when) {
// 每個普通Message必須有一個target
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) { //正在退出時,回收msg,加入到消息池
msg.recycle();
return false;
}
msg.markInUse();
msg.when = when;
Message p = mMessages;
boolean needWake;
if (p == null || when == 0 || when < p.when) { //p爲null(表明MessageQueue沒有消息) 或者msg的觸發時間是隊列中最先的, 則進入該分支並將加入的message放入頭結點
msg.next = p;
mMessages = msg;
needWake = mBlocked; //當阻塞時須要喚醒
} else {
needWake = mBlocked && p.target == null && msg.isAsynchronous();
Message prev;
for (;;) { //開啓死循環遍歷message
prev = p;
p = p.next;
if (p == null || when < p.when) { //退出條件爲當前message的下一個節點爲null或者當前節點的message執行時間大於你放入message的執行時間
break;
}
if (needWake && p.isAsynchronous()) {
needWake = false;
}
}
msg.next = p; //進行賦值
prev.next = msg;
}
//消息沒有退出,咱們認爲此時mPtr != 0
if (needWake) {
nativeWake(mPtr); //往管道中寫數據,喚醒阻塞,nativePollOnce方法阻塞解除
}
}
return true;
}
複製代碼
Message的入列和出列實際上是一個很典型的**生產者-消費者模型**,其中使用了epoll機制,當沒有消息的時候會進行阻塞釋放CPU時間片避免死循環形成性能的浪費。
雖然是不斷循環取出頭結點的Message進行分發處理可是若是沒有消息時它是阻塞在 nativePollOnce這個native方法中的
,當咱們enqueue插入Message時會觸發nativeWake這個方法去喚醒
,從而nativePollOnce阻塞解除繼續遍歷MessageQueue取出頭結點去處理。因此在這兩個方法中使用了synchronized (this) {}同步機制,其中this爲MessageQueue對象,無論在哪一個線程,這個對象都是同一個
,由於Handler中的mQueue指向的是Looper中的mQueue,這樣防止了多個線程對同一個隊列的同時操做(如增長的同時正在輪詢獲取Message,有可能形成MessageQueue中最終結果的不肯定性)。Message next() {
final long ptr = mPtr;
if (ptr == 0) { //當消息循環已經退出,則直接返回
return null;
}
int pendingIdleHandlerCount = -1; // 循環迭代的首次爲-1
int nextPollTimeoutMillis = 0;
for (;;) {
if (nextPollTimeoutMillis != 0) {
Binder.flushPendingCommands();
}
//阻塞操做,當等待nextPollTimeoutMillis時長,或者消息隊列被喚醒,都會返回
//nextPollTimeoutMillis 爲-1,一直阻塞,在調用nativeWake(enqueue Message或Looper.quit()退出Looper)時會被喚醒解除阻塞
//nextPollTimeoutMillis 爲0,不阻塞
//nextPollTimeoutMillis 爲>0,阻塞到對應時間後解除,如爲10000則阻塞十秒後解除,用於處理延遲消息
nativePollOnce(ptr, nextPollTimeoutMillis);
synchronized (this) {
final long now = SystemClock.uptimeMillis();
Message prevMsg = null;
Message msg = mMessages;
if (msg != null && msg.target == null) {
//當消息Handler爲空時,查詢MessageQueue中的下一條異步消息msg,則退出循環。
do {
prevMsg = msg;
msg = msg.next;
} while (msg != null && !msg.isAsynchronous());
}
if (msg != null) {
if (now < msg.when) {
//說明是延遲消息,計算延遲的時間
nextPollTimeoutMillis = (int) Math.min(msg.when - now, Integer.MAX_VALUE);
} else {
// 獲取一條消息,並返回
mBlocked = false;
if (prevMsg != null) {
prevMsg.next = msg.next;
} else {
mMessages = msg.next;
}
msg.next = null;
//設置消息的使用狀態,即flags |= FLAG_IN_USE
msg.markInUse();
return msg; //成功地獲取MessageQueue中的下一條即將要執行的消息
}
} else {
//沒有消息,阻塞
nextPollTimeoutMillis = -1;
}
//消息正在退出,返回null
if (mQuitting) {
dispose();
return null;
}
//當消息
複製代碼
當遍歷出Message後Message會獲取其中的Handler並調用Handler的dispatchMessage進行分發,這時也會有三個優先級。post
一般咱們能夠利用 Callback 這個攔截機制來攔截 Handler 的消息,場景如:Hook ActivityThread.mH,在 ActivityThread 中有個成員變量 mH ,它是個 Handler,又是個極其重要的類,幾乎全部的插件化框架都使用了這個方法。)
public void dispatchMessage(Message msg) {
if (msg.callback != null) { //callback是一個runnable對象
handleCallback(msg);
} else {
if (mCallback != null) { //mCallback是新建Handler時傳進去的Callback接口
if (mCallback.handleMessage(msg)) {
return;
}
}
handleMessage(msg); //默認空實現,通常咱們會本身複寫實現這個方法
}
}
private static void handleCallback(Message message) {
message.callback.run();
}
複製代碼
public final class MainThread {
private MainThread() {
}
private static final Handler HANDLER = new Handler(Looper.getMainLooper());
public static void run(@NonNull Runnable runnable) {
if (isMainThread()) {
runnable.run();
}else{
HANDLER.post(runnable);
}
}
public static boolean isMainThread() {
return Looper.myLooper() == Looper.getMainLooper();
}
}
複製代碼
Handler的實質其實就是共享內存
,咱們看一個例子。性能
public class Demo {
List mList= new ArrayList()<Message>;
public static void main(String[] args) {
//子線程
new Thread(new Runnable() {
@Override
public void run() {
try {
Thread.sleep(2000l);
mList.add(new Message());
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}).start();
//主線程開啓死循環不斷遍歷list取頭結點
for (;;) {
//主線程中處理
Message message = mList.get(0);
if (message != null) {
//處理
}
}
}
}
複製代碼
咱們爲了將數據最終從子線程切換到主線程中去其實只要拿到mList這個實例便可,這個mList對應的其實就是MessageQueue,而咱們要獲取MessageQueue只要獲取對應的Looper便可,當咱們Handler新建的時會根據Handler所在線程獲取到其線程正在輪詢消息的Looper對象,Handler中的mQueue指向的是其所在線程的Looper中的mQueue(固然也能夠手動指定一個其餘線程的Looper,不指定的話默認爲當前線程的Looper),由此即可在發送Message時將任務放到Looper所在線程中處理。
ui
public Handler(Callback callback, boolean async) {
...
mLooper = Looper.myLooper(); //threadLocal.get獲取線程對應的Looper
if (mLooper == null) {
throw new RuntimeException(
"Can't create handler inside thread that has not called Looper.prepare()");
}
mQueue = mLooper.mQueue; //經過Looper獲取MessageQueue
mCallback = callback;
mAsynchronous = async;
}
複製代碼
首先咱們來看形成ANR的緣由:
咱們再來看一下APP的入口ActivityThread的main方法:
public static void main(String[] args) {
...
Looper.prepareMainLooper();
ActivityThread thread = new ActivityThread();
thread.attach(false);
if (sMainThreadHandler == null) {
sMainThreadHandler = thread.getHandler();
}
Looper.loop();
throw new RuntimeException("Main thread loop unexpectedly exited");
}
複製代碼
咱們知道Android 的是由事件驅動的,looper.loop() 不斷地接收事件、處理事件,每個點擊觸摸或者說Activity的生命週期都是運行在 Looper的控制之下,若是它中止了,應用也就中止了。真正的阻塞是由於輪詢出message後在處理message消息的時候因爲執行了耗時操做致使了ANR,而不是死循環致使的阻塞,沒有消息處理的時候消息隊列是阻塞在nativePollOnce方法中的,**這個方法使用的是epoll管道機制,Linux底層執行後會釋放CPU避免不斷死循環形成的CPU浪費。**
當咱們用Handler發送延時消息時,若是在延時期間用戶關閉了 Activity,那麼該 Activity 會泄露。
這個泄露是由於 Message 會持有 Handler,而又由於 Java 的特性,內部類會持有外部類,使得 Activity 會被 Handler 持有,這樣最終就致使 Activity 泄露。 解決該問題的最有效的方法是:將 Handler 定義成靜態的內部類(靜態內部類不會持有外部類引用,可是靜態內部類調用不到外部類的非靜態屬性和方法,因此咱們須要在內部類中使用弱引用持有Activity,使用弱引用調用到Activity中的方法),並及時移除全部消息。 泄漏時的引用鏈 Activity->Handler->Message->MessageQueue ,延遲消息會一直在MessageQueue中等待處理,在等待的過程當中有可能會形成內存泄漏。
示例代碼以下:
private static class SafeHandler extends Handler {
private WeakReference<HandlerActivity> ref;
public SafeHandler(HandlerActivity activity) {
this.ref = new WeakReference(activity);
}
@Override
public void handleMessage(final Message msg) {
HandlerActivity activity = ref.get();
if (activity != null) {
activity.handleMessage(msg);
}
}
}
複製代碼
而且再在 Activity.onDestroy() 前移除消息,加一層保障:
@Override
protected void onDestroy() {
safeHandler.removeCallbacksAndMessages(null);
super.onDestroy();
}
複製代碼
這樣雙重保障,就能徹底避免內存泄露了。 注意:單純的在 onDestroy 移除消息並不保險,由於 onDestroy 並不必定執行(如報異常)。
從下邊MessageQueue的源碼可知道,IdleHandler即在MessageQueue應該被阻塞以前去調用(固然前提是你要講自定義的IdleHandler加入到集合中)
。 IdleHandler接口表示當MessageQueue發現當前沒有更多消息能夠處理的時候,
則順便乾點別的事情的callback函數(即若是發現idle了, 那就找點別的事幹). callback函數有個boolean的返回值, 表示是否keep. 若是返回false, 則它會在調用完畢以後從mIdleHandlers中移除. IdleHandler 能夠用來提高提高性能,主要用在咱們但願可以在當前線程消息隊列空閒時作些事情(譬如UI線程在顯示完成後,若是線程空閒咱們就能夠提早準備其餘內容)的狀況下,不過最好不要作耗時操做
。
Message next() {
final long ptr = mPtr;
if (ptr == 0) { //當消息循環已經退出,則直接返回
return null;
}
int pendingIdleHandlerCount = -1; // 循環迭代的首次爲-1
int nextPollTimeoutMillis = 0;
for (;;) {
if (nextPollTimeoutMillis != 0) {
Binder.flushPendingCommands();
}
//阻塞操做,當等待nextPollTimeoutMillis時長,或者消息隊列被喚醒,都會返回
//nextPollTimeoutMillis 爲-1,一直阻塞,在調用nativeWake(enqueue Message或Looper.quit()退出Looper)時會被喚醒解除阻塞
//nextPollTimeoutMillis 爲0,不阻塞
//nextPollTimeoutMillis 爲>0,阻塞到對應時間後解除,如爲10000則阻塞十秒後解除,用於處理延遲消息
nativePollOnce(ptr, nextPollTimeoutMillis);
synchronized (this) {
final long now = SystemClock.uptimeMillis();
Message prevMsg = null;
Message msg = mMessages;
if (msg != null && msg.target == null) {
//當消息Handler爲空時,查詢MessageQueue中的下一條異步消息msg,則退出循環。
do {
prevMsg = msg;
msg = msg.next;
} while (msg != null && !msg.isAsynchronous());
}
if (msg != null) {
if (now < msg.when) {
//說明是延遲消息,計算延遲的時間
nextPollTimeoutMillis = (int) Math.min(msg.when - now, Integer.MAX_VALUE);
} else {
// 獲取一條消息,並返回
mBlocked = false;
if (prevMsg != null) {
prevMsg.next = msg.next;
} else {
mMessages = msg.next;
}
msg.next = null;
//設置消息的使用狀態,即flags |= FLAG_IN_USE
msg.markInUse();
return msg; //成功地獲取MessageQueue中的下一條即將要執行的消息
}
} else {
//沒有消息,阻塞
nextPollTimeoutMillis = -1;
}
//消息正在退出,返回null
if (mQuitting) {
dispose();
return null;
}
//若是當前MessageQueue頭結點爲空(沒有消息要處理了)或者當前系統時間<消息觸發時間
if (pendingIdleHandlerCount < 0 && (mMessages == null || now < mMessages.when)) {
//看是否加入了idleHandler
pendingIdleHandlerCount = mIdleHandlers.size();
}
if (pendingIdleHandlerCount <= 0) {
//沒有idle handlers 須要運行,則循環並等待。
mBlocked = true;
continue;
}
if (mPendingIdleHandlers == null) {
mPendingIdleHandlers = new IdleHandler[Math.max(pendingIdleHandlerCount, 4)];
}
mPendingIdleHandlers = mIdleHandlers.toArray(mPendingIdleHandlers);
}
//只有第一次循環時,會運行idle handlers,執行完成後,重置pendingIdleHandlerCount爲0.
for (int i = 0; i < pendingIdleHandlerCount; i++) {
final IdleHandler idler = mPendingIdleHandlers[i];
mPendingIdleHandlers[i] = null; //去掉handler的引用
boolean keep = false;
try {
//若是queueIdle()返回false則當前idlehandler只能運行一次
keep = idler.queueIdle(); //idle時執行的方法
} catch (Throwable t) {
Log.wtf(TAG, "IdleHandler threw exception", t);
}
if (!keep) {
synchronized (this) {
//queueIdle返回false,移除idlehandler
mIdleHandlers.remove(idler);
}
}
}
//重置idle handler個數爲0,以保證不會再次重複運行
pendingIdleHandlerCount = 0;
//當調用一個空閒handler時,一個新message可以被分發,所以無需等待能夠直接查詢pending message.
nextPollTimeoutMillis = 0;
}
}
複製代碼
同步屏障是由系統發送,通常用於刷新UI(如16ms刷新一次界面)。當設置了同步屏障以後,next函數將會忽略全部的同步消息,返回異步消息。換句話說就是,設置了同步屏障以後,Handler只會處理異步消息。再換句話說,同步屏障爲Handler消息機制增長了一種簡單的優先級機制,異步消息的優先級要高於同步消息。
Android應用框架中爲了更快的響應UI刷新事件在ViewRootImpl.scheduleTraversals中使用了同步屏障
void scheduleTraversals() {
if (!mTraversalScheduled) {
mTraversalScheduled = true;
//設置同步障礙,確保mTraversalRunnable優先被執行
mTraversalBarrier = mHandler.getLooper().getQueue().postSyncBarrier();
//內部經過Handler發送了一個異步消息
mChoreographer.postCallback(
Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);
if (!mUnbufferedInputDispatch) {
scheduleConsumeBatchedInput();
}
notifyRendererOfFramePending();
pokeDrawLockIfNeeded();
}
}
複製代碼