Handler原理

Handler是什么

Handler主要用于异步消息的处理: 封装了消息投递、消息处理等接口。当发出一个消息之后,首先进入一个消息队列,发送消息的函数即刻返回,而另外一个部分在消息队列中逐一将消息取出,然后对消息进行处理,也就是发送消息和接收消息不是同步的处理。 这种机制通常用来处理相对耗时比较长的操作。

/**
 * 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.

举例:

  • 当你在broadcast receiver的onReceiver做耗时的操作可能会报ANR,使用Handler把要处理的消息发出去。这样不会block此接口。
  • 当在一个接口中,需要接口返回后,再去执行某个操作,也可以使用Hanlder把Message或Runnable发出去。
  • 当想在非UI线程更新UI时,可以使用Handler把Message或Runnable发出去,在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给主线程发消息更新UI。注:如果直接在工作线程设置text,会扔如下异常:

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,乍一看,这个设计有点傻X啊,自己发消息,自己处理,就不会写个接口,自己调用自己吗?这是有病吗?

原因:其实sendMessage和handleMessage是可以不在同一个线程执行的。sendMessage可以在任何线程发送Message,handleMessage只是在Looper所在线程中回调。里面包含了线程转换(当然是同一个线程也没有问题,不管多少个线程,本质是不变的,只不过线程越多,处理越复杂)。

Handler源码分析

  • sendMessage消息发给了谁?发送传了什么数据?
  • handleMessage是何时回调的?
  • loop是什么时候被调用?
  • Looper是什么?如何让Handler机制的回调不在主线程中回调?
  • HandlerThread
  • MessageQueue又是什么,数据结构是什么?

Message时序图

image

上图可知,把Message发送给了MessageQueue,那发送的是那些信息呢?

 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外,还会把Handler对象本身、workdSourceUid发给MessageQueue。(可以思考一下为什么?)

handleMessage的回调逻辑

 /**
     * 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);进而调到handleMessage。

loop什么时候调用

前面说了,handleMessage是在指定的线程中调用,如果new 一个无参的handler,默认使用的调用线程的looper。既然要在此线程中运行,理论上会在线程run方法中调用loop。AS搜索调用的地方,相关的就是ActivityThread.java和HandlerThread.java。而HandlerThread.java里是在run方法中调用的,很好理解。但ActivityThread.java,居然是在main里面调用的。(开始学习Android时,最痛苦的是没有main。哈哈,现在有了)。ActivityThread其实就是Android的主线程,有main函数,程序执行的入口 。(讲Linux环境高级编程的时候,讲过进程运行起来后,会默认创建一个线程,可理解成主线程,在Android中叫UI线程)。
有关ActivityThread的介绍请参考
https://www.cnblogs.com/mingfeng002/p/10323668.html

Looper是什么,如何让Handler机制的回调不在主线程中回调?

/**
  * 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就是在一个线程中运行消息循环的帮助类。示例代码就是在一个自己创建的线程中完成消息轮询。通过此case可以实现消息在指定线程处理。

HandlerThread

HandlerThread是Android实现一个绑定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又是什么,数据结构是什么?

看loop的实现,在for中会调用Message msg = queue.next(); // might block取消息。而sendMessage会调用enqueueMessage送消息,这样就是可以玩下去了。

问题

  • 如果没有消息了,looper所在线程会怎样?
  • 空消息一段时间后,突然来了消息looper所在线程会怎样?
    看loop的实现就是for循环在那取消息,感觉像是轮询。如果是轮询,主线程就不用干其它事了。

这就需要研究MessageQueue的数据结构和实现原理了。
详细介绍参考:
https://blog.csdn.net/kisty_yao/article/details/71191175

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 160,026评论 4 364
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,655评论 1 296
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 109,726评论 0 244
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 44,204评论 0 213
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,558评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,731评论 1 222
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,944评论 2 314
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,698评论 0 203
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,438评论 1 246
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,633评论 2 247
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,125评论 1 260
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,444评论 3 255
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,137评论 3 238
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,103评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,888评论 0 197
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,772评论 2 276
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,669评论 2 271