Android面经_安卓基础面全解析(7 30)之消息机制全解析(上)_cvte message消息队列

  • Handler 引起的内存泄露原因以及最佳解决方案 ⭐⭐⭐⭐⭐
  • 为什么我们能在主线程直接使用 Handler,而不需要创建 Looper ? ⭐⭐⭐⭐⭐
  • Handler、Thread和HandlerThread的差别 ⭐⭐⭐⭐
  • 子线程中怎么使用 Handler? ⭐⭐⭐⭐
  • 为什么在子线程中创建 Handler 会抛异常?⭐⭐⭐⭐
  • Handler 里藏着的 Callback 能干什么?⭐⭐⭐
  • Handler 的 send 和 post 的区别?⭐⭐⭐⭐
  • 创建 Message 实例的最佳方式 ⭐⭐⭐
  • Message 的插入以及回收是如何进行的,如何实例化一个 Message 呢?⭐⭐⭐
  • 妙用Looper机制,或者你知道Handler机制的其他用途吗?⭐⭐⭐
  • Looper.loop()死循环一直运行是不是特别消耗CPU资源呢?不会造成应用卡死吗?⭐⭐⭐⭐⭐
  • MessageQueue 中如何等待消息?为何不使用 Java 中的 wait/notify 来实现阻塞等待呢?⭐⭐
  • 你知道延时消息的原理吗?⭐⭐⭐⭐
  • handler postDelay这个延迟是怎么实现的?⭐⭐⭐⭐
  • 如何保证在msg.postDelay情况下保证消息次序?⭐⭐⭐
  • 更新UI的方式有哪些 ⭐⭐⭐⭐
  • 线程、Handler、Looper、MessageQueue 的关系?⭐⭐⭐⭐
  • 多个线程给 MessageQueue 发消息,如何保证线程安全?⭐⭐⭐
  • View.post 和 Handler.post 的区别?⭐⭐⭐
  • 你知道 IdleHandler 吗?⭐⭐

一个建议:学习Handler一定要啃下源码!

看完以下的解析,一定可以让面试官眼前一亮。

目录

  • 1、什么是Handler消息传递机制
    • 1.1 Handler的组成
    • 1.2 Handler的使用和代码实例
  • 2、源码分析
    • 2.1 Handler机制源码分析
    • 2.2 Handler发送信息的方法有几种
      • 2.2.1 post方法
      • 2.2.2 send方法
    • 2.3 Message源码分析
    • 2.4 Looper源码分析
    • 2.5 MessageQueue源码分析
      • 2.5.1 存放信息的方法:enqueueMessage()
      • 2.5.2 获取消息的方法:next()
  • 3、一问一答:常见问题汇总
    • 3.1 Handler 引起的内存泄露原因以及最佳解决方案
    • 3.2 为什么我们能在主线程直接使用 Handler,而不需要创建 Looper ?
    • 3.3 Handler、Thread和HandlerThread的差别
    • 3.4 子线程中怎么使用 Handler?
    • 3.5 为什么在子线程中创建 Handler 会抛异常?
    • 3.6 Handler 里藏着的 Callback 能干什么?
    • 3.7 Handler 的 send 和 post 的区别?
    • 3.8 创建 Message 实例的最佳方式
    • 3.9 Message 的插入以及回收是如何进行的,如何实例化一个 Message 呢?
    • 3.10 妙用Looper机制,或者你知道Handler机制的其他用途吗?
    • 3.11 Looper.loop()死循环一直运行是不是特别消耗CPU资源呢?不会造成应用卡死吗?
    • 3.12 MessageQueue 中如何等待消息?为何不使用 Java 中的 wait/notify 来实现阻塞等待呢?
    • 3.13 你知道延时消息的原理吗?
    • 3.14 handler postDelay这个延迟是怎么实现的?
    • 3.15 如何保证在msg.postDelay情况下保证消息次序?
    • 3.16 更新UI的方式有哪些
    • 3.17 线程、Handler、Looper、MessageQueue 的关系?
    • 3.18 多个线程给 MessageQueue 发消息,如何保证线程安全?
    • 3.19 View.post 和 Handler.post 的区别?
    • 3.20 你知道 IdleHandler 吗?

1、什么是Handler消息传递机制

1.1 Handler的组成

Handler消息传递机制,从名字看就可以联想到是Handler会发送出一个一个消息,同时系统会根据每一个不同的消息进行不同的处理流程。具体如何实现,直接上图。

在这里插入图片描述

图片来自参考目录1。

Handler由3个模块组成,Handler、MessageQueue、Looper:

  • Handler:主要作用是发送信息以及处理信息(为何发送还自己处理?),其中发送的信息叫作Message,可以传递数据哦;
  • MessageQueue:消息队列,由一个一个Message汇成,遵循先进先出规则,由Looper进行管理;
  • Looper:从MessageQueue里读取消息,并按消息分发机制分配到目标Handler进行消息处理。

1.2 Handler的使用和代码实例

结合1.1小节,简述下完整的流程:

  1. Handler通过sendMessage()发送消息Message,每个Message都在MessageQueue里排队。
  2. Looper通过loop()从MessageQueue里读取Message,并按消息分发机制分配到目标Handler进行消息处理。
  3. 目标Handler收到需要处理的Message时,调用自己的handleMessage()方法来处理Message,因此自定义的Handler都需要重写handlerMessage方法。
private void updateBluetoothStatus(int state) {
    new Thread(new Runnable() {
        @Override
        public void run() {
            ...
            //1. 子线程执行完耗时操作后,触发更新蓝牙列表
            if (mHandler != null) {
                mHandler.removeMessages(H.MSG_XURUI_BLUETOOTH_LIST);
                mHandler.sendEmptyMessage(H.MSG_XURUI_BLUETOOTH_LIST);
            }
        }
    }).start();
}

private void updateBluetoothList() {
    //3. 更新蓝牙列表
}
private static class H extends BaseHandler<BlueToothTestActivity> {
    public static final int MSG_XURUI_BLUETOOTH_LIST = 100;
    public H(BlueToothTestActivity activity) {
        super(activity);
    }

    @Override
    protected void handleMessage(BlueToothTestActivity activity, Message msg) {
        if (msg.what == MSG_XURUI_BLUETOOTH_LIST) {
            //2. 通过Handler机制调用updateBluetoothList
            activity.updateBluetoothList();
        }
    }
}

我们可以看到子线程执行完耗时操作后,需要更新UI的时候,需要返回到主线程(子线程不能或不建议更新UI),因此在子线程通过Handler机制,很容易切换到Handler所在的主线程去执行updateBluetoothList()函数来更新UI。

仔细的同学应该有看到BaseHandler类,为何要封装Handler的基类,这是工程上提高代码稳定性的常用做法,后续马上揭晓,但需循序渐进,我们先看看Handler源码。

2、源码分析

因为Handler在面试中的地位,良心建议,再难都必须过一遍源码,我尽量讲的更容易理解。

2.1 Handler机制源码分析

创建Handler只要执行。。。,其源码见下分析:

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

public Handler(Callback callback, boolean async) {
    //1:获取Looper
    mLooper = Looper.myLooper();
    //如果获取不到Looper会提示我们要事先运行Looper.prepare()
    if (mLooper == null) {
        throw new RuntimeException(
            "Can't create handler inside thread " + Thread.currentThread()
                    + " that has not called Looper.prepare()");
    }
    //2:消息队列,来自Looper对象
    mQueue = mLooper.mQueue;
    mCallback = callback;
    //设置消息为异步处理方式
    mAsynchronous = async;
}

注释2很重要,如果面试官问Looper和MessageQueue怎么关联在一起?那么现在就知道是在Handler的构造函数中关联的。重点继续看看注释1,Looper.myLooper()做了什么?

 public static @Nullable Looper myLooper() {
        return sThreadLocal.get();//1
    }

从注释1看出来,仅仅是从sThreadLocal获取一个Looper变量。思路继续,我们从sThreadLocal变量入手,如何初始化的?在哪里使用?有get()那就一定有set()。继续研究源码:

static final ThreadLocal<Looper> sThreadLocal = new ThreadLocal<Looper>();
public static void prepare() {
    prepare(true);
}
private static void prepare(boolean quitAllowed) {
    if (sThreadLocal.get() != null) {
        //1:一个线程只能有一个 Looper
        throw new RuntimeException("Only one Looper may be created per thread");
    }
    sThreadLocal.set(new Looper(quitAllowed));//2
}

如果某些数据在不同的线程作用域下有各自的数据副本,那么可以用ThreadLocal对数据进行线程隔离。从两个两个函数很容易知道,通过调用Looper.prepare()函数即可执行到注释2,将新new出来的Looper放到sThreadLocal里,供Looper.myLooper()去获取。

如果已经有实际开发经验的同学可能会想到通过Android studio创建的MainActivity可以直接用Handler,然而上一段才说使用Handler需要提前执行Looper.prepare()函数。其实这不矛盾,因为MainActivity所以在的主线程ActivityThread的main()函数已经自动执行了Looper.prepare()函数。
ActivityThread的main方法源码如下:

    public static void main(String[] args) {
	...
        Looper.prepareMainLooper(); //1
        Looper.loop();
        throw new RuntimeException("Main thread loop unexpectedly exited");
    }
    
    public static void prepareMainLooper() {
        prepare(false); //2
        synchronized (Looper.class) {
            if (sMainLooper != null) {
                throw new IllegalStateException("The main Looper has 
                already been prepared.");
            }
            sMainLooper = myLooper();
        }
    }


ActivityThread的main方法通过调用prepareMainLooper()进而调用prepare(false)方法,进而new一个Looper放到sThreadLocal里。

2.2 Handler发送信息的方法有几种

最重要的源码已经分析完了,我们先来看看Hander如何发送信息。发送消息有两种方式,post和send。

2.2.1 post方法

post方法有以下两个函数post()和postDelayed(),用法是Handler.post(Runnable r),举个例子:

public void readLog() {
    myHandler.post(XRReadLog);
}


Runnable XRReadLog = new Runnable() {
        @Override
        public void run() {
           // do something
        }
    };

用法非常简单,进一步看看post源码:

public final boolean post(Runnable r){
    	return  sendMessageDelayed(getPostMessage(r), 0);
    }
    public final boolean postDelayed(Runnable r, long delayMillis){
    	return sendMessageDelayed(getPostMessage(r), delayMillis);
    }

可以看到只是调用了sendMessageDelayed(),这就是send方法了。

:getPostMessage()方法是将Runnable转换为Message,因为sendMessageDelayed接收的是Message参数。

 private static Message getPostMessage(Runnable r) {
        Message m = Message.obtain();
        m.callback = r;
        return m;
    }

2.2.2 send方法

send方法有3个,函数作用从名称上就可以一眼看出:

  • sendMessage():发送带有数据的信息
  • sendMessageDelayed():发送带有数据的信息并延迟执行
  • sendEmptyMessage():发送空信息
    就以sendMessageDelayed为例看看使用方法:
Message logMessage = Message.obtain();
logMessage.what = WIFI_CONNECT_LOG; //信息的名称
logMessage.arg1 = 1;
logMessage.obj = "some thing";      //信息所携带的数据
myHandler.sendMessageDelayed(logMessage,1000); //延迟1000毫秒执行

用法也比较简单,根据自己需要决定是否携带数据,是否延迟执行。看一下源码:

  public final boolean sendMessage(Message msg){
        return sendMessageDelayed(msg, 0);
    }
    public final boolean sendEmptyMessage(int what){
        return sendEmptyMessageDelayed(what, 0);
    }
    public final boolean sendMessageDelayed(Message msg, long delayMillis){
        if (delayMillis < 0) {
            delayMillis = 0;
        }
        return sendMessageAtTime(msg, SystemClock.uptimeMillis() + delayMillis);//1
    }

可以看出来,无论是post还是send,最后都是用SystemClock.uptimeMillis()获取系统开机到当前的时间,加上我们设置的delayMillis时间,并调用sendMessageAtTime()方法做进一步逻辑。

:不能用System.currentTimeMilis(系统时间),因为用户很可能自己修改系统时间。

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

    private boolean enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis) {
        msg.target = this;
        if (mAsynchronous) {
            msg.setAsynchronous(true);
        }
        //转到 MessageQueue 的 enqueueMessage 方法
        return queue.enqueueMessage(msg, uptimeMillis);//1
    }

通过注释1,Handler发送的消息最终发送到消息队列。2.5小节详细讲queue.enqueueMessage(),我们先一起看一下Message和Looper的源码,这样可以更好的理解。

2.3 Message源码分析

Handler机制传递的Message是怎么生成的,每个Message里面都有什么数据。看下源码就可以很清楚的了解了:

public final class Message implements Parcelable {
    
    public int what;//消息的标识
    
    //系统自带的两个参数,注意是int型的,其他类型数据不能放这里
    public int arg1;
    public int arg2;

    long when;  //处理消息的相对时间
    Bundle data;//可以使用message.setData()使用bundle的实型传参
    Handler target;
    Runnable callback;
    
    Message next;	//消息池是以链表结构存储 Message
    private static Message sPool;	//消息池中的头节点
    
    public Message() {
    }
    
    //1:直接通过链表操作获得 Message (注释1)
    public static Message obtain() {
        synchronized (sPoolSync) {
            if (sPool != null) {
                //取出头节点返回
                Message m = sPool;
                sPool = m.next;
                m.next = null;
                m.flags = 0; // clear in-use flag
                sPoolSize--;
                return m;
            }
        }
        return new Message();
    }
    
    //回收消息
    public void recycle() {
        if (isInUse()) {
            if (gCheckRecycle) {
                throw new IllegalStateException("This message cannot be recycled because it "
                        + "is still in use.");
            }
            return;
        }
        recycleUnchecked();
    }

    void recycleUnchecked() { //2
        //清空消息的所有标志信息
        flags = FLAG_IN_USE;
        what = 0;
        arg1 = 0;
        arg2 = 0;
        obj = null;
        replyTo = null;
        sendingUid = -1;
        when = 0;
        target = null;
        callback = null;
        data = null;

        synchronized (sPoolSync) {
            if (sPoolSize < MAX_POOL_SIZE) {
                //链表头插法
                next = sPool;
                sPool = this;
                sPoolSize++;
            }
        }
    }
}

通过源码,首先要知道Message可以传递数据,主要的方式有:

  • what:用户自定义的消息识别标识
  • arg1和arg2:只能传递int型参数
  • obj:可以传递object类型参数,用得最多的就是传递String
  • data:可以使用bundle类型传递
    可以看看2.2.2小节的代码案例加深理解,所以在上面注释2的recycleUnchecked()方法里需要把对应的变量重置。

同时,从2.2.2小节的代码案例也可以看到我是用Message.obtain()来获得一个Message实例的,因为这种方案是直接从Message的消息池里直接获取,避免了new Message()的重复创建开销。

2.4 Looper源码分析

在2.1小节,就有分析到Looper.myLooper()、Looper.prepare() 、 Looper.prepareMainLooper()这三个方法,在此再做个复习:

  • Looper.myLooper():从sThreadLocal获取已创建的Looper实例;
  • Looper.prepare():创建Looper实例;
  • Looper.prepareMainLooper() :主线程main函数中调用的方法,在该方法里会调用到Looper.prepare()来创建Looper实例。
    剩下的源码还有:
public final class Looper {
    final MessageQueue mQueue;
    final Thread mThread;
    
    private Looper(boolean quitAllowed) {
        mQueue = new MessageQueue(quitAllowed); //1:在此创建MessageQueue
        mThread = Thread.currentThread();
    }
    
    public void quit() {
        mQueue.quit(false);
    }

    public void quitSafely() {
        mQueue.quit(true);
    }
    
    public static void loop() { //2
        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 (;;) {
            //3;从MessageQueue 中取消息
            Message msg = queue.next(); // might block
            if (msg == null) {
                // No message indicates that the message queue is quitting.
                return;
            }
	    //4:通过 Handler 分发消息
            msg.target.dispatchMessage(msg);
            //5:回收消息
            msg.recycleUnchecked();
        }
    }
}

从注释1可以知道,MessageQueue是在Looper构建函数里生成的。这个知识点要记一下。

关键是看看注释2,这是整个Handler最重要的源码了,前面所有的源码都最终服务与loop()方法!从源码就能知道该方法中一直在死循环做三件事:

  1. 调用next方法从MessageQueue里获取Message;
  2. 通过dispatchMessage方法将Message分配到目标Handler;
  3. 通过recycleUnchecked方法回收Message;

其中next()函数待会分析MessageQueue一并分析,现在看看Handler.dispatchMessage 做了什么事:

public void dispatchMessage(Message msg) {
    if (msg.callback != null) {
        //通过 handler.pos 形式传入的 Runnable,对应2.2.1小节
        handleCallback(msg); //1
    } else {
        if (mCallback != null) {
            //以 Handler(Handler.Callback) 写法
            if (mCallback.handleMessage(msg)) { //2
                return;
            }
        }
        //以 Handler(){} 写法
        handleMessage(msg); //3
    }
}

private static void handleCallback(Message message) {
    message.callback.run(); //4
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值