1. 程式人生 > >Handler使用場景以及原始碼分析

Handler使用場景以及原始碼分析

路漫漫其修遠兮,吾將上下而求索

Handler的使用場景

  • 子執行緒操作完成之後,通知主執行緒執行操作

    首先在主執行緒建立一個Handler例項

    private val MSG_WHAT: Int = 1000
    
    private var mHandler = object : Handler() {
        override fun handleMessage(msg: Message?) {
            when (msg?.what) {
                MSG_WHAT -> {
                    val isMainThread = msg.obj as
    Boolean if (isMainThread) { showToast("what = 1000, 主執行緒") } else { showToast("what = 1000, 子執行緒") } } else -> showToast("default") } } }

然後在子執行緒操作(這裡的操作是判斷該執行緒是否是主執行緒)完成之後,通知主執行緒:

private fun checkIsMainThread() {
        thread {
            val message = Message()
            message.what = MSG_WHAT
            message.obj = isMainThread()
            mHandler.sendMessage(message)
        }.start()
    }
  • 判斷執行程式碼的執行緒是否是主執行緒

有時候我們需要判斷當前執行緒是否是主執行緒,這個時候就可以用帶Looper了,通過Looper,我們就可以知道當前執行緒是否為主執行緒:

    private fun isMainThread(): Boolean {
        return Looper.myLooper() == Looper.getMainLooper()
    }
  • 在子執行緒中直接切換到主執行緒執行程式碼

    開發中經常遇到的一個場景就是在子執行緒中執行一段操作之後(比如網路請求,IO),然後在主執行緒中更新UI,但是又沒有Context,也不想在新建一個Handler的Field接收訊息,這個時候就可以利用Handler的構造方法了,在構造方法中傳入一個Looper引數,這裡獲取的是主執行緒的Looper例項:

   private fun directUpdateUI() {
        thread {
            Thread.sleep(1000)
            Handler(Looper.getMainLooper()).post {
                btnDirectUpdateUI.text = "UI已經更新了"
            }
        }.start()
    }

當然,這個方法也可以用在兩個子執行緒之間的通訊上。

  • runOnUiThread(Runnable action)方法

    在Activity中,系統為我們提供了一個一個runOnUiThread方法,可以方便的在主執行緒中執行操作,而這個方法本質上也是通過Handler進行的操作:

 public final void runOnUiThread(Runnable action) {
        if (Thread.currentThread() != mUiThread) {
            mHandler.post(action);
        } else {
            action.run();
        }
    }

這裡的mHandler是在主執行緒中建立的,如果當前執行緒不是主執行緒,則利用主執行緒的Handler進行post操作,如果是主執行緒,則直接執行。

原始碼解析

  • Looper.prepare()
    在建立Handler例項之前,必須要先有Looper例項,至於我們平時在主執行緒為什麼沒有顯式呼叫這個方法,前面已經說過了。這裡來簡單的看下原始碼:

    static final ThreadLocal<Looper> sThreadLocal = new ThreadLocal<Looper>();
    
    public static void prepare() {
        prepare(true);
    }
    
    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中的ThreadLocal中有沒有存Looper例項,如果沒有,則新建一個該執行緒的Looper例項。ThreadLocal可以看做是一個儲存執行緒私有變數的資料結構,簡單的就get和set方法,這裡不詳述了。

  • 建立Handler

    一般的用法是在主執行緒中建立Handler:

        val handler = object : Handler() {
            override fun handleMessage(msg: Message?) {
                //接收到message之後執行的操作
            }
        }

    這裡如果要接收Message並執行一定的操作,就必須重寫handleMessage方法,因為Handler原始碼中這個方法是一個空方法。

    /**
     * Subclasses must implement this to receive messages.
     */
    public void handleMessage(Message msg) {
    }

    然後來看一下Handler的建構函式:

    public Handler() {
        this(null, false);
    }
    
    public Handler(Callback callback, boolean async) {
        //省略部分程式碼
        //獲取當前執行緒的Looper
        mLooper = Looper.myLooper();
        //如果當前執行緒沒有Looper,丟擲異常
        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;
    }

    從上面程式碼可以看出,如果要在一個執行緒中建立Handler,一定要先執行Looper.prepare()方法,建立一個Looper例項,不然會丟擲異常。
    這裡可能會有一個疑問,我們在主執行緒中建立Handler的時候並沒有執行Looper.prepare()方法啊,為什麼可以正常執行?
    答案是主執行緒其實在很早之前就已經建立了Looper例項,具體的可以參考ActivityThread這個類中的main方法,具體的這裡就不講了:

    public static void main(String[] args) {
        Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "ActivityThreadMain");
        SamplingProfilerIntegration.start();
        CloseGuard.setEnabled(false);
        Environment.initForCurrentUser();
        EventLogger.setReporter(new EventLoggingReporter());
        final File configDir = Environment.getUserConfigDirectory(UserHandle.myUserId());
        TrustedCertificateStore.setDefaultUserDirectory(configDir);
        Process.setArgV0("<pre-initialized>");

        //在這裡建立主執行緒Looper
        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"));
        }
        Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
        //開始訊息佇列迴圈
        Looper.loop();

        throw new RuntimeException("Main thread loop unexpectedly exited");
    }

上面程式碼中建立主執行緒的Looper,看一下具體程式碼:

/**
     * Initialize the current thread as a looper, marking it as an
     * application's main looper. The main looper for your application
     * is created by the Android environment, so you should never need
     * to call this function yourself.  See also: {@link #prepare()}
     */
    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例項到Handler的建構函式中,用來直接切換到主執行緒執行,或者切換到別的執行緒執行程式碼,來看看原始碼:


    public Handler(Looper looper) {
        this(looper, null, false);
    }

     public Handler(Looper looper, Callback callback, boolean async) {
        mLooper = looper;
        mQueue = looper.mQueue;
        mCallback = callback;
        mAsynchronous = async;
    }

就是將傳入的Looper例項賦值給mLooper。mQueue則是取得mLooper的訊息佇列。

  • Looper.loop()
    建立完Handler例項之後,需要執行Looper.loop()方法,開始從訊息佇列中取出訊息,並分發給對應的Handler進行處理。

    /**
     * Run the message queue in this thread. Be sure to call
     * {@link #quit()} to end the loop.
     */
    public static void loop() {
        final Looper me = myLooper();
        if (me == null) {
            throw new RuntimeException("No Looper; Looper.prepare() wasn't called on this thread.");
        }
        final MessageQueue queue = me.mQueue;
    
        for (;;) {
            //從訊息佇列中取出訊息
            Message msg = queue.next(); // might block
            if (msg == null) {
                // No message indicates that the message queue is quitting.
                return;
            }
    
            //...
    
            try {
                    //分發訊息
                msg.target.dispatchMessage(msg);
            } finally {
                if (traceTag != 0) {
                    Trace.traceEnd(traceTag);
                }
            }
    
            msg.recycleUnchecked();
        }
    }

    這裡從Looper持有的訊息佇列物件中迴圈取出訊息,然後利用msg.target.dispatchMessage(msg)進行訊息分發,這裡的msg.target其實就是Handler,然後執行Handler的dispatchMesage方法.

  • 訊息分發

    看看dispatchMessage的原始碼:

    /**
     * Handle system messages here.
     */
    public void dispatchMessage(Message msg) {
        if (msg.callback != null) {
            handleCallback(msg);
        } else {
            if (mCallback != null) {
                if (mCallback.handleMessage(msg)) {
                    return;
                }
            }
            handleMessage(msg);
        }
    }
    
    private static void handleCallback(Message message) {
        message.callback.run();
    }
    

    如果Message自身的callback不為空,即Message的Runnable不為空,則執行Runable,否則判斷Handler的Callback是否為空,不為空則執行Callback的handleMessage方法,再根據返回值判斷是否執行Handler自身的handleMessage方法。

總結

從一個非主執行緒建立Handler對於分析Handler的流程將會更加直觀,這裡梳理一下流程:

  • 利用Looper.prepare()初始化Looper
  • 建立Handler,其中Handler構造方法可以傳入相關引數實現一些特殊的功能
  • 執行 Looper.loop()方法,開始從訊息佇列中取出訊息,利用Handler的dispatchMessage方法進行分發
  • Handle通過post,sendMessage等方法,傳送訊息,其實就是將Message新增到MessageQueue中
  • Handler通過handleMessage方法處理接收到的Message