Handler系列源碼解析

前言

總是看大佬們分析這個東西,也看了一些文章,總感受雲裏霧裏,決定本身來對着源碼理一理,能力有限,先寫下本身所理解的,後期再加上來。bash

image

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

淺談理解

多線程

談這個以前,我以爲首先須要聊聊多線程這個東西(畢竟Handler不少時候是跨線程通訊的),那麼,什麼是多線程呢?好比,咱們去銀行辦理業務,因爲是淡季,咱們發現辦理窗口只開了一個,你們都得排着隊一個一個來(這就是單線程),那麼,開了兩個甚至多個窗口同時處理銀行的事務,那麼這樣子就等於開了多個線程(這就是多線程)。 數據結構

image

瞭解了多線程,咱們就開始轉移到咱們的主要戰地(Handler)上面,咱們使用Handler的時候,不少的時候都是在子線程中作完操做後須要UI線程(主線程)中更新界面,通常的作法就是多線程

使用

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

Handler是Android線程間通信的一種方式,它常被咱們用來更新UI,是的,我是這麼用,還有延時,只有拿出來總結的時候,纔會發現有時候使用的時候是有缺漏的。因此總結很重要啊!異步

目前爲止總結的一些使用狀況以下:async

  • 1.子線程發送消息到主線程ide

  • 2.在子線程中更新UI函數

  • 3.在子線程中使用Handleroop

  • 4.使用HandlerThread源碼分析

  • 5.Handler的callback回調

一、子線程發送消息到主線程
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這個線程間通訊是怎麼回事。

二、在子線程中更新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喔~!

三、在子線程中使用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,不知道表達的怎麼樣,可是,有幾個問題,在這裏我來解答一下

相關問題

一、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

二、消息(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

三、消息是怎麼分發的,又怎麼就到了本身實現的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類,裏面有個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;
}
複製代碼

這個就是消息結點了。

多線程測試

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
複製代碼

能夠看到,這裏咱們的隊列對新入的消息進行了列隊處理,而後,依次處理收到的消息。

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

相關文章
相關標籤/搜索