Handler原理

Handler是什么

Handler主要用于異步消息的處理: 封裝了消息投遞、消息處理等接口。當(dāng)發(fā)出一個(gè)消息之后,首先進(jìn)入一個(gè)消息隊(duì)列,發(fā)送消息的函數(shù)即刻返回,而另外一個(gè)部分在消息隊(duì)列中逐一將消息取出,然后對消息進(jìn)行處理,也就是發(fā)送消息和接收消息不是同步的處理。 這種機(jī)制通常用來處理相對耗時(shí)比較長的操作。

/**
 * A Handler allows you to send and process {@link Message} and Runnable
 * objects associated with a thread's {@link MessageQueue}.  Each Handler
 * instance is associated with a single thread and that thread's message
 * queue.  When you create a new Handler, it is bound to the thread /
 * message queue of the thread that is creating it -- from that point on,
 * it will deliver messages and runnables to that message queue and execute
 * them as they come out of the message queue.

Handler的用途

 * There are two main uses for a Handler: (1) to schedule messages and
 * runnables to be executed at some point in the future; and (2) to enqueue
 * an action to be performed on a different thread than your own.

舉例:

  • 當(dāng)你在broadcast receiver的onReceiver做耗時(shí)的操作可能會(huì)報(bào)ANR,使用Handler把要處理的消息發(fā)出去。這樣不會(huì)block此接口。
  • 當(dāng)在一個(gè)接口中,需要接口返回后,再去執(zhí)行某個(gè)操作,也可以使用Hanlder把Message或Runnable發(fā)出去。
  • 當(dāng)想在非UI線程更新UI時(shí),可以使用Handler把Message或Runnable發(fā)出去,在UI線程中更新。

Handler使用

package com.example.handlertest;


import android.app.Activity;
import android.os.Bundle;
import android.os.Handler;
import android.os.Message;
import android.view.View;
import android.widget.TextView;

public class MainActivity extends Activity {
    private TextView mTextView;
    private Handler mHandler = new Handler() {
        @Override
        public void handleMessage(Message msg) {
            mTextView.setText((String) msg.obj);
        }
    };

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        mTextView = findViewById(R.id.text_view_id);
        mTextView.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                Thread thread = new Thread() {
                    @Override
                    public void run() {
                        // mTextView.setText("non-ui thread update text");
                        Message message = Message.obtain();
                        message.obj = "onClick";
                        mHandler.sendMessage(message);
                    }
                };
                thread.start();
            }
        });

    }
}

上面代碼是在工作線程通過Handler給主線程發(fā)消息更新UI。注:如果直接在工作線程設(shè)置text,會(huì)扔如下異常:

08-31 16:57:57.788 26607 26664 E AndroidRuntime: Process: com.example.handlertest, PID: 26607
08-31 16:57:57.788 26607 26664 E AndroidRuntime: android.view.ViewRootImpl$CalledFromWrongThreadException: Only the original thread that created a view hierarchy can touch its views.
08-31 16:57:57.788 26607 26664 E AndroidRuntime:        at android.view.ViewRootImpl.checkThread(ViewRootImpl.java:8535)
08-31 16:57:57.788 26607 26664 E AndroidRuntime:        at android.view.ViewRootImpl.requestLayout(ViewRootImpl.java:1519)
08-31 16:57:57.788 26607 26664 E AndroidRuntime:        at android.view.View.requestLayout(View.java:24642)
08-31 16:57:57.788 26607 26664 E AndroidRuntime:        at android.view.View.requestLayout(View.java:24642)
08-31 16:57:57.788 26607 26664 E AndroidRuntime:        at android.view.View.requestLayout(View.java:24642)
08-31 16:57:57.788 26607 26664 E AndroidRuntime:        at android.view.View.requestLayout(View.java:24642)
08-31 16:57:57.788 26607 26664 E AndroidRuntime:        at androidx.constraintlayout.widget.ConstraintLayout.requestLayout(ConstraintLayout.java:3172)
08-31 16:57:57.788 26607 26664 E AndroidRuntime:        at android.view.View.requestLayout(View.java:24642)
08-31 16:57:57.788 26607 26664 E AndroidRuntime:        at android.widget.TextView.checkForRelayout(TextView.java:9691)
08-31 16:57:57.788 26607 26664 E AndroidRuntime:        at android.widget.TextView.setText(TextView.java:6269)
08-31 16:57:57.788 26607 26664 E AndroidRuntime:        at android.widget.TextView.setText(TextView.java:6097)
08-31 16:57:57.788 26607 26664 E AndroidRuntime:        at android.widget.TextView.setText(TextView.java:6049)
08-31 16:57:57.788 26607 26664 E AndroidRuntime:        at com.example.handlertest.MainActivity$2$1.run(MainActivity.java:31)

Handler圖解

image

上圖可以看出Handler就是干兩件事件,sendMessage和handleMessage,乍一看,這個(gè)設(shè)計(jì)有點(diǎn)傻X啊,自己發(fā)消息,自己處理,就不會(huì)寫個(gè)接口,自己調(diào)用自己嗎?這是有病嗎?

原因:其實(shí)sendMessage和handleMessage是可以不在同一個(gè)線程執(zhí)行的。sendMessage可以在任何線程發(fā)送Message,handleMessage只是在Looper所在線程中回調(diào)。里面包含了線程轉(zhuǎn)換(當(dāng)然是同一個(gè)線程也沒有問題,不管多少個(gè)線程,本質(zhì)是不變的,只不過線程越多,處理越復(fù)雜)。

Handler源碼分析

  • sendMessage消息發(fā)給了誰?發(fā)送傳了什么數(shù)據(jù)?
  • handleMessage是何時(shí)回調(diào)的?
  • loop是什么時(shí)候被調(diào)用?
  • Looper是什么?如何讓Handler機(jī)制的回調(diào)不在主線程中回調(diào)?
  • HandlerThread
  • MessageQueue又是什么,數(shù)據(jù)結(jié)構(gòu)是什么?

Message時(shí)序圖

image

上圖可知,把Message發(fā)送給了MessageQueue,那發(fā)送的是那些信息呢?

 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);
    }

出了我們填充的what、arg1、arg2、obj外,還會(huì)把Handler對象本身、workdSourceUid發(fā)給MessageQueue。(可以思考一下為什么?)

handleMessage的回調(diào)邏輯

 /**
     * Handle system messages here.
     */
    public void dispatchMessage(@NonNull Message msg) {
        if (msg.callback != null) {
            handleCallback(msg);
        } else {
            if (mCallback != null) {
                if (mCallback.handleMessage(msg)) {
                    return;
                }
            }
            handleMessage(msg);
        }
    }
public static void loop() {
for (;;) {
// begin xxxxx
            Message msg = queue.next(); // might block
            if (msg == null) {
                // No message indicates that the message queue is quitting.
                return;
            }    
            try {
                msg.target.dispatchMessage(msg);
                if (observer != null) {
                    observer.messageDispatched(token, msg);
                }
                dispatchEnd = needEndTime ? SystemClock.uptimeMillis() : 0;
            } catch (Exception exception) {
                if (observer != null) {
                    observer.dispatchingThrewException(token, msg, exception);
                }
                throw exception;
            } 
        }
// end xxxxx
}

可以看到msg.target.dispatchMessage(msg);進(jìn)而調(diào)到handleMessage。

loop什么時(shí)候調(diào)用

前面說了,handleMessage是在指定的線程中調(diào)用,如果new 一個(gè)無參的handler,默認(rèn)使用的調(diào)用線程的looper。既然要在此線程中運(yùn)行,理論上會(huì)在線程run方法中調(diào)用loop。AS搜索調(diào)用的地方,相關(guān)的就是ActivityThread.java和HandlerThread.java。而HandlerThread.java里是在run方法中調(diào)用的,很好理解。但ActivityThread.java,居然是在main里面調(diào)用的。(開始學(xué)習(xí)Android時(shí),最痛苦的是沒有main。哈哈,現(xiàn)在有了)。ActivityThread其實(shí)就是Android的主線程,有main函數(shù),程序執(zhí)行的入口 。(講Linux環(huán)境高級編程的時(shí)候,講過進(jìn)程運(yùn)行起來后,會(huì)默認(rèn)創(chuàng)建一個(gè)線程,可理解成主線程,在Android中叫UI線程)。
有關(guān)ActivityThread的介紹請參考
https://www.cnblogs.com/mingfeng002/p/10323668.html

Looper是什么,如何讓Handler機(jī)制的回調(diào)不在主線程中回調(diào)?

/**
  * Class used to run a message loop for a thread.  Threads by default do
  * not have a message loop associated with them; to create one, call
  * {@link #prepare} in the thread that is to run the loop, and then
  * {@link #loop} to have it process messages until the loop is stopped.
  *
  * <p>Most interaction with a message loop is through the
  * {@link Handler} class.
  *
  * <p>This is a typical example of the implementation of a Looper thread,
  * using the separation of {@link #prepare} and {@link #loop} to create an
  * initial Handler to communicate with the Looper.
  *
  * <pre>
  *  class LooperThread extends Thread {
  *      public Handler mHandler;
  *
  *      public void run() {
  *          Looper.prepare();
  *
  *          mHandler = new Handler() {
  *              public void handleMessage(Message msg) {
  *                  // process incoming messages here
  *              }
  *          };
  *
  *          Looper.loop();
  *      }
  *  }</pre>
  */

Looper就是在一個(gè)線程中運(yùn)行消息循環(huán)的幫助類。示例代碼就是在一個(gè)自己創(chuàng)建的線程中完成消息輪詢。通過此case可以實(shí)現(xiàn)消息在指定線程處理。

HandlerThread

HandlerThread是Android實(shí)現(xiàn)一個(gè)綁定Looper消息處理線程類。核心代碼如下:

@Override
    public void run() {
        mTid = Process.myTid();
        Looper.prepare();
        synchronized (this) {
            mLooper = Looper.myLooper();
            notifyAll();
        }
        Process.setThreadPriority(mPriority);
        onLooperPrepared();
        Looper.loop();
        mTid = -1;
    }

使用如下:

private HandlerThread mRespondThread;
    private Handler mResponseHandler;
private void startThreads() {
        mRespondThread = new HandlerThread("Response_Camera_AR3.0");
        mRespondThread.start();
        mResponseHandler = new Handler(mRespondThread.getLooper()) {
            @Override
            public void handleMessage(Message msg) {
                super.handleMessage(msg);
            }
        };
    }

    private void stopThreads() {
        if (mRespondThread != null) {
            mRespondThread.quitSafely();
            try {
                mRespondThread.join(JOIN_THREAD_TIMEOUT);
                mRespondThread = null;
                mResponseHandler = null;
            } catch (InterruptedException e) {
                Log.e(TAG, "Interrupted while trying to join mRespondThread", e);
            }
        }
    }

MessageQueue又是什么,數(shù)據(jù)結(jié)構(gòu)是什么?

看loop的實(shí)現(xiàn),在for中會(huì)調(diào)用Message msg = queue.next(); // might block取消息。而sendMessage會(huì)調(diào)用enqueueMessage送消息,這樣就是可以玩下去了。

問題

  • 如果沒有消息了,looper所在線程會(huì)怎樣?
  • 空消息一段時(shí)間后,突然來了消息looper所在線程會(huì)怎樣?
    看loop的實(shí)現(xiàn)就是for循環(huán)在那取消息,感覺像是輪詢。如果是輪詢,主線程就不用干其它事了。

這就需要研究MessageQueue的數(shù)據(jù)結(jié)構(gòu)和實(shí)現(xiàn)原理了。
詳細(xì)介紹參考:
https://blog.csdn.net/kisty_yao/article/details/71191175

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

友情鏈接更多精彩內(nèi)容