当前位置:网站首页>源碼解析 Handler 面試寶典
源碼解析 Handler 面試寶典
2022-06-24 13:34:00 【華為雲】
@[TOC](Handler 面試源碼解析面試寶典
前言
在Android 的中,高級面試中,我們經常會被問到Handler 相關的知識點,而且占重比例還比較大,這是什麼呢?下面一起來看一張圖:

由上圖我們可以看出,整個APP 的啟動流程: Launcher(APP): zygote -> jvm -> ActivityThread.main()ActivityThread.main() 就是我們APP 獨有的main 啟動方法,如圖所示,綠色的部分,就是Handler 為我們開辟的獨有空間,啟動主線程獨有的Looper,將當前App 獨立出來。
由此我們可以得出一個結論:Handler 並不是只屬於進程通訊,進程通訊只是Handler 的附屬功能,而Handler 的真正功能是 所有的代碼,都是在Handler 中運行的
1、一個線程有幾個Handler
考點
這裏面試官其實想了解的是,你對Handler的認知,如果這個問題打不出來,那麼面試官也不會再去問了。
答案
在說答案之前,先看一直 Handler 的執行流程圖:
由上圖,我們可以看出:
- 一個線程裏面,可以創建N個的Handler,如hander.sentXXX、 handler.postXX 都是在創建一個Handler。 每一個message,就是我們插入到消息隊列 中的消息節點。
2、一個線程有幾個Looper?如何保證
考點
這裏面試官其實想了解的是,你對Handler 流程及源碼的理解。
答案
- 在回答這個問題之前,我們再看一下Handler 的執行流程圖:
handler -> sendMessage -> messageQueue.enqueueMessage -> looper.loop() -> messasgeQueue.next() -> handler.dispatchMessage() -> handler.handerMessage(),handler 發送message,進入messageQueue.enqueueMessage 隊列,進入looper中經過loop 死循環的不斷遍曆,驅動隊列一直前進,經過handler.dispatchMessage() 分發給handler.handerMessage,這樣我們就走完了整個的Handler 流程,也可以直接看錯,一個線程中,只有一個Looper。 - 如何保證的呢?ThreadLocal 多線程,線程上下文的存儲變量,其實ThreadLocal 並不能存儲任何的東西,但是在ThreadLocal 中,有一個ThreadLocalMap 集合,裏面存儲的
<this, value>,this 就是上下文,唯一的ThreadLocal key,key 唯一了,那麼value 也就是唯一的,ThreadLocal在創建的時候,會有一個判斷,如果已經創建了,會報异常,所以一個線程有唯一的ThreadLocal 就有唯一的looper。如下圖:
ThreadLocal 線程隔離工具類

ThreadLocal 創建源碼

3、Handler 內存泄漏原因?為什麼其他的內部類沒有說過這個問題
考點
應該是考官想知道,你對於GC回收 JVM 相關的東西吧。
答案
在這裏,我我們先看一段代碼:
Handler handler = new Handler(){ @SuppressLint("HandlerLeak") @Override public void handleMessage(@NonNull Message msg) { Log.d("tiger", "handleMessage: "); View view = null; click(view); MainActivity2.this.click(view); } }; public void click(View view) { }- 上面代碼片段中的handler 代碼,會標黃,並給予一個警告:這個處理程序類應該是靜態的,否則可能發生內存泄漏。
- 那麼為什麼其他類不會有呢?
生命周期的問題。流程sendMessage -> sendMessageAtTime -> enqueueMessage在 enqueueMessage 中,有段代碼msg.target = this;,意思就是Message 會持有當前的handler,handler 已經成為了massage的一部分,假如我設置一個消息需要等待20分鐘後執行,那麼就意味,我的message會一直等待20分鐘之後才會執行,message 持有 handler,handler 持有 (this)activity,這樣就導致GC無法回收,JVM 通過可達性算法,告訴我們,沒法到達,就無法回收。內部類的生命周期,一旦在外部類生命周期中被別的生命周期持有了,那麼外部類也不能被釋放。
4、為何主線程可以new Handler?如果想要在子線程中new Handler 要做些什麼?
考點
好煩啊,我也不知道啊,為什麼還要要求這個?
答案
- 在APP 啟動的時候,就在
ActivityThread.main()方法中,就創建了looper.loop(),源碼如下:
- 那麼如何在子線程中new Handler呢?只需要在子線程中,手動添加
Looper.prepare();和Looper.loop();就可以了。請看下面代碼
public void click (View view){ new Thread(new Runnable() { @Override public void run() { Looper.prepare(); mHandler = new Handler() { public void handleMessage(Message msg) { // do something() } }; Looper.loop(); } }).start(); }5、子線程中維護的Looper,消息隊列無消息的時候的處理方法是什麼?有什麼用?
考點
對於源碼的掌握程度
答案
- 子線程中維護的Looper 在無消息的時候調用quit,可以結束循環。
loop()是一個死循環,想要退出,必須msg == null。請看下面源碼:
public static void loop() { for (;;) { Message msg = queue.next(); // might block if (msg == null) { // No message indicates that the message queue is quitting. return; } msg.recycleUnchecked(); } }- 只有在調用quit 的時候,才會返回null。
Message next() { for (;;) { synchronized (this) { if (mQuitting) { dispose(); return null; } } } }void quit(boolean safe) { synchronized (this) { if (mQuitting) { return; } mQuitting = true; } }- 所以使用quit() 喚醒隊列,執行loop() 退出循環,子線程looper 不在執行了。

- 消息入隊:根據時間排序,當隊列滿的時候,阻塞,直到用戶通過next() 取出消息。當next 方法被調用的時候,通知MassageQueue 可以消息入隊。
- 消息出隊:由
Looper.loop()進行循環, 對queue 進行輪詢操作,當消息達到執行時間就取出來,當MessageQueue 為空的時候,隊列阻塞,等消息調用queue massage 的時候,通知隊列,可以取出消息,停止阻塞。 - handler 沒有使用多線程中的阻塞隊列 BlockQueue,因為主線程(系統)也在使用,如果使用BlockQueue 設置上限的話,系統可能會出現卡頓等情况。

- 從上圖中,我們看可以出,handler 是一個生產者 - 消費者的設計模式。下面我們來看一下,looper 中的兩種循環阻塞方式:
- 執行時間阻塞(沒有到執行時間),
nativePollOnce(long ptr, int timeoutMillis)執行阻塞操作,timeoutMillis 為 -1 錶示無限等待,直到事件發生為止,如果為0,無需等待,立即執行。請看下面源碼:
Message next() { final long ptr = mPtr; if (ptr == 0) { return null; } int nextPollTimeoutMillis = 0; for (;;) { nativePollOnce(ptr, nextPollTimeoutMillis);// 循環進入阻塞狀態,等待執行時間到達後喚醒 synchronized (this) { if (msg != null) { if (now < msg.when) { // 消息不為空,並且沒有到執行時間,nextPollTimeoutMillis 不為-1 nextPollTimeoutMillis = (int) Math.min(msg.when - now, Integer.MAX_VALUE); } } // Process the quit message now that all pending messages have been handled. if (mQuitting) { dispose(); return null; } } } }- MessagaQueue 為空,執行阻塞,等待喚醒。當插入消息時,主動喚醒,請看下面源碼:
Message next() { if (ptr == 0) { // mPtr==0,錶示中斷循環, return null; } int pendingIdleHandlerCount = -1; int nextPollTimeoutMillis = 0; for (;;) { nativePollOnce(ptr, nextPollTimeoutMillis); synchronized (this) { if (msg != null) { } else { // 無消息,timeoutMillis為-1錶示無限等待,直到有事件發生為止 nextPollTimeoutMillis = -1; } } } } // mPtr==0 private void dispose() { if (mPtr != 0) { nativeDestroy(mPtr); mPtr = 0; } } // 喚醒 boolean enqueueMessage(Message msg, long when) { synchronized (this) { boolean needWake; Message p = mMessages; if (p == null || when == 0 || when < p.when) { msg.next = p; mMessages = msg; needWake = mBlocked; } // mPtr != 0 循環沒有中斷,進行喚醒操作. if (needWake) { nativeWake(mPtr); } } return true; }6、既然可以存在多個Handler 往MessageQueue 中添加數據(發消息時各個Handler 可能處於不同線程),那它內部是如何確保線程安全的?
考點
線程鎖,後續會更多 synchronized 相關的東西
答案
- synchronized鎖: synchronized內置鎖,它是由jvm自動完成的,插入和取都需要鎖,因為取的時候,可能正在插入。它是鎖的對象,因為MessageQueue 每個線程中只有一個Looper,每個Looper又只有一個MessageQueue.
7、我們使用Message 時應該如何創建它?
考點
難道是想知道有沒有使用過?
答案
- 在創建Message 對象時,有三種方法:
Message message = new Message();Message message1 = Message.obtain();查看源碼的時候,發現內部調用的也是obtain() 方法。Message message2 = handler.obtainMessage();
8、Looper 死循環為什麼不會導致應用卡死
考點
難道是 ANR 的機制?
答案
- 應用卡死也就是發生ANR,那什麼是ANR?ANR是如何檢測的,知道了ANR是如何檢測的,我們就知道Looper死循環為什麼不會導致應用卡死?
什麼是ANR?
- ANR指的是應用無響應,ANR主體實現在系統層。所有與ANR相關的消息,都會經過系統進程(AMS)調度,然後派發到應用進程完成對消息的實際處理,同時,系統進程設計了不同的超時限制來跟踪消息的處理。 一旦應用程序處理消息不當,超時限制就起作用了, 它收集一些系統狀態,比如CPU/IO使用情况、進程函數調用棧,並且報告用戶有進程無響應了(ANR對話框)。
總結
🤩
️
边栏推荐
- Memory introduction
- Prometheus PushGateway 碎碎念
- I have fundamentally solved the problem of wechat occupying mobile memory
- Integrated API interface code of domestic express companies for intra city distribution and ordering - Express 100
- 硬件开发笔记(六): 硬件开发基本流程,制作一个USB转RS232的模块(五):创建USB封装库并关联原理图元器件
- Gateway processing flow of zuul source code analysis
- Process basic properties
- How to efficiently analyze online log
- Coinbase will launch the first encryption derivative for individual investors
- 敏捷之道 | 敏捷开发真的过时了么?
猜你喜欢

Cloud native essay solicitation progress case practice

3. Caller 服务调用 - dapr

Use abp Zero builds a third-party login module (I): Principles

LVGL库入门教程 - 颜色和图像

青藤入选工信部网安中心“2021年数字技术融合创新应用典型解决方案”

Without home assistant, zhiting can also open source access homekit and green rice devices?

Teach you how to use airtestide to connect your mobile phone wirelessly!

Kubernetes cluster deployment

go Cobra命令行工具入门

Who is the fish and who is the bait? Summary of honeypot recognition methods from the perspective of red team
随机推荐
C语言中常量的定义和使用
Best practices of swagger in egg project
About the hacked database
SYSTEMd common component description
MySQL interview questions
J'a i ouvert quelques mots d'un ami et quelques réflexions personnelles sur le livre des six ancêtres
CVPR 2022 | 美團技術團隊精選論文解讀
Coinbase将推出首个针对个人投资者的加密衍生产品
Parti,谷歌的自回归文生图模型
Quickly understand the commonly used message summarization algorithms, and no longer have to worry about the thorough inquiry of the interviewer
初中级开发如何有效减少自身的工作量?
一文理解OpenStack网络
RAID5 array recovery case tutorial of a company in Shanghai
Introduction to reptile to give up 01: Hello, reptile!
Implement Domain Driven Design - use ABP framework - create entities
The second phase of freshman engineering education seminar is to enroll in the China 100 school peer program
1. Snake game design
The introduction of MySQL memory parameters is divided into two categories: thread exclusive and global sharing
数据科学家面临的七大挑战及解决方法
kotlin 数组、集合和 Map 的使用
