-
前言
Android開發的小夥伴對于Handler一定不陌生了,基本面試必問的東西,但是很多人都是死記硬背不了解原理,這樣面試很容易就丢分了,是以本文将會簡單帶大家了解一下Hander源碼的實作。因為是淺析,是以有些東西不會講的太細,但是會帶大家把源碼走通。
-
上代碼
public class HandlerActivity extends AppCompatActivity {
private Handler mHandler = new Handler() {
@Override
public void handleMessage(Message msg) {
super.handleMessage(msg);
Log.d("lkx", (String) msg.obj);
}
};
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_handler);
}
/**
* 點選發送消息
*/
public void onClick(View view) {
new Thread(() -> {
Message message = Message.obtain();
message.obj = "好好學習";
mHandler.sendMessage(message);
}).start();
}
}
這應該是大家平時寫到吐的代碼了吧,主要是從子線程發送一條消息到主線程的過程。
-
消息進隊列 (源碼解析)
建立一個message并指派obj參數,然後攜帶message調用了sendMessage方法
new Thread(() -> {
Message message = Message.obtain();
message.obj = "好好學習";
mHandler.sendMessage(message);
}).start();
-
**Handler#sendMessage**
sendMessage調用了sendMessageDelayed
public final boolean sendMessage(@NonNull Message msg) {
return sendMessageDelayed(msg, 0);
}
-
**Handler#sendMessageDelayed**
sendMessageDelayed調用了sendMessageAtTime
public final boolean sendMessageDelayed(@NonNull Message msg, long delayMillis) {
if (delayMillis < 0) {
delayMillis = 0;
}
return sendMessageAtTime(msg, SystemClock.uptimeMillis() + delayMillis);
}
-
**Handler#sendMessageAtTime**
sendMessageAtTime調用了enqueueMessage,這裡出現了mQueue,也就是消息隊列,暫時不用管,下面會解釋。
public boolean sendMessageAtTime(@NonNull 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);
}
-
**Handler#enqueueMessage**
enqueueMessage調用了queue.enqueueMessage(),并把message傳了進去
private boolean enqueueMessage(@NonNull MessageQueue queue, @NonNull Message msg,
long uptimeMillis) {
msg.target = this;
msg.workSourceUid = ThreadLocalWorkSource.getUid();
if (mAsynchronous) {
msg.setAsynchronous(true);
}
return queue.enqueueMessage(msg, uptimeMillis);
}
-
消息進隊列 (總結)
上面流程走完,大家是不是很懵逼,mQueue消息隊列是啥玩意?
mQueue初次相遇
應該是sendMessageAtTime方法:
public boolean sendMessageAtTime(@NonNull Message msg, long uptimeMillis) {
MessageQueue queue = mQueue;
...
return enqueueMessage(queue, msg, uptimeMillis);
}
看起來是個成員變量,我們去成員變量找一下:
public class Handler {
...
final MessageQueue mQueue;
...
}
找找在哪裡指派的:
public Handler(@Nullable Callback callback, boolean async) {
...
mLooper = Looper.myLooper();
...
mQueue = mLooper.mQueue;
mCallback = callback;
mAsynchronous = async;
}
原來是在兩個參數的構造方法中被指派了,調用Looper.myLooper() 拿到mLooper,然後調用mLooper.mQueue方法,也就是說mQueue是Looper的一個成員變量。
以我們目前分析的源碼,隻知道我們把Message放入了MessageQueue中:

-
Looper源碼解析
Looper初次相遇
是在Handler兩個參數的構造方法中,但是Looper并不是在這裡建立的,那在哪裡建立的呢?一切還要從盤古開天地說起:
在我們的程式啟動後,經過一系列的啟動流程,最終将會調用我們的ActivityThread.java這個類,這個類裡面有個main()方法,這個main方法大家一定不會陌生:
-
**ActivityThread#main**
ActivityThread的main()首先調用了Looper.prepareMainLooper,初始化了Looper
public static void main(String[] args) {
...
Looper.prepareMainLooper();
...
Looper.loop();
}
-
**Looper#prepareMainLooper**
調用了prepare并傳入了false
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**
這裡new了一個Looper,然後放進了ThreadLocal裡面
private static 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#Looper**
Looper構造方法中初始化了一個MessageQueue
private Looper(boolean quitAllowed) {
mQueue = new MessageQueue(quitAllowed);
mThread = Thread.currentThread();
}
- ThreadLocal是什麼?
ThreadLocal源碼還是比較複雜的,是以我會專門寫一篇文章來講解,大家暫時了解為: 同一個ThreadLocal在不同的線程中set和get可以實作資料隔離,線程之間互不影響。比如A線程存入一個1,B線程存儲一個2,那在A線程中擷取的值是1,B線程擷取的值是2
public class HelloThreadLocal {
public static void main(String[] args) {
ThreadLocal<Integer> threadLocal = new ThreadLocal<>();
new Thread(() -> {
threadLocal.set(1);
System.out.println(Thread.currentThread().getName()+": "+threadLocal.get());
},"線程A").start();
new Thread(() -> {
threadLocal.set(2);
System.out.println(Thread.currentThread().getName()+": "+threadLocal.get());
},"線程B").start();
}
}
執行結果:
線程A: 1
線程B: 2
-
**回到Looper#prepare**
現在我們學會了ThreadLocal,再來看一遍這個代碼,這裡有一個判斷,如果ThreadLocal擷取的值不為空,就會抛出異常,否則就存儲一個Looper到目前線程,這裡用了ThreadLocal的特性,可以保證我們一個線程最多隻有一個Looper。
private static void prepare(boolean quitAllowed) {
if (sThreadLocal.get() != null) {
throw new RuntimeException("Only one Looper may be created per thread");
}
sThreadLocal.set(new Looper(quitAllowed));
}
-
**回到ActivityThread#main**
上面我們已經分析完了Looper.prepareMainLooper(),會建立一個Looper對象存到ThreadLocal,現在我們看到main()還執行了Looper.loop()方法。
public static void main(String[] args) {
...
Looper.prepareMainLooper();
...
Looper.loop();
}
-
**Looper#loop**
loop方法中的代碼特别多,這裡我們精簡之後大概還剩這麼多代碼:
- 通過myLooper()從ThreadLocal中拿到我們存放的Looper對象me
- 從me中拿到Looper的消息隊列queue
- 建立一個死循環不停地從queue消息隊列中取資料
- 如果資料為null就直接return,如果有資料就調用Handler的dispatchMessage方法
public static void loop() {
final Looper me = myLooper();
...
final MessageQueue queue = me.mQueue;
...
for (;;) {
Message msg = queue.next();
if (msg == null) {
return;
}
...
msg.target.dispatchMessage(msg); //如果消息不等于null,就會走這裡
}
...
}
-
**Handler#dispatchMessage**
如果消息隊列循環不等于null,就會走這個方法,然後調用handleMessage(msg)
public void dispatchMessage(@NonNull Message msg) {
if (msg.callback != null) {
handleCallback(msg);
} else {
if (mCallback != null) {
if (mCallback.handleMessage(msg)) {
return;
}
}
handleMessage(msg);
}
}
-
**Handler#handleMessage**
這個方法是一個空實作,最終由使用者定義的Handler子類實作。
public void handleMessage(@NonNull Message msg) {
}
-
**自己的定義的Handler#handleMessage**
會回調到我們自己的Handler的handleMessage方法,并傳回Message
private Handler mHandler = new Handler() {
@Override
public void handleMessage(Message msg) {
super.handleMessage(msg);
Log.d("lkx", (String) msg.obj);
}
};
大概流程好像是走通了…
-
總結
上面已經簡單分析了Handler的核心源碼,可能很多人還是無法把這個流程串起來,是以我們簡單總結一下:
- 在使用者打開APP的時候就會執行ActivityThread的mian()。
- main()方法中會調用Looper.prepareMainLooper()建立一個Looper存入ThreadLocal中。
- main()方法建立完Looper後會調用Looper.loop()。
- loop方法會建立死循環不停地從MessageQueue中擷取消息。
- 如果拿到消息就會回調我們的Handler.handleMessage()方法。
- ooooooooooooo上面的消息循環機制已經開始執行oooooooooooooooo
- 現在使用者建立一個消息,并調用Handler.sendMessage(message)。
- 最終會調用到queue.enqueueMessage(),将Message方法MessageQueue中。
- 如果消息隊列有消息了,就會執行上面的第5步,這裡就形成了一個完整的閉環。
-
常見面試題
- 一個線程可以有幾個Handler?
一個線程可以有多個Handler。
- 一個線程可以有幾個Looper?怎麼保證的?
一個線程隻能有一個Looper,線上程建立的時候會調用Looper.prepare()方法進行初始化,它會建立new一個Looper對象并存到ThreadLocal中,當再次建立Looper對象的時候,會先從ThreadLocal中取出Looper,如果不為空就直接抛出異常,這樣就可以保證一個線程隻能有一個Looper了。
- 子線程中可以使用Handler嗎?
子線程可以使用Handler,但是需要我們自己去建立Looper,是以我們需要調用Looper.prepare()進行初始化,然後再調用Looper.loop()進行循環。
- Message如何建立更好?
我們可以直接使用new Message()進行建立,但是這樣不好,推薦使用Message.obtain()進行建立,這樣可以複用Message,減少對象的頻繁建立,避免記憶體抖動。
- Handler會引起記憶體洩露嗎?為什麼?
Handler會引起記憶體洩露,如果發送一個延遲的Message,當延遲還沒有結束的時候銷毀了Activity,這個時候因為Message持有Handler的引用,Handler是内部類預設持有外部類的引用,是以Activity無法被回收,就會引起記憶體洩露。
- Handler記憶體洩露怎麼解決?
可以把Handler改為靜态内部類的方式,然後使用弱引用持有Activity的引用,GC在回收對象的時候,遇到弱引用會直接回收掉,就可以避免記憶體洩露了。
最後
小編學習提升時,順帶從網上收集整理了一些 Android 開發相關的學習文檔、面試題、Android 核心筆記等等文檔,希望能幫助到大家學習提升,如有需要參考的可以直接去我 CodeChina位址:https://codechina.csdn.net/u012165769/Android-T3 通路查閱。