当前位置:   article > 正文

【Framework】透视Android中的Handler_rxbus 消息队列

rxbus 消息队列

准备对基于Android应用开发Framework层的内容进行学习回顾,学习一个新技术前我们一般都会灵魂三问:What-Why-How(是什么、为什么、怎么用)。源码的学习一定要亲自去看,用IDE或者Source Insight都没问题,如果看不懂就再看,多看,反复地看,相信我每看一次都有收获。尤其是Android中AMS这里,内容太多了,看资料和文档,并找到适合自己的方法论,先了解总体流程,再去抠细节,一上来就看细节会淹死在代码里。AMS学习基于Handler、Binder、Activity…ActivityThread最终围绕着Handler玩的,Handler是Framework学习的第一步,相对简单易学,也是基础,所以从Handler开始吧。
Framework学习过程Handler->Binder->应用启动流程/Zygote->dex文件构成/差分包->AMS->…

网上关于Android中Handler的文章已经多到数不清, 入门使用、工作流程、深入解析源码……等等,可见,Handler在Android中是一个基础且重要的知识块。我想写一篇从入门到深入,系统全面的讲解Handler机制的文章,这里总结一下自己对Handler的分析,记录一下自己的学习成果。内容应该会比较多,篇幅会很长。Let’s go.

一、概述

首先明确“是什么”的问题,准确的来讲,这里指的是Handler机制,Handler只是这个机制里面的一个角色,主角。开发中与Handler接触的比较多,所以用Handler代称Handler机制。Handler机制是Android中的一套线程间的通信机制,它的本质是负责消息的分发和处理,事务处理贯穿了整个Android系统层,Handler就是整个系统的维护者。看ActivityThread(AMS范畴)源码就可以理解这个点。

1.1 Handler存在的意义

  • 与web端开发的Ajax有异曲同工之妙,spring中就有
  • 降低Android开发难度
  • 几乎看不到多线程死锁问题

1.2 数据通信在开发中会带来问题

  1. 线程间如何通信?
    Handler通信的实现方案本质是内存共享。
  2. 为什么线程间不会干扰?
    内存管理设计思路优秀,加了锁,用锁机制设计了方案(这里需要了解为什么加锁和加锁的意义,后续一并解答)。
  3. 为什么wait()/notify()没有用武之地?
    Java语言的wait()/notify()是基于Java虚拟机的,而虚拟机是用C语言写,最终 wait()/notify()通过JVM编译成c/c++的wait()/notify(),所以,Handler设计的时候直接在Linux层将这部分逻辑进行了封装。

可以用以上三点评估一下自己对Handler的理解程度,理解了以上三个点,对Handler已经有了简单的理解,否则只是知道有、用过Handler,一知半解的水平。

1.3 Handler学习的重要性

思考一下,应用程序App是如何启动的?

lancher(app):zygote -> jvm -> ActvityThread.main()

点击一个应用图标就是点击Lancher(App,桌面程序)的一个"图标按键",Lancher管理整个屏幕并会做出响应,去启用Zygote,为每个App应用fork一个进程,对应分配、启动一个独立的JVM虚拟机(每个App单独分配JVM有什么好处?隔离,独立,自己出了问题不影响别的应用和整体系统)。

在Java中,每个程序都会有个main()方法,作为整个应用程序启动的入口。同样的每个App也都有自己的JVM,在ActivityThread就会有独立的main()方法,内部会初始化环境,打日志,重要的是执行Looper.prepareMainLooper()Looper.loop(),启动了主线程独有的Looper,主线程(App)就运行起来了。Looper.loop()内部是个死循环,意味着App所有的代码都是由Handler管理的。不断的把要做的事情以消息的形式分发出去处理。

Handler不仅仅用来做线程间通信的,线程间通信只是Handler的一个附属功能,Handler真正的功能是App应用所有代码都在Handler中运行,维持着Android应用的运行的整个框架。故此,Handler是至关重要的!

1.4 Handler机制里面涉及到如下几个类

包括但不限于:

  • Handler:负责收发消息
  • Looper:负责传送消息
  • MessageQueue:消息队列,仓库
  • Message:消息载体
  • ThreadLocal:Java中线程内部储存数据的工具类
    在这里插入图片描述

MessageQueue是Looper内部的一个属性,MessageQueue和Looper每个线程有且只有一个,而Handler是可以有很多个的(一定要记牢,要考)。

  1. 使用者使用线程的Looper构建Handler之后,通过Handler的send和post方法传送信息
  2. 信息会加入到MessageQueue中,等待Looper获取处理
  3. Looper会不断地从MessageQueue中获取Message然后交付给对应的Handler处理

这就是大名鼎鼎的Handler机制内部模式了,说难,其实也是很简单。

Handler内容很多:源码分析(理解epoll)、设计思路、设计模式,异步消息和同步消息,消息屏障、Handlerthread、IntentService……做好心理准备。

有了初步的认识之后,先看一下如何使用。

二、使用

平常使用Handler有两种不同的建立方式,但总体流程是相同的:

  1. 建立Looper
  2. 使用Looper建立Handler
  3. 启动Looper
  4. 使用Handler发送消息

首先,Looper可理解为循环器,就像“流水线”上的传送带,后面会详细讲到。每个线程只有一个Looper,通常主线程ActivityThread已经为我们建立好了,了解应用程序启动流程就知道,启动过程中调用了Looper.prepareMainLooper(),而在子线程就必须使用如下方法来初始化Looper:

Looper.prepare();
  • 1

第二步是建立Handler,也是开发者最熟悉的一步。有两种方法来建立Handler:传入callBack回调和继承。如下:

// 开发中,线程间通信:子线程携带Javabean数据-->主线程做显示

private TextView tvTxt;

// 第一种方法:使用callBack建立handler
// 主线程中处理收到的消息
private Handler mHandler = new Handler(msg -> {
   
    // 接收到消息,通常把数据交给主线程刷新界面
    if(msg.what == 100){
   
        Log.d("Handler", msg.obj.toString());
        // 问题一:Android不能在非UI线程操作界面UI刷新,需要在主线程完成
        tvTxt.setText(msg.obj.toString());// 界面刷新数据
    }
    // false 重写Handler类的handleMessage会被调用,true 不会被调用
    return false;
});

// 子线程使用Handler发送消息
private void useHandler() {
   
	// 问题二:Android不能在主线程/UI线程执行耗时任务,需要交给子线程处理
    new Thread(() -> {
   
        Message msg = Message.obtain();
        msg.what = 100;
        msg.obj = "土豆土豆,这是子线程发出的消息,听到请回答";
        mHandler.sendMessage(msg);
    }).start();
}

//-----------------------------

// 第二种方法:继承Handler并重写handlerMessage方法
static MyHandler extends Hanlder{
   
    public MyHandler(Looper looper){
   
        super(looper);
    }
    @Override
    public void handleMessage(Message msg){
   
        super.handleMessage(msg);
        // TODO(重写这个方法)
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48

注意第二种方法,要使用静态内部类,不然可能会造成內存泄露。原因是非静态内部类会持有外部类的引用,而Handler发出的Message会持有Handler的引用。如果这个Message是个延迟的信息,此时activity被退出了,但Message依然在“流水线”上,Message->handler->activity,那么activity就无法被回收,导致內存泄露。

两种Handler的写法各有千秋,继承方式可以写比较复杂的逻辑,callback方式适合比价简单的逻辑,看具体的业务来选择。

然後再调用Looper的loope方法来启动Looper:

Looper.loop();
  • 1

最后,就是使用Handler来传送信息了。当我们获得handler的实例之后,就可以通过他的sendMessage相方法和post相关方法来传送信息,如下:

handler.sendMessage(msg);
handler.sendMessageDelayed(msg,delayTime);
handler.post(runnable);
handler.postDelayed(runnable,delayTime);
  • 1
  • 2
  • 3
  • 4

Handler的使用就到这里结束。

如果在非UI线程去刷新界面我们就获得下面这个异常成就:

android.view.ViewRoot$CalledFromWrongThreadException: 
Only the original thread that created a view hierarchy can touch its views.
  • 1
  • 2

第一个问题,为什么非主线程不能更新UI?因为界面一般都是由主线程绘制的,所以界面的更新也就被限制在主线程内。这个异常是在viewRootIimpl.checkThread()方法中抛出来的,那可以绕过它吗?当然可以,在它还没建立的时候就可以偷偷更新UI了。阅读过Activity启动流程的会知道,ViewRootImpl是在onCreate()方法之后被建立的,所以可以在onCreate()方法中创建子线程偷偷更新UI。但还是那句话,可以,但没必要去绕过这个限制,这是Google为程序更加安全而设计的。

为什么不能在子线程去更新UI?因为这会让界面产生不可预期的结果。例如,主线程在绘制一个按钮过程中,另一个线程突然过来把按钮的大小改成两倍大,这个时候再回到主线程继续执行绘制逻辑,这个绘制效果就会出现问题。所以UI的访问是决不能是并发的。但,子线程又想更新UI,怎么办?加锁。加锁确实可以解决这个问题,但是会带来另外的问题:界面卡顿(开发大忌,会被用户骂娘的)。锁是比较重量级的操作,对于性能是有消耗的,而UI操作讲究快准狠,加锁会让UI操作性能降低。那有什么更好的方法?Handler就是解决这个问题的。

第二个问题,不能在主线程执行耗时操作。通常说,耗时操作包括网络请求、数据库操作等,这些操作会导致ANR(Application Not Responding)。这个是比较好理解的,没有什么问题,但是这两个问题结合起来,就有大问题了。数据请求一般是耗时操作,必须在子线程进行请求,而当请求完成之后又必须更新UI,UI又只能在主线程更新,这就导致代码必须切换线程执行,上面讨论了加锁是不可取的,那么Handler的重要性就体现出来了。

不用Handler可以吗?可以,但没必要。Handler是Google设计来方便开发者切换线程以及处理信息,然后你说我偏不用,我自己用Java工具类,自己弄个出来不可以吗?那……当然……也可以。早些年用BroadcastReceiver、接口回调,现在又涌现出通信总线类框架EventBus、RxBus,Jetpack又提供了LiveData,kotlin协程,Android消息通信仍在不断的演进之中,这些方案就不在本文讨论的范畴了。

三、Handler的工作流程

先上一张Handler工作流程的灵魂手绘,哈哈。
Handler的工作流程
Handler整个工作流程就像一个传送带,Looper作为动力角色,不停的去轮询消息队列MessageQueue。Looper断开的唯一条件是返回一个Message为null的消息,可以停止循环,这里带出一个面试题:什么时候返回一个空Message?放到后面去解答。

sendMessage()/postMessage()的时候会把消息发送到传送带(MessageQueue,队列)上面,队列会拉着整个传输系统滚动,由Looper.loop()实现,里面是个死循环,相当于是动力,一旦启动就源源不断的滚动,不断地轮询MessageQueue的next()方法,获取队列上所有的消息Message对象,从执行时间最早消息开始,一旦达到了消息的执行时刻,获取到消息通过dispatch()进行分发,完成了整个消息的通信。

一般,子线程发送消息,主线程处理消,这样就实现了线程间的通信。

看Handler源码思路
从使用开始:sendMessage()
…->…// 如果中间打断点看执行流程,需要自己编译模拟器,很复杂
结束处:handleMessage()

在Handler内提供了很多发送消息的方法,包括sendXXX()和postXXX():
Handler发送消息
网上有一副经典的方法的调用关系如下:
Handler发送消息关系图
去源码看,从sendMessage()方法开始,send和post这些方法最终都会调用sendMessageAtTime(),sendMessageAtTime()->Handler.enqueueMessage()->MessageQueue.enqueueMessage(),
这个过程就完成了把Message消息放到消息队列MessageQueue的工作,入队插入,把消息放到了传送带上。

消息放好了,有入就有出,怎么出去?
MessageQueue的next()方法从消息队列取出消息,返回Message对象

Message next() {
   
    // Return here if the message loop has already quit and been disposed.
    // This can happen if the application tries to restart a looper after quit
    // which is not supported.
    final long ptr = mPtr;
    if (ptr == 0) {
   
        return null;
    }

    int pendingIdleHandlerCount = -1; // -1 only during first iteration
    int nextPollTimeoutMillis = 0;
    for (;;) {
   
        if (nextPollTimeoutMillis != 0) {
   
            Binder.flushPendingCommands();
        }

        nativePollOnce(ptr, nextPollTimeoutMillis);

        synchronized (this) {
   
            // Try to retrieve the next message.  Return if found.
            final long now = SystemClock.uptimeMillis();
            Message prevMsg = null;
            ...
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28

谁来取消息,调用next()方法?
Looper。Looper内部有个loop()方法,内部死循环,调用next()返回Message对象

public static void loop() {
   
    ...
    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;
        }
        ...
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14

再调用dispatchMessage()最终到达handleMessage()

public void dispatchMessage(@NonNull Message msg) {
   
    if (msg.callback != null) {
   
        handleCallback(msg);
    } else {
   
        if (mCallback != null) {
   
            if (mCallback.handleMessage(msg)) {
   
                return;
            }
        }
        handleMessage(msg);
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17

Looper.loop() -> MessasgeQueue.next() -> handler.dispatchMessage() -> handler.handleMessage()

整个Handler调度流程到此为止,整个流程中唯一奔跑的就是Message消息。

细节上说,Message在动的过程中,Message对象是new或者obtain出来的,都是一块内存,就形成了一个内存的共享。如何体现从子线程到主线程的?内存不分线程,主线程和子线程都可以用,在能把一块内存进行移动拿走。那么,不断地new Message会不会挂掉、卡死?肯定会。

3.1 MessageQueue

MessageQueue是什么数据结构?单列表实现的优先级队列。
为什么叫优先级?看enqueueMessage()方法,Handler机制里面这个方法很关键,需要多看几遍。

boolean enqueueMessage(Message msg, long when) {
   
    if (msg.target == null) {
   
        throw new IllegalArgumentException("Message must have a target.");
    }

    synchronized (this) {
   
        if (msg.isInUse()) {
   
            throw new IllegalStateException(msg + " This message is already in use.");
        }

        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;
        boolean needWake;
        if (p == null || when == 0 || when < p.when) {
   
            // New head, wake up the event queue if blocked.
            msg.next = p;
            mMessages = msg;
            needWake = mBlocked;
        } else {
   
            // Inserted within the middle of the queue.  Usually we don't have to wake
            // up the event queue unless there is a barrier at the head of the queue
            // and the message is the earliest asynchronous message in the queue.
            needWake = mBlocked && p.target == null && msg.isAsynchronous();
            Message prev;
            for (;;) {
   
                prev = p;
                p = p.next;
                if (p == null || when < p.when) {
   
                    break;
                }
                if (needWake && p.isAsynchronous()) {
   
                    needWake = false;
                }
            }
            msg.next = p; // invariant: p == prev.next
            prev.next = msg;
        }

        // We can assume mPtr != 0 because mQuitting is false.
        if (needWake) {
   
            nativeWake(mPtr);
        }
    }
    return true;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65

看到msg.next = p;这个next属性我们要看Message对象

public final class Message implements Parcelable {
   
    public int what;
    public int arg1;
    public int arg2;
    public Object obj;
    ...
    public long when;
    ...
    Message next;
    ...
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11

Message对象里面next还是一个Message,这样看来Message对象就是一个单向结构:Message->next(Message)->next(Message)

接下来,Message的先后顺序,也就是优先级,看enqueueMessage()

boolean enqueueMessage(Message msg, long when) {
   
    ...
    synchronized (this) {
   
        ...
        if (p == null || when == 0 || when < p.when) {
   
            // New head, wake up the event queue if blocked.
            msg.next = p;
            mMessages = msg;
            needWake = mBlocked;
        } else {
   
            ...
            for (;;) {
   
                prev = p;
                p = p.next;
                if (p == null || when < p.when) {
   
                    break;
                }
                if (needWake && p.isAsynchronous()) {
   
                    needWake = false;
                }
            }
            msg.next = p; // invariant: p == prev.next
            prev.next = msg;
        }

        // We can assume mPtr != 0 because mQuitting is false.
        if (needWake) {
   
            nativeWake(mPtr);
        }
    }
    return true;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40

看到,按时间排序,死循环轮询中对比执行时间if (p == null || when < p.when) { 。无论哪个方式发送Message都会加上时间。(小声逼逼:这里是排序算法?插入排序)
按时间排序
MessageQueue为什么叫队列?
前面讲到ThreadLocalMap是一个“修改版的HashMap”,而MessageQueue就是一个“修改版的LinkQueue”。他也有两个关键的方法:入队(enqueueMessage)和出队(next)。这也是MessageQueue的重点所在。队列本身会要求先进先出,这里由于时间排序保证不了先进,MessageQueue的next()方法执行时每次取消息,Message不为空的时候直接取now时间点作比较

Message next() {
   
    ...
    for (;;) {
   
        ...
        synchronized (this) {
   
            ...
            if (msg != null) {
   
                if (now < msg.when) {
   
                    // Next message is not ready.  Set a timeout to wake up when it is ready.
                    nextPollTimeoutMillis = (int) Math.min(msg.when - now, Integer.MAX_VALUE);
                } else {
   
            ...
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18

MessageQueue总结

Message两大重点:阻塞休眠和队列操作。基本都是围绕着两点来展开。而原始码中还涉及到了同步屏障以及IdleHandler,这两部分内容我分开到了最后一部分的相关问题中讲。平时用的比较少,但也是比较重要的内容。

3.2 Looper

Looper可以说是Handler机制中的一个非常重要的核心。Looper相当于线程消息机制的引擎,驱动整个机制执行。Looper负责从队列中取出信息,然后交给对应的Handler去处理。如果队列中没有消息,则MessageQueue的next()方法会阻塞线程,等待新的信息的到来。每个线程有且只能有一个“引擎”,也就是Looper,如果没有Looper,那么信息机制就执行不起来,而如果有多个Looper,则会违背单线操作的概念,造成并发操作。

每个线程仅有一个Looper,由不同Looper分发的Message执行在不同的线程中。Looper的内部维护一个MessageQueue,当初始化Looper的时候会顺带初始化MessageQueue。Looper使用ThreadLocal来保证每个线程都有且只有一个相同的副本。

学习Handler机制源码看哪里?核心?Looper部分必须掌握的3点:

  1. 构造函数
  2. loop()
  3. ThreadLocal

如何初始化?私有的构造函数

private Looper(boolean quitAllowed) {
   
    mQueue = new MessageQueue(quitAllowed);
    mThread = Thread.currentThread();
}
  • 1
  • 2
  • 3
  • 4
  • 5

私有化谁来初始化?自己在prepare()

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));
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/weixin_40725706/article/detail/930710
推荐阅读
相关标签
  

闽ICP备14008679号