Android點將臺:烽火狼煙[-Handler-]

張風捷特烈發表於2019-01-29

個人所有文章整理在此篇,將陸續更新收錄:知無涯,行者之路莫言終(我的程式設計之路)

前言

哥發誓,這是我最後一次分析Handler(這是我第三次說這句話,希望不會有下次了)
說起Handler新手的小噩夢,用起來不難,理解起來煩神

Handler總覽

Handler.png


一、引入Handler的一般套路

1.說Handler一般套路是從一個異常開始

你以為我會這麼俗氣地從:非主執行緒禁止更新UI開始說Handler嗎?--是的
這個異常定義在ViewRootImpl裡,更新TextView的UI為什麼ViewRootImpl報異常?
子不教,父之過,教不嚴,師之惰唄。在framework的原始碼裡看一下吧,

非主執行緒禁止更新UI.png

---->[ViewRootImpl#checkThread]-------
void checkThread() {
    if (mThread != Thread.currentThread()) {
        throw new CalledFromWrongThreadException(
                "Only the original thread that created a view hierarchy can touch its views.");
    }
}
|--可見在checkThread時會判斷mThread是不是等於當前執行緒,如果不等於,就異常
|---那mThread又是什麼執行緒呢?

---->[ViewRootImpl#ViewRootImpl]-------
public ViewRootImpl(Context context, Display display) {
    mThread = Thread.currentThread();
|--在構造方法中被賦值的,也就是說是建立ViewRootImpl時所在的執行緒
|---ViewRootImpl又是在哪裡被建立的呢?這裡不深入講了,是在main執行緒
|---從異常來看是在進行requestLayout方法時崩的

---->[ViewRootImpl#requestLayout]-------
@Override
public void requestLayout() {
    if (!mHandlingLayoutInLayoutRequest) {
        checkThread();
        mLayoutRequested = true;
        scheduleTraversals();
    }
}
複製程式碼

2.然後再俗套的說一下怎麼用Handler解決

然後發現Handler好神奇啊,至於那裡神奇也說不出個道道,也就是神祕
Handler的面具之下隱藏著一個有點小複雜的訊息機制,這篇就來理一下

public class HandlerActivity extends AppCompatActivity {
    private TextView msgTv;
    private Handler mHandler = new Handler(){
        @Override
        public void handleMessage(Message msg) {
            msgTv.setText("hello");
        }
    };
    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.ac_handler);
        msgTv = findViewById(R.id.id_tv_handler);
        msgTv.setOnClickListener(v->{
            new Thread(new Runnable() {
                @Override
                public void run() {
                    mHandler.sendEmptyMessage(0x01);
                }
            }).start();
        });
    }
}
複製程式碼

3.原始碼中對Handler的描述

自己翻譯的,僅供參考,有不恰之處敬請指出
其中提到了MessageQueuemessage以及runnables

|--Handler允許您傳送和處理與執行緒的MessageQueue關聯的訊息和可執行物件。
|--每個Handler例項都與單個執行緒和該執行緒的訊息佇列相關聯。
|--當您建立一個新的Handler時,它會被繫結到正在建立它的執行緒的執行緒/訊息佇列上,
|--從那時起,它將向該訊息佇列傳遞訊息和可執行項,並在它們從訊息佇列發出時執行它們。

|--Handler有兩大主要的用處:
|--(1) 安排將訊息和可執行項在將來的某個點執行
|--(2) 將在不同執行緒上執行的操作加入佇列。

|--排程訊息是通過方法:
|--post(Runnable), postAtTime(Runnable, long), postDelayed(Runnable, long),
|--sendEmptyMessage(int), sendMessage(Message),sendMessageAtTime(Message, long),
|--sendMessageDelayed(Message, long). 
複製程式碼

4.主要成員變數

Handler主要成員變數.png

final Looper mLooper;
final MessageQueue mQueue;
final Callback mCallback;
final boolean mAsynchronous;
IMessenger mMessenger;
複製程式碼

5.構造方法

看這七個葫蘆娃,核心就只有兩個構造方法(三個hide的,外面不能用),其他四個可以用

Handler的七個建構函式.png

|--對具有指定回撥介面的[當前執行緒]使用Looper,並設定handler是否應該是非同步的。
|--預設情況下,handler是同步的,除非使用此建構函式建立一個嚴格非同步的handler。
|--(插句話,此方法是隱藏的,說明外部呼叫者是無法建立非同步的handler)

|--非同步訊息表示:不需要對同步訊息進行全域性排序的中斷或事件。
|--非同步訊息不受MessageQueue#enqueueSyncBarrier(long)引入的同步屏障的限制。

* @param callback 處理訊息的回撥介面,或null。
* @param async 如果為true,handler將為[傳送到它那的每個Message或Runnable]呼叫
* Message#setAsynchronous(boolean)
---->[Handler#Handler(Callback,boolean)]-------------------------------
public Handler(Callback callback, boolean async) {
    if (FIND_POTENTIAL_LEAKS) {//final常量---false,所以不管他  
        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();//通過Looper的myLooper方法返回值,為mLooper賦值
    if (mLooper == null) {//如果mLooper拿不到,報異常
        throw new RuntimeException(
            "Can't create handler inside thread that has not called Looper.prepare()");
    }
    mQueue = mLooper.mQueue;//mQueue通過mLooper獲取
    mCallback = callback;//入參
    mAsynchronous = async;//入參
}
|--貌似也沒有做什麼東西,只是將mLooper、mQueue、mCallback、mAsynchronous賦值
|--焦點在Looper.myLooper()上

---->[Handler#Handler(Callback,boolean)]-------------------------------
public Handler(Looper looper, Callback callback, boolean async) {
    mLooper = looper;
    mQueue = looper.mQueue;
    mCallback = callback;
    mAsynchronous = async;
}
|--三參的大佬挺任性,直接賦值,所以Handler的建構函式沒有什麼太特別的
|--下面看一下Looper類
複製程式碼

二、赤膽忠心:Looper

1:ThreadLocal

花了點時間看了ThreadLocal,已單獨成文,詳情見:java點將臺:多重影分身[-ThreadLocal-]
這裡不過多探討ThreadLocal,給個小例子,看它的功能

public class ThreadLocalTest {
    //例項化一個裝載Integer型別的ThreadLocal靜態變數
    static final ThreadLocal<Integer> sThreadLocal = new ThreadLocal<>();
    //用來測試的共享成員變數
    static int count = 10;
    public static void main(String[] args) throws InterruptedException {
        sThreadLocal.set(count);
        Thread thread = new Thread() {
            @Override
            public void run() {//新建執行緒
                count++;
                sThreadLocal.set(count);
                System.out.println("new :" + sThreadLocal.get());
            }
        };
        thread.start();
        thread.join();//為避免歧義,這裡新執行緒join,讓main執行緒的列印在新執行緒執行之後
        System.out.println("main:"+sThreadLocal.get());

    }
}
複製程式碼

結果2.png

可以看出,在新執行緒中對sThreadLocal.set(),並不會影響main執行緒的get()
所以共享成員變數count放在sThreadLocal這個籃子裡,可以保證執行緒間共享變數的獨立

ThreadLocal基本使用2.png


2.Looper類(looper:/'luːpə/ 迴圈者)
類名:Looper      父類:Object      修飾:public final
實現的介面:[]
包名:android.os   依賴類個數:7
內部類/介面個數:0
原始碼行數:344       原始碼行數(除註釋):158
屬性個數:8       方法個數:20       public方法個數:18
複製程式碼

Looper建構函式+主要成員變數.png

---->[Looper#成員變數]------------
//例項化一個裝載Looper型別的ThreadLocal靜態變數
static final ThreadLocal<Looper> sThreadLocal = new ThreadLocal<Looper>();
//似有靜態內部成員變數sMainLooper
private static Looper sMainLooper; 
//MessageQueue物件
final MessageQueue mQueue;
//執行緒物件
final Thread mThread;

---->[Looper#Looper]------------
private Looper(boolean quitAllowed) {
    mQueue = new MessageQueue(quitAllowed);
    mThread = Thread.currentThread();
}
|--在Looper的建構函式中,分別對mQueue,mThread進行初始化
|--注意建構函式是私有的,所以無法直接構造Looper物件

複製程式碼

所以本類中一定存在new Looper,搜尋一下:

搜尋.png

//公共靜態方法:準備
---->[Looper#prepare]------------
public static void prepare() {
    prepare(true);
}

//私有靜態方法:準備
---->[Looper#prepare(boolean)]------------
private static void prepare(boolean quitAllowed) {
    if (sThreadLocal.get() != null) {
    //如果sThreadLocal可與獲取到Looper,拋異常
    //也就是一個執行緒只能夠建立一個Looper
        throw new RuntimeException("Only one Looper may be created per thread");
    }
    //為空時建立Looper物件,並以sThreadLocal為鍵,new Looper(quitAllowed)為值
    //設定到當前執行緒的threadLocals(ThreadLocalMap物件)上
    sThreadLocal.set(new Looper(quitAllowed));
}

---->[Looper#prepare(boolean)]------------
 public static @Nullable Looper myLooper() {
        return sThreadLocal.get();
}
|--Looper會在因ThreadLocal在每個執行緒中獨立,非Looper生成的執行緒
|--sThreadLocal.get()會得到null,那prepare()是framework層的那裡初始化(即prepare)的呢?
複製程式碼

3.ActivityThread中對Looper的初始化

到framework層的原始碼去看一下,程式的入口main方法在ActivityThread中

ActivityThread中關於Looper.png

---->[ActivityThread#成員變數]---------
final Looper mLooper = Looper.myLooper();

---->[ActivityThread#main]---------
public static void main(String[] args) {
    //略...
    Looper.prepareMainLooper();
    ActivityThread thread = new ActivityThread();
    thread.attach(false);
    if (sMainThreadHandler == null) {
        sMainThreadHandler = thread.getHandler();
    }
    if (false) {
        Looper.myLooper().setMessageLogging(new
                LogPrinter(Log.DEBUG, "ActivityThread"));
    }
    // End of event ActivityThreadMain.
    Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
    Looper.loop();//開啟loop
    throw new RuntimeException("Main thread loop unexpectedly exited");
    
---->[Looper#prepareMainLooper]---------
public static void prepareMainLooper() {
    //這裡呼叫了prepare方法,為sThreadLocal設定了Looper值
    而且在main函式中呼叫,所線上程為main執行緒,即主執行緒
    prepare(false);
    synchronized (Looper.class) {
        if (sMainLooper != null) {//這裡看出只能prepared一次
            throw new IllegalStateException("The main Looper has already been prepared.");
        }
        sMainLooper = myLooper();//在這裡初始化成員變數sMainLooper
    }
}
複製程式碼

在這裡為了方便說明,debug來測試一下:
在main執行緒和子執行緒分別進行斷點除錯,看一下兩個執行緒中的Looper.myLooper()
由於Looper.prepare在main執行緒中執行,即:sThreadLocal.set在main執行緒
所以Looper.myLooper(),即sThreadLocal.get()在子執行緒無法獲取到Looper,這就是ThreadLocal的作用

Looper.myLooper()測試.png


三、再看Handler

1.使用含Callback的建構函式

以前是直接在Handler中覆寫handleMessage方法,AndroidStudio飄橙色,看起來很礙眼
我們完全可以傳回撥來構建Handler,加上Java8的簡化書寫看著爽多了,執行無誤

public class HandlerActivity extends AppCompatActivity {
    private TextView msgTv;
    private Handler mHandler ;
    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        mHandler = new Handler(msg -> {
            msgTv.setText("hello");
            return true;
        });
        setContentView(R.layout.ac_handler);
        msgTv = findViewById(R.id.id_tv_handler);
        msgTv.setOnClickListener(v->{
            Thread thread = new Thread(() ->
                    mHandler.sendEmptyMessage(0x01));
            thread.start();
        });
    }
}
複製程式碼

2.看一下CallBack介面
/**
 * Callback interface you can use when instantiating a Handler to avoid
 * having to implement your own subclass of Handler.
  在例項化處理程式時可以使用回撥介面,以避免必須實現自己的處理Handler子類。
 */
  ---->[Handler$Callback]----------------------
public interface Callback {
    /**
     * @param msg A {@link android.os.Message Message} object
     * @return True 如果不需要進一步處理
     */
    public boolean handleMessage(Message msg);
}


/**
 * Handle system messages here.(在這裡處理系統訊息)
 */
 ---->[Handler#dispatchMessage]----------------------
public void dispatchMessage(Message msg) {
    if (msg.callback != null) {
        handleCallback(msg);
    } else {
        if (mCallback != null) {//如果mCallback不為空
        //先回撥用 mCallback.handleMessage(msg),返回true的話,之後就return了  
        //這有什麼用? 如果你重寫Handler的handleMessage又有Callback都有的話
        // true就不會再去執行Handler的handleMessage 的方法了,例子如下
            if (mCallback.handleMessage(msg)) {
                return;
            }
        }
        handleMessage(msg);
    }
}
複製程式碼

3.即使用Callback又覆寫handleMessage

感覺很不是很常用,瞭解一下即可,
下面程式碼return true;結果是hello,return false;結果是hello2

public class HandlerActivity extends AppCompatActivity {
    private TextView msgTv;
    private Handler mHandler;

    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        mHandler = new Handler(msg -> {
            msgTv.setText("hello");
            return true;
        }) {
            @Override
            public void handleMessage(Message msg) {
                msgTv.setText("hello2");
            }
        };
        setContentView(R.layout.ac_handler);
        msgTv = findViewById(R.id.id_tv_handler);
        msgTv.setOnClickListener(v -> {
            Thread thread = new Thread(() -> {
                mHandler.sendEmptyMessage(0x01);
            });
            thread.start();
        });
    }
}
複製程式碼

4.在子執行緒中建立Handler物件是否有用?

現在將建立Handler放在子執行緒進行,不出所料,崩了

子執行緒中初始化Handler.png

public class HandlerActivity extends AppCompatActivity {
    private TextView msgTv;
    private Handler mHandler;
    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        Handler.Callback callback = msg -> {
            msgTv.setText("hello");
            return true;
        };
        setContentView(R.layout.ac_handler);
        msgTv = findViewById(R.id.id_tv_handler);
        msgTv.setOnClickListener(v -> {
            Thread thread = new Thread(() -> {
                mHandler = new Handler(callback);//<--建立Handler
                mHandler.sendEmptyMessage(0x01);
            });
            thread.start();
        });
    }
}

---->[看一下崩的原因]----------------
---->[Handler#Handler(Callback, boolean)]----------------
public Handler(Callback callback, boolean async) {
    //略...
    mLooper = Looper.myLooper();
    if (mLooper == null) {
        throw new RuntimeException(
            "Can't create handler inside thread that has not called Looper.prepare()");
    }
    mQueue = mLooper.mQueue;
    mCallback = callback;
    mAsynchronous = async;
}
|---前面我們已經debug過,在子執行緒中Looper.myLooper()為空,所以會崩掉
複製程式碼

5.如何在子執行緒中建立Handler

Handler的建構函式中有Looper引數,我們可以在外面獲取主執行緒的Looper物件,給Handler構造
除此之外Context也給我們提供了獲取main執行緒Looper的方法,debug可以看出,兩者是同一物件

如何在子執行緒中建立Handler.png

public class HandlerActivity extends AppCompatActivity {
    private TextView msgTv;
    private Handler mHandler;
    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        Handler.Callback callback = msg -> {
            msgTv.setText("hello");
            return true;
        };
        Looper looper = Looper.myLooper();//獲取main執行緒的looper
        setContentView(R.layout.ac_handler);
        msgTv = findViewById(R.id.id_tv_handler);
        msgTv.setOnClickListener(v -> {
            Thread thread = new Thread(() -> {
                mHandler = new Handler(looper,callback);
                mHandler.sendEmptyMessage(0x01);
            });
            thread.start();
        });
    }
}
複製程式碼

四、訊息佇列(難點,核心)

1.回到這個最簡單的訊息傳送
public class HandlerActivity extends AppCompatActivity {
    private TextView msgTv;
    private Handler mHandler;

    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        Handler.Callback callback = msg -> {
            msgTv.setText("hello");
            return true;
        };
        mHandler = new Handler(callback);

        setContentView(R.layout.ac_handler);
        msgTv = findViewById(R.id.id_tv_handler);

        msgTv.setOnClickListener(v -> {
            Thread thread = new Thread(() -> {
                mHandler.sendEmptyMessage(0x01);
            });
            thread.start();
        });
    }
}
複製程式碼

2.走一下Handler原始碼

一連串的sendMessageXXX基本上把傳送訊息的方法走了一遍,
但萬劍歸一,最終呼叫的是enqueueMessage(MessageQueue queue,Message msg, long uptimeMillis)

sendEmptyMessage(int what) : 傳送空訊息,
sendEmptyMessageDelayed(int what, long delayMillis),傳送延遲空訊息
sendMessageDelayed(Message msg, long delayMillis) 傳送延遲訊息
sendMessageAtTime(Message msg, long uptimeMillis) 定時傳送訊息
enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis) 訊息入隊
複製程式碼

Handler傳送訊息.png

---->[Handler#sendEmptyMessage]-----------------
|--只有訊息的what(用來表識訊息),呼叫:sendEmptyMessageDelayed
public final boolean sendEmptyMessage(int what){
    return sendEmptyMessageDelayed(what, 0);
}

---->[Handler#sendEmptyMessageDelayed]-----------------
|--只有訊息的what(用來表識訊息),延遲毫秒數,呼叫:sendMessageDelayed
public final boolean sendEmptyMessageDelayed(int what, long delayMillis) {
    Message msg = Message.obtain();
    msg.what = what;
    return sendMessageDelayed(msg, delayMillis);
}

---->[Handler#sendMessageDelayed]-----------------
|--接收一個訊息物件,延遲毫秒數,呼叫 sendMessageAtTime
public final boolean sendMessageDelayed(Message msg, long delayMillis){
    if (delayMillis < 0) {
        delayMillis = 0;
    }
    return sendMessageAtTime(msg, SystemClock.uptimeMillis() + delayMillis);
}

---->[Handler#sendMessageAtTime]-----------------
|--接收一個訊息物件,延遲毫秒數,呼叫 enqueueMessage
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);
}

---->[Handler#enqueueMessage]-----------------
private boolean enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis) {
    msg.target = this;//注意這裡講當前Handler作為訊息的target
    if (mAsynchronous) {
        msg.setAsynchronous(true);
    }
    return queue.enqueueMessage(msg, uptimeMillis);//呼叫了MessageQueue的enqueueMessage方法
}
複製程式碼

現在我們面前出現了兩個類:MessageMessageQueue


3.先看Message(訊息)類

Message的成員變數有點多

what----int 型,用來表識該資訊
arg1----int型,儲存簡單的int資料
arg2----int型,儲存簡單的int資料
obj --- 任意型別,儲存任意資料
replyTo ---- Messenger型別 可選的信使,在那裡回覆這條訊息可以傳送。具體如何使用它的語義取決於傳送方和接收方。
複製程式碼

Message部分成員變數.png

先從一個例子開始引入Message吧


3.1:new Message()Message.obtain()Handler.obtain()

可見三種方式都能運作,那麼有什麼區別呢?

三種建立訊息物件的方式.png

/**
 * 作者:張風捷特烈<br/>
 * 時間:2019/1/25/025:14:24<br/>
 * 郵箱:1981462002@qq.com<br/>
 * 說明:三種建立訊息物件的方式
 */
public class HandlerActivity extends AppCompatActivity {
    private TextView msgTv;
    private Handler mHandler;
    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        Handler.Callback callback = msg -> {
            String txt = (String) msg.obj;
            switch (msg.what) {
                case 0x01:
                    txt += "----第一條";
                    break;
                case 0x02:
                    txt += "----第二條";
                    break;
                case 0x03:
                    txt += "----第三條";
                    break;
            }
            msgTv.setText(txt);
            return true;
        };
        mHandler = new Handler(callback);
        setContentView(R.layout.ac_handler);
        msgTv = findViewById(R.id.id_tv_handler);
        msgTv.setOnClickListener(v -> {
            Thread thread = new Thread(() -> {
                //new Message()建立訊息
                Message newMsg = new Message();
                newMsg.what = 0x01;
                newMsg.obj = "玉面奕星龍";
                mHandler.sendMessage(newMsg);
                //Message.obtain()建立訊息
                Message msgObtain = Message.obtain();
                msgObtain.what = 0x02;
                msgObtain.obj = "張風捷特烈";
                mHandler.sendMessageDelayed(msgObtain, 3000);
                //mHandler.obtainMessage()建立訊息
                Message handlerObtain = mHandler.obtainMessage();
                handlerObtain.what = 0x03;
                handlerObtain.obj = "千里巫纓";
                mHandler.sendMessageDelayed(handlerObtain, 6000);
            });
            thread.start();
        });
    }
}
複製程式碼

3.2:三者的區別
1. new Message() 直接建立物件,沒什麼好說的


2.---->[Message]------------------
private static final Object sPoolSync = new Object();//鎖物件
private static Message sPool;//訊息池的連結串列首
private static int sPoolSize = 0;//當前訊息池的大小
private static final int MAX_POOL_SIZE = 50;//訊息池的最大尺寸

---->[Message#obtain]------------------
public static Message obtain() {
    synchronized (sPoolSync) {//同步鎖
        if (sPool != null) {//如果訊息池不為空
            Message m = sPool;//將sPool物件賦值給m物件
            sPool = m.next;//m的next賦值給sPool物件
            m.next = null;//將m的next置空
            m.flags = 0; // clear in-use flag
            sPoolSize--;//訊息池的大小-1
            return m;//將訊息返回
        }
    }
    return new Message();//如果訊息池為空時,返回新的Message物件
}
|-- 這裡很明顯使用了單連結串列,將一個訊息從訊息池中出列。
|-- 維護訊息池的好處不用多說,避免頻繁建立和銷燬Message,
|-- 比如頻繁地傳送訊息(輪播圖),每次切換一下發一個訊息,使用訊息池維護會更好

3.---->[Handler#obtainMessage]------------------
public final Message obtainMessage(){
    return Message.obtain(this);
}

public static Message obtain(Handler h) {
    Message m = obtain();
    m.target = h;
    return m;
}

|-- 獲取Message後將target設定成當前handler,這一步enqueueMessage中已經有了,所以兩者一樣
|-- 除此之外Message還有很多過載的 `Message obtain`,都是基於Message.obtain,
|-- 同時再為其設定一些屬性,本質上也沒有什麼太大的區別,自己看一下就行了
複製程式碼

4.MessageQueue訊息佇列

Message中並沒有為訊息池中新增訊息的方法,那麼訊息池是怎麼實現的?
Handler#enqueueMessage為切入點,看一下這個訊息是如何加入訊息佇列中的

---->[Handler#enqueueMessage]------------------------
private boolean enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis) {
    msg.target = this;//設定當前訊息的target為本Handler
    if (mAsynchronous) {
        msg.setAsynchronous(true);
    }
    return queue.enqueueMessage(msg, uptimeMillis);
}

---->[MessageQueue#enqueueMessage]------------------------
boolean enqueueMessage(Message msg, long when) {
    if (msg.target == null) {//target為null,即沒有對應的Handler,拋異常
        throw new IllegalArgumentException("Message must have a target.");
    }
    if (msg.isInUse()) {//訊息已經在使用,拋異常
        throw new IllegalStateException(msg + " This message is already in use.");
    }
    synchronized (this) {//this同步鎖
        if (mQuitting) {
            IllegalStateException e = new IllegalStateException(
                    msg.target + " sending message to a Handler on a dead thread");
            Log.w(TAG, e.getMessage(), e);
            msg.recycle();
            return false;
        }
        msg.markInUse();
        msg.when = when;//設定入隊時間
        Message p = mMessages;//將mMessages,即隊首賦給p
        boolean needWake;
        if (p == null || when == 0 || when < p.when) {//p為空
            //新建佇列頭,如果阻塞,喚醒事件佇列
            msg.next = p;//當前訊息作為佇列首
            mMessages = msg;//維護隊首
            needWake = mBlocked;
        } else {//表示當前訊息佇列中有訊息了
            needWake = mBlocked && p.target == null && msg.isAsynchronous();
            Message prev;//宣告前一個訊息
            for (;;) {//相當於while(true)的作用
                prev = p;//將原來的隊首元素賦給prev變數
                p = p.next;//將p的下一個訊息賦值給p
                if (p == null || when < p.when) {
                    break;
                }
                if (needWake && p.isAsynchronous()) {
                    needWake = false;
                }
            }
            msg.next = p; //將當前訊息的下一指向到p
            prev.next = msg;//prev指向當前訊息
        }
        // We can assume mPtr != 0 because mQuitting is false.
        if (needWake) {
            nativeWake(mPtr);
        }
    }
    return true;
}

>看文字估計挺暈的,以上面三個訊息為例,分析一下他們加入的流程
複製程式碼

第一條訊息入隊.png

當第二條訊息入隊時:msg:張風捷特烈
Message p = mMessages  p:玉面奕星龍 不為空,走下面
Message prev;//宣告前一個訊息
for (;;) {//相當於while(true)的作用
    prev = p;//prev:玉面奕星龍
    p = p.next;//p.next =null,執行後 p=null
    if (p == null || when < p.when) {
        break;
    }
    if (needWake && p.isAsynchronous()) {
        needWake = false;
    }
}
msg.next = p; //張風捷特烈-->null
prev.next = msg;//玉面奕星龍-->張風捷特烈-->null


當第三條訊息入隊時:msg:百里巫纓
Message p = mMessages  p:玉面奕星龍 不為空,走下面
Message prev;//宣告前一個訊息
for (;;) {//相當於while(true)的作用
    //第一次迴圈--prev:玉面奕星龍
    //第二次迴圈--prev:張風捷特烈
    prev = p;
    //第一次迴圈--p.next = 張風捷特烈,執行後 p=張風捷特烈
    //第二次迴圈--p.next = null,執行後 p=null
    p = p.next;
    if (p == null || when < p.when) {
        break;
    }
    if (needWake && p.isAsynchronous()) {
        needWake = false;
    }
}
msg.next = p; //百里巫纓-->null,此時prev:張風捷特烈
prev.next = msg;//玉面奕星龍-->張風捷特烈-->百里巫纓-->null

|-- 由此可見,每新增一條訊息都是新增到隊尾
複製程式碼

5.Looper的loop方法

將訊息加入佇列之後是如何取出的?又如何觸發Handler的dispatchMessage回撥方法?
這裡略去了打日誌的一些語句,可見loop方法一直將呼叫queue.next()直到msg == null
在拿到訊息後出發msg.target.dispatchMessage(msg);然後就豁然開朗了
但當沒有訊息時MessageQueue#next()會被阻塞,而導致loop阻塞。所以next無法讓輪循停止
要關閉迴圈使用MessageQueue#quit。

---->[Looper#loop]---------
public static void loop() {
    //獲取當前執行緒的Looper物件
    final Looper me = myLooper();
    if (me == null) {//如果為空,報異常。說明當前執行緒沒有Looper物件
        throw new RuntimeException("No Looper; Looper.prepare() wasn't called on this thread.")
    }
    //queue使用的是Looper中的mQueue(在建構函式中被初始化)
    final MessageQueue queue = me.mQueue;
    Binder.clearCallingIdentity();
    final long ident = Binder.clearCallingIdentity();
    for (;;) {//相當於while(true)的作用
        Message msg = queue.next(); // 訊息
        if (msg == null) {
            return;
        }
        //略...
        final long slowDispatchThresholdMs = me.mSlowDispatchThresholdMs;
        //略...
        final long start = (slowDispatchThresholdMs == 0) ? 0 : SystemClock.uptimeMillis();
        final long end;
        try {
            //在這裡呼叫了msg.target即該handler的dispatchMessage方法
            msg.target.dispatchMessage(msg);
            end = (slowDispatchThresholdMs == 0) ? 0 : SystemClock.uptimeMillis();
        } finally {
            if (traceTag != 0) {
                Trace.traceEnd(traceTag);
            }
        }
         //略...
        msg.recycleUnchecked();
    }
}

---->[MessageQueue#next]---------------
Message next() {
    final long ptr = mPtr;
    if (ptr == 0) {
        return null;
    }
    int pendingIdleHandlerCount = -1; 
    int nextPollTimeoutMillis = 0;
    for (;;) {
        if (nextPollTimeoutMillis != 0) {
            Binder.flushPendingCommands();
        }
        nativePollOnce(ptr, nextPollTimeoutMillis);//這裡用來一個native方法
        synchronized (this) {
            //嘗試檢索下一條訊息。如果發現返回。
            final long now = SystemClock.uptimeMillis();
            Message prevMsg = null;//前一個msg
            Message msg = mMessages;//當前msg為隊首
            if (msg != null && msg.target == null) {//一般都有target
                // 被障礙物擋住了。在佇列中查詢下一個非同步訊息
                do {
                    prevMsg = msg;
                    msg = msg.next;
                } while (msg != null && !msg.isAsynchronous());
            }
            if (msg != null) {
                if (now < msg.when) {
                    // 下一條訊息沒有準備好。設定超時以在何時喚醒
                    nextPollTimeoutMillis = (int) Math.min(msg.when - now, Integ
                } else {
                    //獲取一個msg-------
                    mBlocked = false;
                    if (prevMsg != null) {
                        prevMsg.next = msg.next;
                    } else {
                        mMessages = msg.next;
                    }
                    msg.next = null;
                    if (DEBUG) Log.v(TAG, "Returning message: " + msg);
                    msg.markInUse();
                    return msg;
                }
            } else {
                // 沒有訊息了
                nextPollTimeoutMillis = -1;
            }
            // 現在所有掛起的訊息都已完成,請處理退出訊息
            if (mQuitting) {
                dispose();
                return null;
            }
   
            if (pendingIdleHandlerCount < 0
                    && (mMessages == null || now < mMessages.when)) {
                pendingIdleHandlerCount = mIdleHandlers.size();
            }
            if (pendingIdleHandlerCount <= 0) {
                // No idle handlers to run.  Loop and wait some more.
                mBlocked = true;
                continue;
            }
            if (mPendingIdleHandlers == null) {
                mPendingIdleHandlers = new IdleHandler[Math.max(pendingIdleHandl
            }
            mPendingIdleHandlers = mIdleHandlers.toArray(mPendingIdleHandlers);
        }
        for (int i = 0; i < pendingIdleHandlerCount; i++) {
            final IdleHandler idler = mPendingIdleHandlers[i];
            mPendingIdleHandlers[i] = null; // release the reference to the hand
            boolean keep = false;
            try {
                keep = idler.queueIdle();
            } catch (Throwable t) {
                Log.wtf(TAG, "IdleHandler threw exception", t);
            }
            if (!keep) {
                synchronized (this) {
                    mIdleHandlers.remove(idler);
                }
            }
        }
        // Reset the idle handler count to 0 so we do not run them again.
        pendingIdleHandlerCount = 0;
        // While calling an idle handler, a new message could have been delivere
        // so go back and look again for a pending message without waiting.
        nextPollTimeoutMillis = 0;
    }
}

---->[Handler#dispatchMessage]---------------
public void dispatchMessage(Message msg) {
    if (msg.callback != null) {//如果msg有回撥
        handleCallback(msg);//處理msg的callback 
    } else {
        if (mCallback != null) {//這個上面舉例說明過,不說了
            if (mCallback.handleMessage(msg)) {
                return;
            }
        }
        handleMessage(msg);//回撥覆寫的handleMessage方法
    }
}

---->[Handler#handleCallback]---------------
private static void handleCallback(Message message) {
    message.callback.run();
}
複製程式碼

關於MessageQueue#next方法,無非就是讓訊息出隊,沒有訊息時next會一直阻塞
這裡涉及了native的方法,以及控制next的阻塞來完成延遲觸發,這裡native不展開,
想深入的,這裡推薦一篇文章Android MessageQueue訊息迴圈處理機制


五、Handler#postXXX與Message的callback

1.dispatchMessage方法分析

不知道你有沒有注意到,msg裡有的callback,為了強調dispatchMessage,這裡再看一次

dispatchMessage的流程.png

---->[Handler#dispatchMessage]---------------
public void dispatchMessage(Message msg) {
    if (msg.callback != null) {//如果msg有回撥
        handleCallback(msg);//處理msg的callback 
    } else {
        if (mCallback != null) {//這個上面舉例說明過,不說了
            if (mCallback.handleMessage(msg)) {
                return;
            }
        }
        handleMessage(msg);//回撥覆寫的handleMessage方法
    }
}

---->[Handler#handleCallback]---------------
private static void handleCallback(Message message) {
    message.callback.run();
}
複製程式碼

2.既然msg可以新增一個Runnable的callback那試一下唄

不幸的事callback是包訪問級別的,沒有提供set方法,所以用不了
但是提供了一個obtain的過載,可以放置callback

---->[Message#obtain(Handler, Runnable)]
public static Message obtain(Handler h, Runnable callback) {
    Message m = obtain();
    m.target = h;
    m.callback = callback;
    return m;
}
------------------------------------------------------
/**
 * 作者:張風捷特烈<br/>
 * 時間:2019/1/25/025:14:24<br/>
 * 郵箱:1981462002@qq.com<br/>
 * 說明:當msg自身有Runnable回撥時
 */
public class HandlerMsgWithCbkActivity extends AppCompatActivity {
    private TextView msgTv;
    private Handler mHandler;

    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        Handler.Callback callback = msg -> {
            msgTv.setText("回撥的handleMessage");
            return true;
        };

        mHandler = new Handler(callback){
            @Override
            public void handleMessage(Message msg) {
                msgTv.setText("覆寫的handleMessage");

            }
        };

        setContentView(R.layout.ac_handler);
        msgTv = findViewById(R.id.id_tv_handler);

        msgTv.setOnClickListener(v -> {
            Thread thread = new Thread(() -> {

                Message msgObtain = Message.obtain(mHandler, new Runnable() {
                    @Override
                    public void run() {
                        msgTv.setText("Message + Runnable");
                    }
                });
                msgObtain.what = 0x02;
                msgObtain.obj = "張風捷特烈";
                mHandler.sendMessageDelayed(msgObtain, 3000);
            });
            thread.start();
        });
    }
}

|--執行結果如下,結合dispatchMessage方法分析圖,應該足以說明
複製程式碼

jieguo.png


3.Handler的幾個postXXX方法

post(Runnable r).png

boolean post(Runnable r) post一個Runnable
boolean postAtTime(Runnable r, long uptimeMillis) 定時
boolean postAtTime(Runnable r, Object token, long uptimeMillis) 加token
boolean postDelayed(Runnable r, long delayMillis) 演示
boolean postAtFrontOfQueue(Runnable r) post到隊首

---->[Handler#post]-----------------
public final boolean post(Runnable r){
   return  sendMessageDelayed(getPostMessage(r), 0);
}

---->[Handler#getPostMessage]-----------------
private static Message getPostMessage(Runnable r) {
    Message m = Message.obtain();
    m.callback = r;
    return m;
}

|-- 可見post也沒有多麼高大上,只是呼叫了sendMessageDelayed而已
|-- 其中的Message通過getPostMessage獲取,在getPostMessage中
|-- 通過Message.obtain()從訊息池中取出一個訊息,並新增callback而已
|-- 後面幾個方法差不多,只是給Message多添一點資訊而已,Message理解了,就不成問題
|-- 這幾個方法相當於Handler給我們封裝了一下,要完成上面的測試,可以:

---->[HandlerMsgWithCbkActivity]-----------------
msgTv.setOnClickListener(v -> {
    Thread thread = new Thread(() -> {
        mHandler.postDelayed(new Runnable() {
            @Override
            public void run() {
                msgTv.setText("Message + Runnable");
            }
        }, 3000);
    });
    thread.start();
});

|-- 注意,使用postXXX,會讓Handler.callback和覆寫的handleMessage失效  
|-- 因為其本質上是通過Message的callback,前面已經講了Message.callback一旦存在  
|-- 是不會再走上兩者的。
複製程式碼

終曲、回看Handler:

這時候回頭看一下Handler背後的訊息機制:

訊息承載單體:Message  
訊息管理器:MessageQueue
訊息機制驅動力:Looper  
訊息處理器:Handler

---->[為了結尾點題,編個故事吧]---注:本故事純屬虛構-------------
傳說在100億年前,宇宙不止一個,人們生活的宇宙被稱為main宇宙
除了main宇宙之外,稱為子宇宙,由於宇宙眾多,被人們稱為三千宇宙
有些人試圖在子宇宙改變自己的樣子(包括服飾),開始新的生活,
但無一例外,全部灰飛煙滅,非main宇宙無法改變容飾成了平行宇宙法則
但main宇宙中人員眾多,資源相對匱乏,很多人只能去子宇宙去獲取資源

一個叫TextView的女生想要一件漂亮的衣服,作為男友的Handler義無反顧獨闖子宇宙,
他爆出了3件非常漂亮的衣服,知道讓TextView直接來到子宇宙穿上的話,她便會立刻灰飛煙滅  
於是他用3個空間立方(Message)將3件衣服分別裝入其中,並標識message的target是自己  
然後3個空間立方被依次放入了[平行宇宙傳送臺(MessageQueue)],
main宇宙中的強大Looper能源驅動著[平行宇宙傳送臺],自從三千宇宙誕生的那刻就開啟了(Looper.loop)  
當空間立方傳遞到主宇宙時,空間立方傳根據target找到自己的主人曾經的囑託(handleMessage) 
然後將三件美麗的服飾依次給TextView穿上,這樣就實現了子宇宙資源對宇宙的傳送  

有些常年在子宇宙工作的人,也經常使用這種機制,寄一封回家,傳達思念與祝福
而這些,就是當時他們的日常生活...
複製程式碼
最後一個問題:Handler只是能在主宇宙和子宇宙間傳遞資源嗎?

每個子宇宙都可以擁有僅屬於自己的一個Looper,子宇宙間也可以通過Handler進行通訊

/**
 * 作者:張風捷特烈<br/>
 * 時間:2019/1/25/025:14:24<br/>
 * 郵箱:1981462002@qq.com<br/>
 * 說明:子執行緒間通訊
 */
public class HandlerOtherActivity extends AppCompatActivity {
    private TextView msgTv;
    private Handler mHandler;
    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.ac_handler);
        msgTv = findViewById(R.id.id_tv_handler);
        msgTv.setOnClickListener(v -> {
            new Thread("第一子宇宙") {//第一子宇宙
                @Override
                public void run() {
                    Message newMsg = new Message();
                    newMsg.what = 0x01;
                    newMsg.obj = "玉面奕星龍";
                    mHandler.sendMessage(newMsg);
                    Log.e("HandlerOtherActivity", "當前執行緒名稱: " + Thread.currentThread().getName() + " 傳送:" + ne
                }
            }.start();
        });
        new Thread("第二子宇宙") {//第二子宇宙
            @Override
            public void run() {
                Looper.prepare();//讓當前子宇宙生成--looper能源
                //Handler通過第二子宇宙的looper能源能源構造
                mHandler = new Handler(msg -> {
                    Log.e("HandlerOtherActivity", "當前執行緒名稱: " + Thread.currentThread().getName() + " 接收:" + ms
                    return false;
                });
                Log.e("HandlerOtherActivity", "run: ");
                Looper.loop();//looper能源啟動--此時該執行緒會阻塞------下面的方法無法執行
                Log.e("HandlerOtherActivity", "run:-------- ");
            }
        }.start();
    }
}

|-- 注意點:當第一執行緒要向第二執行緒傳遞資料,mHandler要擁有第二執行緒的looper
|-- 也就是讓Handler在第二執行緒中建立,下圖已經很明確讓的表達出:
|-- 第一執行緒向第二執行緒傳遞了一條資訊,而且第二執行緒會阻塞在Looper.loop()是,下一句無法列印
複製程式碼

子執行緒通過handler通訊.png


為了讓你對Looper有更深的認識,換一種寫法

下面的寫法如果你看懂了,handler機制就不在話下了
一句話:在main執行緒中使用執行緒2的looper建立Handler,線上程1中通過Handler傳送訊息
結果訊息仍是線上程2中執行的,看日誌並未改變:(只有looper在哪個執行緒,訊息就會發到哪個執行緒)

子執行緒通過handler通訊.png

/**
 * 作者:張風捷特烈<br/>
 * 時間:2019/1/25/025:14:24<br/>
 * 郵箱:1981462002@qq.com<br/>
 * 說明:子執行緒間通訊
 */
public class HandlerOtherActivity extends AppCompatActivity {
    private TextView msgTv;
    private Handler mHandler;

    private Looper mOtherLooper;

    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        setContentView(R.layout.ac_handler);
        msgTv = findViewById(R.id.id_tv_handler);

        msgTv.setOnClickListener(v -> {

            new Thread("第一子宇宙") {//第一子宇宙
                @Override
                public void run() {
                    Message newMsg = new Message();
                    newMsg.what = 0x01;
                    newMsg.obj = "玉面奕星龍";
                    mHandler.sendMessage(newMsg);
                    Log.e("HandlerOtherActivity", "當前執行緒名稱: " + Thread.currentThread().getName() + " 傳送:" + newMsg.obj);
                }
            }.start();
        });

        new Thread("第二子宇宙") {//第二子宇宙
            @Override
            public void run() {
                Looper.prepare();//讓當前子宇宙生成--looper能源
                mOtherLooper = Looper.myLooper();

                Log.e("HandlerOtherActivity", "run: ");
                Looper.loop();//looper能源啟動--此時該執行緒會阻塞------下面的方法無法執行
                Log.e("HandlerOtherActivity", "run:-------- ");
            }
        }.start();

        try {
            Thread.sleep(10);//睡10ms讓mOtherLooper可以初始化
//            Handler通過第二子宇宙的looper能源能源構造
            mHandler = new Handler(mOtherLooper, msg -> {
                Log.e("HandlerOtherActivity", "當前執行緒名稱: " + Thread.currentThread().getName() + " 接收:" + msg.obj);
                return false;
            });

        } catch (InterruptedException e) {
            e.printStackTrace();
        }

    }
}
複製程式碼

相關文章