Android系統分析之帶着問題看Handler

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相關變動?

答:

20 學習連接

一、換個姿式,帶着問題看Handler

二、Android Handler:手把手帶你深刻分析 Handler機制源碼

相關文章
相關標籤/搜索