Handler系列原始碼解析

不靠譜的老肖發表於2019-10-22

前言

老是看大佬們分析這個東西,也看了一些文章,總感覺雲裡霧裡,決定自己來對著原始碼理一理,能力有限,先寫下自己所理解的,後期再加上來。

image

在我努力認真(邊玩邊睡)的閱讀下,終於,瞧出了一絲門道,下面就給大家分析分析。

淺談理解

多執行緒

談這個之前,我覺得首先需要聊聊多執行緒這個東西(畢竟Handler很多時候是跨執行緒通訊的),那麼,什麼是多執行緒呢?比如,我們去銀行辦理業務,由於是淡季,我們發現辦理視窗只開了一個,大家都得排著隊一個一個來(這就是單執行緒),那麼,開了兩個甚至多個視窗同時處理銀行的事務,那麼這樣子就等於開了多個執行緒(這就是多執行緒)。

image

瞭解了多執行緒,我們就開始轉移到我們的主要戰地(Handler)上面,我們使用Handler的時候,很多的時候都是在子執行緒中做完操作後需要UI執行緒(主執行緒)中更新介面,一般的做法就是

使用

Handler handler = new Handler(){
 @Override
 public void handleMessage(Message msg) {
 /**
 * 根據引數做一些操作
 */
 }
 };
​
handler.sendEmptyMessageDelayed(1,2000);
複製程式碼

Handler是Android執行緒間通訊的一種方式,它常被我們用來更新UI,是的,我是這麼用,還有延時,只有拿出來總結的時候,才會發現有時候使用的時候是有缺漏的。所以總結很重要啊!

目前為止總結的一些使用情況如下:

  • 1.子執行緒傳送訊息到主執行緒

  • 2.在子執行緒中更新UI

  • 3.在子執行緒中使用Handler

  • 4.使用HandlerThread

  • 5.Handler的callback回撥

1、子執行緒傳送訊息到主執行緒
Handler mainHandler = new Handler() {
 @Override
 public void handleMessage(Message msg) {
 super.handleMessage(msg);
​
 Toast.makeText(HandlerActivity.this, "訊息", Toast.LENGTH_SHORT).show();
​
 }
 };
複製程式碼
new Thread(new Runnable() {
 @Override
 public void run() {
 try {
 Thread.sleep(1000);
 } catch (InterruptedException e) {
 e.printStackTrace();
 }
 /*子執行緒傳給主執行緒*/
 mainHandler.sendEmptyMessage(0);
 }
 }).start();
複製程式碼

這裡是在子執行緒中Handler物件傳送一個空訊息,然後在handleMessage方法中進行操作,此時Toast執行已經是在UI執行緒了。

然後剛剛測試了一下,不僅僅是子執行緒往主執行緒發訊息,主執行緒也可以向子執行緒發訊息,子執行緒也可以向子執行緒發訊息,自己手動去試一下才會理解Handler這個執行緒間通訊是怎麼回事。

2、在子執行緒中更新UI
Handler updateHandler = new Handler();
複製程式碼
new Thread(new Runnable() {
 @Override
 public void run() {
 try {
 Thread.sleep(1000);
 } catch (InterruptedException e) {
 e.printStackTrace();
 }
 /*在子執行緒中更新UI*/
 updateHandler.post(new Runnable() {
 @Override
 public void run() {
 /*
 *更新UI的操作
 * */
 }
 });
 }
 }).start();
複製程式碼

這裡的程式碼都是一些區域性的程式碼塊,這裡的updateHandler是在主執行緒宣告的,子執行緒是開在主執行緒下的, 然後updateHandler物件在子執行緒使用post方法,new了一個Runnable去切換執行緒到主執行緒執行更新UI的程式碼。當然,也可以像上面那樣傳送一個訊息在Handler的handleMessage裡更新UI喔~!

3、在子執行緒中使用Handler

匿名內部類實現

new Thread(new Runnable() {//建立一個子執行緒
 @Override
 public void run() {
​
 Looper.prepare();//建立與當前執行緒相關的Looper
 myThreadTwoHandler = new Handler() {    //建立一個子執行緒裡的Handler
 @Override
 public void handleMessage(Message msg) {
 super.handleMessage(msg);
 Log.e(TAG, "當前存線上程為" + Thread.currentThread().getName());
 }
 };
 Looper.loop();//呼叫此方法,訊息才會迴圈處理
 }
 }).start();
​
 myThreadTwoHandler.sendEmptyMessageDelayed(0, 5000);
複製程式碼

子類繼承Thread實現

//MyThread 子類繼承 Thread
 public class MyThread extends Thread {
 public Looper childLooper;
​
 @Override
 public void run() {
 Looper.prepare();//建立與當前執行緒相關的Looper
 childLooper = Looper.myLooper();//獲取當前執行緒的Looper物件
 Looper.loop();//呼叫此方法,訊息才會迴圈處理
 }
 }
複製程式碼
/*在子執行緒使用Handler*/
 MyThread myThread = new MyThread();
 myThread.start();
 myThreadHandler = new Handler(myThread.childLooper) {//與MyThread執行緒繫結
 @Override
 public void handleMessage(Message msg) {
 super.handleMessage(msg);
 Log.e(TAG, "當前存線上程為" + Thread.currentThread().getName());
 }
 };
 //主執行緒呼叫子執行緒的Handler物件傳送訊息
 myThreadHandler.sendEmptyMessageDelayed(0, 5000);
複製程式碼

HandlerThread實現

HandlerThread handlerThread = new HandlerThread("ceshi");
 handlerThread.start();
 //通過HandlerThread的getLooper方法可以獲取Looper
 Looper looper = handlerThread.getLooper();
 //通過Looper我們就可以建立子執行緒的handler了
 Handler handler = new Handler(looper) {
 @Override
 public void handleMessage(Message msg) {
 super.handleMessage(msg);
 //測試結果是在ceshi這個執行緒內
 Log.e(TAG, "這個是HandlerThread執行緒哦 : " + Thread.currentThread().getName());
 }
 };
 handler.sendEmptyMessageDelayed(0, 1000);
複製程式碼

Handler的記憶體洩露

為什麼會導致記憶體洩露呢?而在Java語言中,非靜態內部類會持有外部類的一個隱式引用,所以,Handler會持有Activity的引用啦,然後就會有可能造成外部類,也就是Activity無法被回收,導致記憶體洩露。

那麼,如何避免記憶體洩漏,使用正確的Handler呢?

  • 使用靜態的匿名內部類,並持有外部類的弱引用

宣告靜態的Handler內部類,持有外部類的弱引用,通過外部類例項去引用外部類的各種控制元件例項,引數例項等等。然後當GC回收時,因為外部類是弱引用,所以會被回收。

/**
 * 宣告一個靜態的Handler內部類,並持有外部類的弱引用
 */
 private static class MyHandler extends Handler {
​
 private final WeakReference<HandlerActivity> mActivty;
​
 private MyHandler(HandlerActivity mActivty) {
 this.mActivty = new WeakReference<>(mActivty);
 }
​
 @Override
 public void handleMessage(Message msg) {
 super.handleMessage(msg);
 HandlerActivity activity = mActivty.get();
 if (activity != null) {
 Log.e("eee", "handleMessage: " + Thread.currentThread().getName());
 }
 }
 }
複製程式碼

在外部類中宣告MyHandler物件

private final MyHandler mHandler = new MyHandler(this);
複製程式碼

然後呼叫傳送訊息,post的方式和sendMessage的方式

mHandler.post(sRunnable);
mHandler.sendMessage(message);
複製程式碼

如果使用sendMessage方法的話,會被MyHandler的 handleMessage方法接收。那麼,若使用post方法的話,我們還需要宣告一個靜態的Runable來完成我們的post

 private static final Runnable sRunnable = new Runnable() {
 @Override
 public void run() {
 // ...你的操作
 Log.e(TAG, "這裡是run");
 }
 };
複製程式碼

非同步任務引發的資源洩露,比如handler或者thread。這種情況發生的原因主要是非同步任務的生命週期與activity生命週期不同步造成的,以handler中的message為例:

Handler handler =  new Handler();
handler.postDelayed(new Runnable() {
 @Override
 public void run() {
 tvContent.setText("newContent");
 }
}, 2000);
handler.obtainMessage(1).sendToTarget();
複製程式碼

不管是使用哪種形式來傳送message,message都會直接或者間接引用到當前所在的activity例項物件,如果在activity finish後,還有其相關的message在主執行緒的訊息佇列中,就會導致該activity例項物件無法被GC回收,引起記憶體洩露。所以一般我們需要在onDestroy階段將handler所持有的message物件從主執行緒的訊息佇列中清除。示例如下:

@Override
protected void onDestroy() {
 super.onDestroy();
 if (handler != null) {
 handler.removeCallbacksAndMessages(null);
 }
}
複製程式碼

原始碼分析

在這裡,我就不貼上太多的原始碼了,畢竟大佬們的文章裡面都寫的很詳細,我在這裡就用我自己的理解給大家舉個例子。

舉個例子

還是拿銀行的業務作比喻,在Handler的整套流程中,有四個主要的類 Looper、Handler、MessageQueue、Message。那麼在我們去銀行的辦理業務流程中分別代表啥咧?

假設只有一個辦理視窗,可以看做是主執行緒,我們自己做的事情也是一個子執行緒做的一些準備工作,如網路請求,現在我們需要處理一些事務(我們自己處理不了的事務),需要銀行視窗處理(類同於我們當前的子執行緒的更新UI的操作需要給到主執行緒去處理),那麼,到銀行的第一步我們需要去取視窗排隊的編號,這裡取號的過程,就可以理解為我們去得到相應執行緒的new Handler物件一樣,畢竟我們的事務需要到這個視窗才能處理。然後取號成功時就向銀行的訊息處理系統裡面新增了一個新的待處理訊息,這裡可以理解為傳送了一個Message(Runable),然後,在銀行的訊息處理系統中,我們的這個訊息就被排在了上一個訊息的後面,先來先處理嘛,這裡就可以用MessageQueue裡面加入了新的訊息(Message)來比喻;這個系統是用來迴圈檢查是否還有人在排隊等候,如果有的話,就提示這個編號到視窗去處理業務,那麼這個迴圈的工作就是Looper來做的,它從MessageQueue裡面迴圈的去檢查佇列裡面是否還有有訊息(Message),有的話就把這個Message分發給對應的Handler物件去處理。這裡的可以比喻為銀行廣播提示我們可以去視窗(Handler)處理我們的事務了。

image

那麼,整個過程可以理解為,我們得到主執行緒Handler的一個例項物件,然後通過這個物件向訊息佇列(MessageQueue)裡面新增新的訊息(Message),然後Looper一直在那裡迴圈檢查佇列是否有訊息,有的話就把這個訊息分發給我們得到的這個Handler物件來處理,這樣子,一個訊息的流程就走完了(簡單的過程)

image

Emmmmm,不知道表達的怎麼樣,但是,有幾個問題,在這裡我來解答一下

相關問題

1、Looper一直執行,那它啥時候啟動的咧?

這就比如銀行的訊息系統一樣,肯定是要啟動了之後,才能去處理事務的,那銀行的系統是一通電一開門就開啟了,我們主執行緒的Looper從哪裡啟動的咧?

從這裡開始,我們就大概的過一下原始碼,從new Handler下手,我們看哈例項化時做了啥

 final Looper mLooper;
 final MessageQueue mQueue;
 final Callback mCallback;
​
public Handler(Callback callback, boolean async) {
 if (FIND_POTENTIAL_LEAKS) {
 final Class<? extends Handler> klass = getClass();
 if ((klass.isAnonymousClass() || klass.isMemberClass() || klass.isLocalClass()) &&
 (klass.getModifiers() & Modifier.STATIC) == 0) {
 Log.w(TAG, "The following Handler class should be static or leaks might occur: " +
 klass.getCanonicalName());
 }
 }
​
 mLooper = Looper.myLooper();   //1
 if (mLooper == null) {
 throw new RuntimeException(
 "Can't create handler inside thread " + Thread.currentThread()
 + " that has not called Looper.prepare()");
 }
 mQueue = mLooper.mQueue;   //2
 mCallback = callback; 
 mAsynchronous = async;
 }
複製程式碼

這裡我標註出了2個地方,第一個,我們得到一個Looper物件,第二個是從這個Looper物件裡面得到MessageQueue的引用物件,那麼得到Looper物件的方法做了啥,我們瞧瞧

static final ThreadLocal<Looper> sThreadLocal = new ThreadLocal<Looper>();
public static @Nullable Looper myLooper() {
 return sThreadLocal.get();
}
複製程式碼

可以看到,這裡是從當前執行緒中取出Looper物件,很奇怪的是,這裡只有取,沒有看到存,我們初始化的時候也只做了這些操作呀,那麼啥時候存的咧?從大佬們的總結中可以看到,主執行緒的Looper的初始化是在ActivityThread的main方法中進行的

public static void main(String[] args) {
 ....
 Looper.prepareMainLooper();
​
 ...
 Looper.loop();
 .....
 }
複製程式碼

這裡,首先呼叫了prepareMainLooper方法,然後呼叫了loop方法,我們來看看這兩個方法分別做了啥?(省略了大量程式碼,有興趣仔細看的可以自己去看原始碼)

public static void prepareMainLooper() {
 prepare(false);
 ......
 }
​
private static void prepare(boolean quitAllowed) {
 ......
 sThreadLocal.set(new Looper(quitAllowed));
 }
​
public static void loop() {
 ......
 for (;;) {
 Message msg = queue.next(); // might block
 ......
 msg.recycleUnchecked();
 }
 }
複製程式碼

從這幾個方法裡面看到,我們的 存 是在這裡進行的,然後loop方法就讓這個Looper開始迴圈的檢查訊息佇列了。

image

2、訊息(Message)那麼多,我們怎麼知道是那個Handler發的,那最後處理的時候怎麼找到這個Handler來處理列?

這個問題,我們就需要看一哈Message的原始碼

....
public int what;
public int arg1;
public int arg2;
public Object obj;
Handler target;
Message next;
....
複製程式碼

我們可以看到,這些引數中,有個target(Handler ),其實,這個就是最後分發訊息的時候,訊息能找到對應Handler的欄位(這裡我們看到了next(Message),熟悉資料結構單連結串列的哥們肯定知道是啥,所以從這個欄位可以看出,MessageQueue控制了這個單連結串列的存取方式為佇列的方法。)

那麼,這個target是啥時候設定上的?我們來看看,我們發訊息的操作

public final boolean sendEmptyMessageDelayed(int what, long delayMillis) {
 Message msg = Message.obtain();
 msg.what = what;
 return sendMessageDelayed(msg, delayMillis);
 }
​
public final boolean sendMessageDelayed(Message msg, long delayMillis)
 {
 if (delayMillis < 0) {
 delayMillis = 0;
 }
 return sendMessageAtTime(msg, SystemClock.uptimeMillis() + delayMillis);
 }
​
public boolean sendMessageAtTime(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);
 }
​
private boolean enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis) {
 msg.target = this;
 if (mAsynchronous) {
 msg.setAsynchronous(true);
 }
 return queue.enqueueMessage(msg, uptimeMillis);
 }
複製程式碼

方法的呼叫過程有點長,我們就簡單的理解為,新建了一個Message物件,然後通過MessageQueue的引用物件把這個Message物件新增到佇列中去了。我們重點看最後一個方法的這一句

msg.target = this;
複製程式碼

在這裡,我們就知道了,target是在傳送(新增訊息的時候)設定上的。

image

3、訊息是怎麼分發的,又怎麼就到了自己實現的handleMessage方法了呢?

剛才在前面,我們大概看了一下Looper的loop方法,裡面是個死迴圈一直從MessageQueue裡面取訊息,那取到訊息之後咧?我們來看看

for (;;) {
 Message msg = queue.next(); // might block
 .....
 final long dispatchStart = needStartTime ? SystemClock.uptimeMillis() : 0;
 final long dispatchEnd;
 try {
 msg.target.dispatchMessage(msg);   //1
 dispatchEnd = needEndTime ? SystemClock.uptimeMillis() : 0;
 } finally {
 if (traceTag != 0) {
 Trace.traceEnd(traceTag);
 }
 }
 .....
​
 msg.recycleUnchecked();
 }
複製程式碼

在標註1的地方,我們就可以看到,這裡就呼叫了這個訊息(Message)的Handler的dispatchMessage方法

public void dispatchMessage(Message msg) {
 if (msg.callback != null) {
 handleCallback(msg);
 } else {
 if (mCallback != null) {
 if (mCallback.handleMessage(msg)) {
 return;
 }
 }
 handleMessage(msg);
 }
 }
複製程式碼

在這裡,我們就看到了,它最後呼叫了handleMessage方法。

模擬Handler訊息機制

瞭解了這些內容,是不是感覺自己收穫滿滿噠,莫慌還有一個大禮給大家。

Handler系列原始碼解析

我們來使用多執行緒模仿Handler訊息機制。那麼開始,首先是四大類。

Handler類

我們定義一個Handler類,裡面有個MesQueue 的引用和Looper的引用,然後在建構函式裡面進行初始化,定義一個傳送訊息的方法sendMessage和一個接收訊息的方法handleMessage。

public class Handler {
 MesQueue queue;
 Looper looper;
​
 public Handler() {
 this.looper = Looper.myLoop();
 queue = looper.queue;
 }
​
 public Handler(Looper looper) {
 this.looper = looper;
 if (looper == null)
 System.out.println("looper is null");
 queue = looper.queue;
 }
​
 public void sendMessage(Message message){
 message.target = this;
 queue.enterQueue(message);
 }
​
 public void dispatchMessage(Message mes){
 handleMessage(mes);
 }
​
 public void handleMessage(Message msg) {
 }
}
複製程式碼

Looper類

public class Looper {
​
 static final ThreadLocal<Looper> mlocal = new ThreadLocal<Looper>();
​
 public MesQueue queue;
​
 public Looper() {
 queue = new MesQueue();
 }
​
 public static Looper myLoop() {
 return mlocal.get();
 }
​
 public static void prepare(){
 mlocal.set(new Looper());
 }
​
 public static void loop(){
 Looper me = myLoop();
 MesQueue queue = me.queue;
 for (;;) {
 try {
 Thread.sleep(3000);
 } catch (InterruptedException e) {
 e.printStackTrace();
 }
 Message message = queue.next();
 message.target.dispatchMessage(message);
 }
 }
} 
複製程式碼

使用ThreadLocal儲存執行緒變數,帶有MesQueue 的引用,prepare方法初始化當前執行緒的Looper物件,myLoop得到當前執行緒的Looper物件,loop方法開始運作處理訊息佇列的訊息。

MesQueue

public class MesQueue {
​
 Message front ;
 Message rear;
​
 public MesQueue() {
 front = new Message();
 rear = front;
 front.next = null;
 }
​
 public synchronized void enterQueue(Message message){
 message.next = null;
 if (front.next == null){
 front.next = message;
 rear = message;
 notifyAll();
 }else {
 rear.next = message;
 rear = message;
 }
​
 System.out.println(Thread.currentThread() + " put Message what = " + message.what);
 }
​
 public synchronized Message next(){
 Message temp;
 while (true){
 if (front.next == null) {
 try {
 System.out.println("佇列為空,開始阻塞");
 wait();
 } catch (InterruptedException e) {
 System.out.println("佇列為空,異常");
 e.printStackTrace();
 }
 }else {
 temp = front.next;
 front.next = temp.next;
 break;
 }
 }
 return temp;
 }
}
複製程式碼

在構造方法裡面初始化佇列,定義enterQueue入佇列的方法,next出佇列的方法。

Message

public class Message {
 public Object obj;
 public int what;
 public Handler target;
 public Message next;
}
複製程式碼

這個就是訊息結點了。

Handler系列原始碼解析

多執行緒測試

Main(模擬主執行緒)
public class MessageHandlerQus {
​
 public static void main(String[] args) {
​
 Looper.prepare();
​
 //模擬子執行緒向主執行緒傳送訊息
 Handler handler = new Handler(){
 @Override
 public void handleMessage(Message msg) {
 System.out.println(Thread.currentThread() +"  msg what = " + msg.what);
 }
 };
​
 //模擬子執行緒向子執行緒傳送訊息
 ThreadB threadB = new ThreadB(){
 @Override
 protected void onLooperPrepared(Looper looper) {
 Handler handlerB = new Handler(looper){
 @Override
 public void handleMessage(Message msg) {
 System.out.println(Thread.currentThread() +"  msg what = " + msg.what);
 }
 };
 ThreadA threadA = new ThreadA(handlerB);
 threadA.start();
​
 }
 };
 threadB.start();
​
 ThreadA threadA = new ThreadA(handler);
 threadA.start();
​
 Looper.loop();
 System.out.println("主執行緒結束");
 }
}
複製程式碼
ThreadA
public class ThreadA extends Thread {
​
 Handler handler;
 int value = 1;
​
 public ThreadA(Handler handler) {
 this.handler = handler;
 }
​
 @Override
 public void run() {
 System.out.println("run");
 while (true){
 Message message = new Message();
 message.what = value++;
 handler.sendMessage(message);
 try {
 Thread.sleep(1000);
 } catch (InterruptedException e) {
 e.printStackTrace();
 }
 }
 }
}
複製程式碼
ThreadB
public class ThreadB extends Thread {
 Looper looper;
​
 public ThreadB() { }
​
 @Override
 public void run() {
 Looper.prepare();
 synchronized (this) {
 looper = Looper.myLoop();
 notifyAll();
 }
 onLooperPrepared(looper);
 Looper.loop();
 System.out.println("B end");
 }
​
 protected void onLooperPrepared(Looper looper) {
 }
}
複製程式碼
執行結果
run
run
Thread[Thread-2,5,main] put Message what = 1
Thread[Thread-1,5,main] put Message what = 1
Thread[Thread-2,5,main] put Message what = 2
Thread[Thread-1,5,main] put Message what = 2
Thread[Thread-2,5,main] put Message what = 3
Thread[Thread-1,5,main] put Message what = 3
Thread[Thread-0,5,main]  msg what = 1
Thread[main,5,main]  msg what = 1
Thread[Thread-1,5,main] put Message what = 4
Thread[Thread-2,5,main] put Message what = 4
Thread[Thread-1,5,main] put Message what = 5
Thread[Thread-2,5,main] put Message what = 5
Thread[Thread-2,5,main] put Message what = 6
Thread[Thread-1,5,main] put Message what = 6
Thread[main,5,main]  msg what = 2
Thread[Thread-0,5,main]  msg what = 2
Thread[Thread-2,5,main] put Message what = 7
Thread[Thread-1,5,main] put Message what = 7
Thread[Thread-1,5,main] put Message what = 8
Thread[Thread-2,5,main] put Message what = 8
Thread[Thread-1,5,main] put Message what = 9
Thread[Thread-2,5,main] put Message what = 9
Thread[main,5,main]  msg what = 3
Thread[Thread-0,5,main]  msg what = 3
Thread[Thread-1,5,main] put Message what = 10
Thread[Thread-2,5,main] put Message what = 10
Thread[Thread-1,5,main] put Message what = 11
Thread[Thread-2,5,main] put Message what = 11
Thread[Thread-1,5,main] put Message what = 12
Thread[Thread-2,5,main] put Message what = 12
Thread[main,5,main]  msg what = 4
Thread[Thread-0,5,main]  msg what = 4
Thread[Thread-1,5,main] put Message what = 13
Thread[Thread-2,5,main] put Message what = 13
複製程式碼

可以看到,這裡我們的佇列對新入的訊息進行了列隊處理,然後,依次處理收到的訊息。

好了,這篇文章先到這裡,後期有新的的再補充(客套話)。

Handler系列原始碼解析

相關文章