1 Handler問題三連:是什麼?有什麼用?爲何要用,不用行不行?
1.1 Handler是什麼?
答:Handler是Android FrameWork架構中的一個基礎組件;android
1.2 Handler有什麼用?
答:把子線程中的UI更新信息傳遞給主線程(UI線程),以此完成UI更新操做;安全
1.3 Handler爲何要用,不用行不行?
答:不行,Handler是Android在設計之初就封裝的一套消息建立、傳遞、處理機制。Android要求在主線程(UI線程)中更新UI。網絡
2 真的只能在主(UI)線程中更新UI嗎?
答:Android要求在主線程(UI線程)中更新UI,是要求,不是規定,硬要在子線程中更新UI也是能夠的。多線程
2.1 爲何可在一個子線程中建立一個對話框,並可更新對話框的UI,而不能更新主線程的UI?
答:Android的UI更新(GUI)被設計成了單線程,子線程可更新子線程建立的UI、不能更新主線程建立的UI。案例以下:架構
private lateinit var textView: TextView thread { Looper.prepare() val dialog = AlertDialog.Builder(this) .apply { setIcon(R.drawable.ic_launcher) setTitle("子線程建立的對話框") setCancelable(true) setNegativeButton("子線程更新 主線程建立的UI", object : DialogInterface.OnClickListener { override fun onClick(dialog: DialogInterface?, which: Int) { // 拋出異常:android.view.ViewRootImpl$CalledFromWrongThreadException: Only the original thread that created a view hierarchy can touch its views. // 翻譯後是:只有建立這個view的線程才能操做這個view; textView.text = "子線程更新 主線程建立的UI ${Thread.currentThread().name}" } }) setPositiveButton("子線程更新 子線程建立的UI", object : DialogInterface.OnClickListener { override fun onClick(dialog: DialogInterface?, which: Int) { setTitle("子線程更新 子線程建立的UI ${Thread.currentThread().name}") } }) }.create() dialog.show() Looper.loop() }
2.2 爲何直接在子線程中更新主線程建立的UI,沒有報錯;而若是延遲300毫秒後就報錯了?
答:Android系統在onResume()時會檢查:只有建立這個view的線程才能操做這個view,不然拋出異常。①ViewRootImp在onCreate()時還沒建立,因此子線程中更新了 主線程建立的UI。②在onResume()時,ActivityThread的handleResumeActivity()執行後才建立ViewRootImp,而後調用requestLayout(),走到checkThread()檢查時拋出異常。案例以下:併發
fun onCreate(savedInstanceState: Bundle?) { thread { // 直接在子線程中更新了 主線程建立的UI,卻沒有報錯: textView.text = "子線程更新UI ${Thread.currentThread().name}" } thread { // 二、加上休眠300毫秒,程序就崩潰了 Thread.sleep(300) textView.text = "子線程更新UI ${Thread.currentThread().name}" } }
void checkThread() { if (mThread != Thread.currentThread()) { throw new CalledFromWrongThreadException ("Only the original thread that created a view hierarchy can touch its views."); } }
3 Android UI更新機制(GUI) 爲什麼設計成了單線程的?
答:由於多線程對同一個UI控件操做,容易形成不可控的錯誤。而必須解決這種多線程安全問題,簡單的作法是加鎖,不是加一個,而是每層加鎖(用戶代碼–GUI頂層–GUI底層),可是這意味着更多耗時以及UI更新效率低下,而若是每層共用一把鎖的話,其實就是單線程。所以,Android沒有采用線程鎖,而是採用單線程消息隊列機制,實現了一個僞鎖。app
4 真的不能在主(UI)線程中執行網絡操做嗎?
答:能。一般,網絡請求在主線程進行,會拋出異常NetworkOnMainThreadException,由於Android 2.3引入用於檢測兩大問題:ThreadPolicy(線程策略) 和 VmPolicy(VM策略)。在onCreate()的setContentView()後加上permitNetWork(),把嚴苟模式的網絡監測關了,就能夠在主線程執行網絡請求了。async
5 Handler怎麼用?
答:有兩個方式,一是sendMessage() + handleMessage();二是post(runnable)。其中,post(Runnable r)調用的是sendMessageDelayed(getPostMessage®, 0)發送消息,只不過延遲秒數爲0。案例以下:ide
// 方式-:sendMessage() + handleMessage() // 步驟1:在主線程中 經過匿名內部類 建立Handler類對象 val mhandler = Handler() { // 經過複寫handlerMessage()從而肯定更新UI的操做 override fun handleMessage(msg: Message) { super.handleMessage(msg) // 需執行的UI操做 } } // 步驟2:建立消息對象 val msg = Message.obtain() // 實例化消息對象 msg.what = 1 // 消息標識 msg.obj = "AA" // 消息內容存放 // 步驟3:在工做線程中 經過Handler發送消息到消息隊列中 mHandler.sendMessage(msg) // 步驟4:開啓工做線程(同時啓動了Handler)
// 方式二是post(runnable) // 步驟1:在主線程中建立Handler實例 val mhandler = Handler() // 步驟2:在工做線程中 發送消息到消息隊列中 & 指定操做UI內容,需傳入1個Runnable對象 mHandler.post(Runnable { // 需執行的UI操做 } }) // 步驟3:開啓工做線程(同時啓動了Handler)
5.1 那Runnable是怎麼變成Message的呢?
答:在getPostMessage()方法中,經過Message.obtain()獲取一個新的Mesage對象,把Runnable變量的值賦值給callback屬性。函數
private static Message getPostMessage(Runnable r) { Message m = Message.obtain(); m.callback = r; return m; }
5.2 其餘兩個種在子線程中更新UI的方法?
答:activity.runOnUiThread(),和view.post()與view.postDelay();
6 爲何建議使用Message.obtain()來建立Message實例?
答:由於隨着事件不斷髮送,會頻繁大量建立消息對象,帶來內存消耗;所以使用Message.obtain()經過消息池複用消息對象,能夠避免重複建立對象,節約內存。
6.1 obtain()是怎麼複用消息對象的?
答:分析obtain()的邏輯(以下),加鎖判斷消息池是否爲空?不爲空,取消息池的鏈表表頭消息對象返回,正在使用標記爲0,吃容量-1;爲空,建立一個新的消息對象返回。Message池實際上是一個單鏈表。獲取消息池邏輯以下圖:
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(); }
6.2 消息對象時何時加到池中?
答:當消息對象被Looper分發完後,在loop()最後會調用msg.recycleUnchecked()函數,回收沒有被使用的消息對象。具體邏輯是:標記設置爲FLAG_IN_USE,表示正在使用,相關屬性重置;加鎖判斷消息池是否滿50,未滿,採用單鏈表頭插法把消息插入到鏈表表頭。回收消息邏輯以下圖:
void recycleUnchecked() { // ...... flags = FLAG_IN_USE; // 表示正在使用 when = 0; target = null; callback = null; data = null; synchronized (sPoolSync) { if (sPoolSize < MAX_POOL_SIZE) { // MAX_POOL_SIZE = 50 next = sPool; sPool = this; sPoolSize++; } } }
7 Handler涉及到的類有哪幾個?
答:主要有6個,分別是以下:
7 爲何子線程中不能夠直接new Handler(),而主線程中能夠?
答:在new Handler()時,調用Looper.myLooper()獲取當前線程的Looper對象,若線程無Looper對象則拋出異常,異常和邏輯以下圖所示。主線程在啓動時在ActivityThread的main函數中,調用Looper.prepareMainLooper()建立了Looper和MessageQueue對象,完成了初始化。而子線程還須要額外調用Looper.prepare()和Looper.loop()開啓輪詢,不然會報錯。
public Handler(Callback callback, boolean async) { // 1. 指定Looper對象 mLooper = Looper.myLooper(); if (mLooper == null) { throw new RuntimeException("Can't create handler inside thread that has not called Looper.prepare()"); } // 2. handler對象綁定Looper的消息隊列對象(MessageQueue) mQueue = mLooper.mQueue; }
7.1 具體的Looper.prepareMainLooper()的初始化過程是?
定位到: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"); }
定位到:Looper.prepareMainLooper();
public static void prepareMainLooper() { prepare(false); synchronized (Looper.class) { if (sMainLooper != null) { throw new IllegalStateException("The main Looper has already been prepared."); } sMainLooper = myLooper(); } }
定位到:Looper → prepare函數
public static final 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對象,並存放在ThreadLocal變量中 }
定位到:Looper → Looper構造函數
private Looper(boolean quitAllowed) { mQueue = new MessageQueue(quitAllowed); // 建立1個Looper對象時,建立一個與之綁定的消息隊列對象 mRun = true; mThread = Thread.currentThread(); // Looper與線程綁定 }
7.2 mQuitAllowed變量,直譯「退出容許」,具體做用是?
答:用來防止開發者手動終止消息隊列,中止Looper循環。定位到:MessageQueue → quit函數:
void quit(boolean safe) { if (!mQuitAllowed) { throw new IllegalStateException("Main thread not allowed to quit."); } synchronized (this) { if (mQuitting) { return; } mQuitting = true; if (safe) { removeAllFutureMessagesLocked(); } else { removeAllMessagesLocked(); } // We can assume mPtr != 0 because mQuitting was previously false. nativeWake(mPtr); } }
7.3 一個線程中初始化多個handler,會產生多少個Looper?主線程和子線程Looper是同一個麼?
答:不會,一個線程只能對應一個Looper,主線程和子線程Looper不是同一個。在Looper.prepare()方法中會調用sThreadLocal.get()獲取當前線程的Looper,當屢次調用不爲空時會拋出異常。邏輯以下:
public static final 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對象,並存放在ThreadLocal變量中 }
8 ThreadLocal是如何將Looper對象存放在Thread線程裏,並解決併發訪問的衝突問題的?
答:ThreadLocal是線程局部變量,爲每一個線程提供一個獨立變量副本。每一個線程內部都維護了一個ThreadLocalMap,變量名是threadLocals,這個map的key是ThreadLocal。在存值時,經過當前線程取出線程的變量threadLocals,以ThreadLocal爲key,存儲一個值;在獲取值時,過當前線程取出線程的變量threadLocals,以ThreadLocal爲key,獲取一個值。由於存和取都在線程本身的變量中操做,因此不存在線程安全問題。
定位到:ThreadLocal → set函數:
public void set(T value) { // 取出當前線程 Thread t = Thread.currentThread(); // 經過當前線程取出線程的成員(ThreadLocalMap)threadLocals(2) ThreadLocalMap map = getMap(t); if (map != null) { // 向ThreadLocalMap,以ThreadLocal爲key,存儲一個值 (---->源碼看:2.5 再深刻分析) map.set(this, value); } else { // 建立Thread成員threadLocals(3) createMap(t, value); } }
定位到:ThreadLocal → getMap函數:
ThreadLocalMap getMap(Thread t) { // 取出Thread的成員 return t.threadLocals; }
定位到:ThreadLocal → get函數:
public T get() { // 取出當前線程 Thread t = Thread.currentThread(); // 不一樣線程有不一樣ThreadLocalMap,就是有不一樣的副本 ThreadLocalMap map = getMap(t); if (map != null) { // 根據key獲取table中的Entry ThreadLocalMap.Entry e = map.getEntry(this); if (e != null) { return (T)e.value; } } return setInitialValue(); }
10 主線程給子線程的Handler發送消息怎麼寫?
答:普通實現方法中,子線程初始化Handler,存在報空指針風險,由於:多線程併發的問題,當主線程執行到sendMessage時,子線程的Handler尚未初始化。所以,最優方法是:經過HandlerThread獲取子線程的Looper,再在主線程初始化Handler,並傳入子線程的Looper。
// 在主線程中給子線程的Handler發送信息 fun mainSendMessageToThread(view: View) { val thread = LooperThread() thread.start() // 1.報空指針,由於:多線程併發的問題,當主線程執行到sendEmptyMessage時,子線程的Handler尚未初始化 // thread.mHandler!!.sendEmptyMessage(0x123) // 2.解決方法是:主線程延時給子線程發消息,等待子線程的Handler完成初始化 Handler().postDelayed(Runnable { thread.mHandler!!.sendMessage(obtainSyncMessage(0x123, "同步消息")) }, 1000) // 3.更優解決方法是:經過HandlerThread獲取子線程的Looper,再在主線程初始化Handler,並傳入子線程的Looper initHandlerThread() } // 經過HandlerThread獲取子線程的Looper,再在主線程初始化Handler,並傳入子線程的Looper private fun initHandlerThread() { val handlerThread = HandlerThread("Thread-1") handlerThread.start() val handler = MyHandler(handlerThread.looper) handler.sendMessage(obtainSyncMessage(0x123, "同步消息")) } /** * 子線程的Handler接收信息 */ private inner class LooperThread : Thread() { var mHandler: Handler? = null override fun run() { Looper.prepare() // 存在報空指針風險,由於:多線程併發的問題,當主線程執行到sendEmptyMessage時,子線程的Handler尚未初始化 mHandler = MyHandler(Looper.myLooper()) Looper.loop() } }
11 HandlerThread實現的核心原理?
答:①HandlerThread = 繼承線程 + 封裝Looper;②getLooper()加鎖死循環wait()等待堵塞線程;③線程的run方法中,加鎖等待當前線程的Looper對象建立成功,再notifyAll()通知getLooper()中的wait()等待,說Looper對象已經建立成功;④等待喚醒後,getLooper()返回在run中建立的Looper對象。
10 當你用Handler發送一個消息,發生了什麼?
答:
11 Looper是怎麼循環揀隊列裏的消息的?
答:
12 分發給Handler的消息是怎麼處理的?
答:
9 若是隻有一個looper,looper如何區分handler,handler發送了消息會不會致使Looper錯亂,最終不知道誰處理。
答:
13 IdleHandler是什麼?有什麼做用?應用場景是什麼?
答:
14 Looper在主線程中死循環,爲啥不會ANR?
答:
15 Handler泄露的緣由及正確寫法?
答:
16 Handler中的同步屏障機制是什麼?同步屏障使用場景?
答:
17 Android 11 Handler相關變動?
答: