上文本身一個Handler機制對 Handler 機制進行了一番探索和思考,對 Handler 的設計意圖有了本身的認識和思考,那麼這篇文章咱們將從 android 系統 Handler 機制的源碼出發,來看一看 android 系統 Handler 機制的設計,對比一下上文中本身寫的 Handler 機制。html
闡述邏輯以下:java
知道爲何 Activity 的 onCreate() / onStart() 等生命週期函數都是運行在主線程當中嗎?在下面的主線程 Handler 的建立和消息處理過程分析中,這個問題將會獲得解答。android
主線程的建立和使用方式和自定義一個Handler機制文章中的 Handler 機制使用方式同樣,首先建立 Looper ,而後向 Looper 中的消息容器發送消息,最後 Looper 開啓死循環獲取消息/處理消息。c++
//ActivityThread.java
public static void main(String[] args) {
......
// 建立 Looper
Looper.prepareMainLooper();
// App 啓動流程入口,會發送向 Looper 的消息隊列中添加一系列的消息以啓動 App。
ActivityThread thread = new ActivityThread();
thread.attach(false, startSeq);
// 獲取 Handler
if (sMainThreadHandler == null) {
sMainThreadHandler = thread.getHandler();
}
// 啓動 looper,開啓死循環處理消息
Looper.loop();
......
}
複製代碼
下面就直接看 looper 的建立過程,這裏的重點有兩處。git
// Looper.java
// 給當前線程建立 Looper,爲何說給當前線程?
public static void prepareMainLooper() {
// 建立 Looper ,false 表示該 Looper 不容許開發者調用退出函數退出 looper 死循環。
prepare(false);
synchronized (Looper.class) {
if (sMainLooper != null) {
throw new IllegalStateException("The main Looper has already been prepared.");
}
sMainLooper = myLooper();
}
}
private static void prepare(boolean quitAllowed) {
// 若是該線程中已經有一個 Looper 就不容許建立多個 Looper。
if (sThreadLocal.get() != null) {
throw new RuntimeException("Only one Looper may be created per thread");
}
// 建立一個 Looper 實例,傳入是否容許該 Looper 退出死循環的參數,並添加到該線程的 ThreadLocal 中
sThreadLocal.set(new Looper(quitAllowed));
}
// Looper 構造函數
private Looper(boolean quitAllowed) {
// 初始化 MessageQueue ,傳入是否容許退出函數
mQueue = new MessageQueue(quitAllowed);
mThread = Thread.currentThread();
}
// 獲取該線程的 Looper
public static @Nullable Looper myLooper() {
return sThreadLocal.get();
}
複製代碼
loop() 方法中也有幾個重點。面試
public static void loop() {
// 死循環
for (;;) {
// 重點!!!!重點!!!!重點!!!!!!該 queue 即爲 Looper 構造函數中的 MessageQueue
// 該 next() 方法表示從 MessageQueue 中獲取消息,若是沒有消息在該函數中會調用 Linux 的
// epoll 機制陷入沉睡狀態,等待有消息加入 MessageQueue 時被喚醒。
Message msg = queue.next(); // might block
if (msg == null) {
// 從 MessageQueue 中獲取不到消息就表示退出死循環,爲空的具體的能夠看 MessageQueue.quit() 函數。
return;
}
// 消息被執行前輸出消息,能夠在 FPS 監控中經常使用
final Printer logging = me.mLogging;
if (logging != null) {
logging.println(">>>>> Dispatching to " + msg.target + " " +
msg.callback + ": " + msg.what);
}
......
// 重點!!! target 爲向 MessageQueue 發送消息的 Handler 實例。
// 處理 msg 消息。
msg.target.dispatchMessage(msg);
......
// 消息執行完成後輸出信息,對應以前的 println()
if (logging != null) {
logging.println("<<<<< Finished to " + msg.target + " " + msg.callback);
}
}
}
複製代碼
next() 方法中的重點有幾個。安全
epoll 機制。 關於 epoll 機制,這個主要是 Linux 方面的知識,我也仍是隻知其一;不知其二,這裏推薦系列文章,寫的能夠,值得一看。markdown
如何處理延時 Message 消息和按順序執行?app
這裏分爲兩步處理的,第一步是在向 MessageQueue 中會根據消息加入時間和延時時間進行排序,加入時間在前和延時時間短的 Message 排在隊列的前面。第二步是在 MessageQueue 獲取到要執行的 Message 以後,會判斷其執行時間是不是當前,若不是,則會計算時間差,使用該時間差調用 epoll 機制進入定時睡眠。框架
同步屏障的概念以及同步消息處理。
爲了優先執行異步 Message ,如:渲染 UI 、響應用戶操做等消息所以設計了同步障礙這個機制。當有異步 Message 須要優先執行時,先向 MessageQueue 的頭部插入一條 target(即處理消息的Handler) 爲空的消息(設置同步障礙),並將須要執行的 Message 的是不是異步消息的標誌設置爲 true(isAsynchronous()返回值爲 true)。在後續的 next() 方法中,當碰到同步障礙時就會忽略同步消息,選擇性的優先執行異步消息。
處理 looper 退出。
退出的處理也是在 MessageQueue 中處理,由 MQ 的退出觸發 Looper 的退出。主要流程是移除隊列中全部 Msg。由是否移除即將處理的 msg 爲去區別點,分兩種方法進行消息移除,一種是保留即將執行的 msg,消息執行完畢以後退出,一種是直接移除全部 msg,直接退出。
IdleHandler 的使用。
當 MessageQueue 中後續沒有消息執行時,若是有 IdlerHander 就是執行 IdlerHander,即當 Handler 空閒時執行。
另外咱們要注意,同一 IderHander 會被觸發屢次,咱們須要處理這種狀況。這裏舉例一種,實現 IderHandler 接口,queueIdle() 返回 false ,待執行完畢後,即會移除該 IderHandler。
// MessageQueue.java
Message next() {
// mPtr 爲 c++ 層指針,當 MessageQueue 須要退出時,會將該值賦爲 0
final long ptr = mPtr;
if (ptr == 0) {
return null;
}
// IdleHandler 數量
int pendingIdleHandlerCount = -1; // -1 only during first iteration
// 利用 epoll 機制沉睡的時間
int nextPollTimeoutMillis = 0;
for (;;) {
// 利用 epoll 機制進入沉睡,
nativePollOnce(ptr, nextPollTimeoutMillis);
synchronized (this) {
// 嘗試獲取 Message
final long now = SystemClock.uptimeMillis();
Message prevMsg = null;
Message msg = mMessages;
// 判斷當前 Message 是不是同步障礙,標誌就是 msg.target 爲空
if (msg != null && msg.target == null) {
// 尋找同步消息執行,標誌是 msg.isAsynchronous()
do {
prevMsg = msg;
msg = msg.next;
} while (msg != null && !msg.isAsynchronous());
}
if (msg != null) {
// msg.when 會在消息被髮送時進行賦值
if (now < msg.when) {
// 當前消息是否到了執行時刻,沒到將 epoll 沉睡時間計算出來
nextPollTimeoutMillis = (int) Math.min(msg.when - now, Integer.MAX_VALUE);
} else {
// 到了?返回目標 msg ,同時安排好下一個 msg。
mBlocked = false;
if (prevMsg != null) {
prevMsg.next = msg.next;
} else {
mMessages = msg.next;
}
msg.next = null;
if (DEBUG) Log.v(TAG, "Returning message: " + msg);
msg.markInUse();
return msg;
}
} else {
// 沒有更多的 msg 將該值賦值爲-1,將會在 nativePollOnce() 函數中陷入沉睡等待喚醒。
nextPollTimeoutMillis = -1;
}
// 若是啓動調用了退出函數(主線程不容許退出),則處理退出流程,返回 Null 值,在 Looper 中會獲得 null 值會直接退出 loop() 中的死循環,該線程若無其它操做,運行完畢後將會天然退出。
if (mQuitting) {
dispose();
return null;
}
// 注意!!!能運行到這裏的時間點,當 MQ 中沒有更多消息時纔會運行到這裏
// 計算 IdleHandler 的數量,準備觸發
if (pendingIdleHandlerCount < 0
&& (mMessages == null || now < mMessages.when)) {
pendingIdleHandlerCount = mIdleHandlers.size();
}
if (pendingIdleHandlerCount <= 0) {
// 沒有 IdleHandler 執行,進行下一次循環。
mBlocked = true;
continue;
}
...
}
// 循環執行已添加的 IdleHandler
for (int i = 0; i < pendingIdleHandlerCount; i++) {
final IdleHandler idler = mPendingIdleHandlers[i];
mPendingIdleHandlers[i] = null; // release the reference to the handler
boolean keep = false;
...
// 判斷是否移除該 IdleHandler
keep = idler.queueIdle();
if (!keep) {
synchronized (this) {
mIdleHandlers.remove(idler);
}
}
}
...
}
}
複製代碼
在上面咱們已經說明了什麼是同步障礙、何爲添加同步障礙。而設計同步障礙的緣由就是爲了優先執行某些消息,這裏的表明就是系統中更新 UI ,這裏能夠去看ViewRootImpl.scheduleTraversals()
函數(View三大步驟的地方哦)和 UI 更新相關機制,這裏就不作展開。這裏咱們來看 MessageQueue.postSyncBarrier()
函數,這裏是添加同步障礙的地方,同時結合上一步驟處理 msg 的邏輯來看。
另外一個設置異步消息就十分簡單了,調用 Message.setAsynchronous()
函數,傳入 true 便可(處理邏輯見上一步驟分析)。
// MessageQueue.java
private int postSyncBarrier(long when) {
synchronized (this) {
final int token = mNextBarrierToken++;
/// 重點!!!這裏的 msg 沒有 traget,在處理邏輯中有體現。
final Message msg = Message.obtain();
msg.markInUse();
msg.when = when;
msg.arg1 = token;
// 後面的操做就是將承擔同步障礙做用的 msg 添加到隊列頭
return token;
}
}
複製代碼
首先明確該類被設計出來的做用是什麼,解決了怎樣的問題。
ThreadLocal 類出如今 jdk 1.2 中,此時 java 的 synchronized 關鍵字提供的鎖機制還未優化(沒有鎖升級,直接重量級鎖)。當程序中某些變量只能被一個線程使用,若使用 synchronized 關鍵提供線程安全會增長系統開銷,而 ThreadLocal 就提供了在不加鎖的狀況下,保證某個變量只能被一個線程訪問的機制。使用簡便,只要 set() / get() 一番便可。
關於使用內存泄露的問題
該問題是面試中常問。因爲 ThreadLocal.ThreadLocalMap 保存數據構建 Entry 時 key 值(TheadLocal)使用了弱引用,看成爲 key 的 ThreadLocal 被回收後,當時持有 ThreadLocal 還未中止,這時存在這 Thread.threadLocals 指向 value 的一條引用,致使 key 已經爲空的 value 對象實例沒法回收,出現內存泄露問題。
內存泄露問題推薦閱讀 > www.cnblogs.com/aspirant/p/…
Thradlocal 詳細分析 > www.cnblogs.com/fsmly/p/110…
在平常開發工做當中,可能是使用主線程的 Handler 作事情,不多會在其餘線程中使用。固然,想要使用也很簡單,只須要三步。
Looper.prepare()
函數建立 Looper / MessageQueue,使用該方法建立的 Looper 是能夠退出地。退出
直接調用 Looper.myLooper().quit()
或者 Looper.myLooper().quitSafely()
方法便可,關於二者的區別在上文中有闡述。
主線程 Handler 爲何不能退出?
不是不能退出,是開發者不能調用主線程 Looper 退出的方法,調用會拋出異常。
緣由在於,Looper 的死循環的做用不僅僅是處理消息,同時還保證了主線程無外力做用下永遠在運做的機制,iOS / Windows 等開發框架都有類似的機制。
Looper 死循環的退出即表示 App 主線程退出,App 將退出運行。
該類繼承自 Thread,內部建立了一個使用使用了 Handler 機制,start() 以後開啓了 Looper 的死循環處理消息,Looper 能夠退出,Message 消息再該線程中處理。能夠利用其執行有序的任務!
繼承自 Service 擁有 Serivce 的特性,同時內部使用率 HandlerThread,當 onStart() 函數觸發時,向 HandlerThread 中的 MessageQueue 中添加一條 Message ,自建的 Handler 處理完畢以後當即調用 stopSelf() 函數中止 Service 的運行。能夠利用其在後臺執行任務,執行完成以後不須要管理,自動中止。固然,記得和通常 Service 同樣清單文件註冊。
Only the original thread that created a view hierarchy can touch its views.
當咱們在子線程操做 UI 時系統會拋出這樣一個錯誤。首先明確,這個錯誤並非說不能在主線程更新 UI ,而是隻能在建立了 ViewRootImpl 實例的線程中更新 UI。拋出這個錯誤的是 ViewRootImpl 中的 checkThread()
函數,目的是檢測當前請求更新 UI 的線程是否是建立 ViewRootImpl 的線程,爲何須要檢測呢?緣由在於在調用 requestlayout()
函數後,會向當前線程的 Looper 中添加一條同步屏障,若當前更新 UI 的線程不是建立 ViewRootImpl 的線程,那麼該同步屏障不會起做用,那麼後續極有可能不回第一時間響應用戶操做更新 UI 。
其實子線程是能夠更改 UI 的,在 onCreate() / onResume() 函數中都是能夠的。具體緣由,能夠去看 ViewRootImpl 建立時機和 setContentView() 過程。
推薦文章:關於 TextView 子線程更新文字問題 / 爲何要 checkThread()
爲何主線程不能執行耗時操做?從設計上來考慮,單線程更新 UI ,若在 UI 線程執行耗時操做,會致使 UI 更新延遲。所以在 UI 線程(主線程)執行操做時,系統會添加耗時檢測機制,UI 線程超過必定時間無響應即會彈出咱們常見的 ANR 警告彈框。
關於這篇問題,強烈推薦 gityuan 大佬的系列文章。
Handler 存在內存泄露的問題主要是 Java 語言中,非靜態內部類和匿名內部類會持有外部類的引用,而這個外部類一般就是 Activity 了。當存在延時 Message 還未觸發或者有子線程運行耗時任務又持有 Handler 引用,就極有可能引起內存泄露。
這裏主要當 Message 建立頻繁時,使用優化手段進行優化。也就是 java 語言中優化對象的幾種方式。
Message 中的 obtain() 函數已經提供了對象池供咱們優化使用。
這個就推薦文章了,我也還在研究學習中,若是有了本身的理解,會有文章出來說講的。
以前有講過哦,不記得了????
滑上去再嘍一眼吧!
首先,Looper 和 MessageQueue 中都是使用率 synchronized
來保證線程安全。
線程切換?線程之間共享資源,向目標線程中的 MessageQuque 中添加 Message ,目標線程進行處理就完成了線程切換。
經過 Looper 獲取到 MessageQueue 添加 IdleHander 便可。
該機制是當 MessageQueue 中沒有 Message 了(即空閒時)或者處在一個延時消息的執行時,就會調用 IdleHander 執行。注意!這個執行時機是比較模糊的。
當 IdleHander 的 queueIdle()
返回 true 時表示保留該 IdlerHander ,下次繼續執行,爲 false 則移除。
系統在以下幾個地方使用了
推薦閱讀 www.wanandroid.com/wenda/show/…
Handler 機制的設計目的是提供一種單線程消息處理機制,而 UI 框架的單線程更新機制又使得 Handler 自然的能完成能承擔這個任務,這個呢是 Handler 的重點,是 Handler 的本質。清楚了本質,那麼諸如其餘的同步屏障、異步消息和 ThreadLocal 等,爲了實現這個本質中所採起的方法,也能很快的分而治之的弄明白。
總之,對於 adnroid frmework 的學習之路,我仍是推薦本身思考、依靠源碼、踩在巨人的肩膀上的方法路線進行學習。