Android消息机制

  • 请介绍下Handler消息机制 ⭐⭐⭐⭐⭐

  • 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会发送出一个一个消息,同时系统会根据每一个不同的消息进行不同的处理流程。具体如何实现,直接上图。

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

  • Handler:主要作用是发送信息以及处理信息(为何发送还自己处理?),其中发送的信息叫作Message,可以传递数据哦;

  • MessageQueue:消息队列,由一个一个Message汇成,遵循先进先出规则,由Looper进行管理;

  • Looper:从MessageQueue里读取消息,并按消息分发机制分配到目标Handler进行消息处理。

1.2 Handler的使用和代码实例

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

  1. Handler通过sendMessage()发送消息Message,每个Message都在MessageQueue里排队。

  1. Looper通过loop()从MessageQueue里读取Message,并按消息分发机制分配到目标Handler进行消息处理。

  1. 目标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;

  1. 通过dispatchMessage方法将Message分配到目标Handler;

  1. 通过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
}

可以看出dispatchMessage()就是按照既定的优先级策略,决定Message由谁去处理,我们优先级由高到低介绍:

  1. handleCallback(msg):对应注释1,Message里自带的callback优先级最高,实际是调用注释4,最终调用Runnable重写的run(),对应案例可以看看2.2.1小节,getPostMessage(Runnable r)方法。

  1. mCallback.handleMessage(msg):也就是Handler.Callback 写法,可以看出这个方法是有返回值的,如果返回true则注释3不会运行到;代码案例见下:

//创建mCallback
private final Handler.Callback mCallback = new Handler.Callback() {
    @Override
    public boolean handleMessage(Message msg) {switch (msg.what) {
        case MSG_DELETE_INFORMATION: {
         //do something
        }
    }
}
//创建mHandler,并传入mCallback
mHandler = new Handler(this.getMainLooper(), mCallback);
//使用
mHandler.sendEmptyMessageDelayed(MSG_DELETE_INFORMATION, 1000);
  1. handleMessage(msg):重写handlerMessage()方法,优先级最低,代码案例见下:

public class BaseHandler extends Handler {
   
    @Override
    public void handleMessage(Message msg) {
        //do something
    }
}

2.5 MessageQueue源码分析

恭喜你看到这里,这是最后一个源码分析了。 经过上面的分析,我们还遗留了两个关于MessageQueue的函数没有分析,分别是enqueueMessage()和next()。

2.5.1 存放信息的方法:enqueueMessage()

回到2.2.2小节Handler发送消息的函数最后调用了queue.enqueueMessage()将要发送的Message发送到MessageQueue里做处理。

boolean enqueueMessage(Message msg, long when) {
    ...
    synchronized (this) {
        if (mQuitting) {  //正在退出时,回收msg,加入到消息池
            msg.recycle();
            return false;
        }
        msg.markInUse();
        msg.when = when;
        Message p = mMessages;
        boolean needWake;
        if (p == null || when == 0 || when < p.when) {
            //p为null(代表MessageQueue没有消息)或者msg的触发时间是队列中最早的, 则进入该该分支
            msg.next = p;
            mMessages = msg;
            needWake = mBlocked; 
        } else {
            //将消息按时间顺序插入到MessageQueue。一般地,不需要唤醒事件队列,除非
            //消息队头存在barrier,并且此时Message是队列中最早的异步消息。
            needWake = mBlocked && p.target == null && msg.isAsynchronous();
            Message prev;
            //遍历链表,找到合适位置插入新的消息
            for (;;) {
                prev = p;
                p = p.next;
                if (p == null || when < p.when) { //2
                    break;
                }
                if (needWake && p.isAsynchronous()) {
                    needWake = false;
                }
            }
            msg.next = p;
            prev.next = msg;
        }
        if (needWake) {
            nativeWake(mPtr);
        }
    }
    return true;
}

如果是第一个触发的信息,则作为对头的消息,否则在注释2,根据when(相对时间)的大小排序找到合适的插入位置。到此就完成了Handler通过调用post或者send方法将一个Message发送到MessageQueue并放在合适的链表位置的逻辑。那么该如何取出来呢?

2.5.2 获取消息的方法:next()

Message next() {
    int pendingIdleHandlerCount = -1; // 循环迭代的首次为-1
    int nextPollTimeoutMillis = 0; //代表下一个消息到来前,还需要等待的时长
    for (;;) {
        if (nextPollTimeoutMillis != 0) {
            Binder.flushPendingCommands();
        }

        //1:阻塞操作,当等待nextPollTimeoutMillis时长,或者消息队列被唤醒,都会返回
        nativePollOnce(ptr, nextPollTimeoutMillis);
        
        synchronized (this) {
            // 开始尝试获取信息,获取到就返回信息
            final long now = SystemClock.uptimeMillis();
            Message prevMsg = null;
            //取出头节点 Message
            Message msg = mMessages;

            //2:当消息Handler为空时,在队列中找到下一个异步消息。
            if (msg != null && msg.target == null) {
                do {
                    prevMsg = msg;
                    msg = msg.next;
                } while (msg != null && !msg.isAsynchronous());
            }

            //3:如果消息不为空,根据Message的相对时间判断(when变量)立即取出还是延迟取出
            if (msg != null) {
                if (now < msg.when) {
                    // 下一个消息还没有准备好。设置一个下一轮的等待时间nextPollTimeoutMillis
                    nextPollTimeoutMillis = (int) Math.min(msg.when - now, Integer.MAX_VALUE);
                } else {
                    // 消息准备好了则成功从MessageQueue里获取一个Message并返回
                    mBlocked = false;
                    if (prevMsg != null) {
                        prevMsg.next = msg.next;
                    } else {
                        mMessages = msg.next;
                    }
                    msg.next = null;
                    msg.markInUse();
                    return msg;
                }
            } else {
                // No more messages.(没有消息,-1代表一直等待)
                nextPollTimeoutMillis = -1;
            }
    }
}

获取一个Message的步骤如下:

  1. 注释1是阻塞操作,nextPollTimeoutMillis代表下一个消息到来前,还需要等待的时长,当为-1时代表一直等待。当等待时间到了或者消息队列被唤醒,则开始从队列里获取信息;

  1. 注释2,如果获取的信息是空的,则找下一个节点;

  1. 注释3,获取到非空的信息后,判断是否立刻取出来还是等待一段时间后取出,但最后都会成功返回一个Message;

  1. 如果队列里没有信息了,则在for循环里又回到了注释1进行阻塞等待。

也就是说next()方法根据消息出发的相对时间,返回下一条需要执行的消息,队列中消息为空时,则会进行阻塞操作。

3、一问一答:常见问题汇总

经过上面的学习,到了最激动人心的时刻了,揭秘下Handler常见的问题都如何回答。一开始觉得不知道如何回答的问题,你现在一定能轻松理解

3.1 Handler 引起的内存泄露原因以及最佳解决方案

因为Handler一般是作为Activity的内部类,可以发送延迟执行的消息,如果在延迟阶段,我们把Activity关掉,此时因为该Activity还被Handler这个内部类所持有,导致Activity无法被回收,没有真正退出并释放相关资源,因此就造成内存泄漏。

工程上常用的方法是将 Handler 定义成静态的内部类,在内部持有 Activity 的弱引用,并在Acitivity的onDestroy()中调用handler.removeCallbacksAndMessages(null)及时移除所有消息。如果和面试官说了这两个方法,那你就100分过关了,但更进一步是建议将Handler抽离出来作为BaseHandler,然后每个Activity需要用到Handler的时候,就去继承BaseHandler。最佳解决方案具体代码:

// 这个是BaseHandler
public abstract class BaseHandler<T> extends Handler {
    private final WeakReference<T> mWeakReference; //弱引用

    protected BaseHandler(T t) {
        mWeakReference = new WeakReference<T>(t);
    }

    protected abstract void handleMessage(T t, Message msg);

    @Override
    public void handleMessage(Message msg) {
        super.handleMessage(msg);
        if (mWeakReference == null) {
            return;
        }

        T t = mWeakReference.get();
        if (t != null) {
            handleMessage(t, msg);
        }
    }
}

//然后在某个Activity中使用
 private static class H extends BaseHandler<XuruiActivity> { //静态的内部类哦
       
        public H(XuruiActivity activity) {
            super(activity);
        }

        @Override
        protected void handleMessage(XuruiActivity activity, Message msg) {
            //do something
        }
    }
//同时Activity的onDestroy函数取消掉所有消息
@Override
protected void onDestroy() {
    mMyHandler.removeCallbacksAndMessages(null);
    super.onDestroy();
}

3.2 为什么我们能在主线程直接使用 Handler,而不需要创建 Looper ?

详情对应2.1小节,ActivityThread是主线程操作的管理者,在 ActivityThread.main() 方法中调用了 Looper.prepareMainLooper() ,该方法调用prepare()创建Looper。因此主线程不是不需要创建Looper,而是系统帮我们做了。

3.3 Handler、Thread和HandlerThread的差别

又是这种考区别的题目,不过还算是比较常见的三个知识点:

  1. Handler:本文所学的知识,是Android的一种异步消息机制,负责发送和处理消息,可实现子线程和主线程的消息通讯;

  1. Thread:Java的一个多线程类,是Java进程中最小执行运算单位,用于给子类继承,创建线程/

  1. HandlerThread:从名字看就知道是由前面两者结合起来的。可以理解为“一个继承自Thread的Handler类”,因此本质上和父类一样是Thread,但其内部直接实现了Looper,我们可以直接在HandlerThread里面直接使用Handler消息机制。减少了手动调用Looper.prepare()和Looper.loop()这些方法。

3.4 子线程中怎么使用 Handler?

这个题目就可以结合上面两个题目来拓展理解了。子线程中使用 Handler 需要先执行两个操作:Looper.prepare() 和 Looper.loop(),看到这里你应该要记得这两个函数执行顺序是不能变的哦。同时可以直接使用HandlerThread类即可。

3.5 为什么在子线程中创建 Handler 会抛异常?

不能在还没有调用 Looper.prepare() 方法的线程中创建 Handler。 因为抛出异常的地方,在Handler的构建函数,判断 mLooper 对象为null的时候, 会抛出异常

3.6 Handler 里藏着的 Callback 能干什么?

详情对应2.4小节,当从消息队列获取到信息后,需要分配给对应的Handler去处理,总共有3种优先级。

  1. handleCallback(msg):Message里自带的callback优先级最高;对应Handler的post方法;

  1. mCallback.handleMessage(msg):也就是Handler.Callback 写法;

  1. handleMessage(msg):重写handlerMessage()方法,优先级最低;

而Handler.Callback处于第二优先级,当一条消息被 Callback 处理并返回true,那么 Handler 的 handleMessage(msg) 方法就不会被调用了;但如果 Callback 处理后返回false,那么这个消息就先后被Handler.Callback和handleMessage(msg)都处理过。

3.7 Handler 的 send 和 post 的区别?

基于上道题继续展开,post方法,它会把传入的 Runnable 参数赋值给 Message 的 callback 成员变量。当 Handler 进行分发消息时,msg.callback 会最优先执行。

  • post是属于sendMessage的一种赋值callback的特例

  • post和sendMessage本质上没有区别,两种都会涉及到内存泄露的问题

  • post方式配合lambda表达式写法更精简

3.8 创建 Message 实例的最佳方式

详情对应2.3小节,为了节省开销,Android 给 Message 设计了回收机制,所以我们在使用的时候尽量复用 Message ,减少内存消耗:

通过 Message 的静态方法 Message.obtain(); 通过 Handler 的公有方法 handler.obtainMessage()。

3.9 Message 的插入以及回收是如何进行的,如何实例化一个 Message 呢?

插入对应2.5.1小节注释2,Message 往 MessageQueue 插入消息时,会根据 when 字段(相对时间)来判断插入的顺序.

消息回收对应2.4小节loop()函数注释5,在消息执行完成之后,会进行回收消息,回收消息可见2.3小节recycleUnchecked()函数,只是 Message 的成员变量设置为0或者null;

实例化 Message 的时候,也是件2.3小节,本文建议多次了,尽量使用 Message.obtain 方法,这是从缓存消息池链表里直接获取的实例,可以避免 Message 的重复创建。

3.10 妙用Looper机制,或者你知道Handler机制的其他用途吗?

  • 将 Runnable post 到主线程执行;

  • 利用 Looper 判断当前线程是否是主线程;

public boolean isMainThread() {
    return Looper.getMainLooper() == Looper.myLooper();
}

3.11 Looper.loop()死循环一直运行是不是特别消耗CPU资源呢?不会造成应用卡死吗?

详情对应2.4和2.5小节。这还涉及linux多进程通讯方式:Pipe管道通讯。Android应用程序的主线程在进入消息循环过程前,会在内部创建一个Linux管道。首先在loop()方法中,调用queue的next()方法获取下一个消息。具体看2.5.2小节,next()源码分析说过,MessageQueue没有消息时,便阻塞在nativePollOnce()方法里,此时主线程会释放CPU资源进入休眠状态,因此并不特别消耗CPU资源。

直到等待时长到了或者有新的消息时,通过往pipe管道写端写入数据来唤醒主线程工作。这里采用的epoll机制是一种IO多路复用机制,可以同时监视多个描述符。当一个描述符号准备好(读或写)时,立即通知相应的程序进行读或写操作,其实质是同步 I/O,即读写是阻塞的。其实主线程大多数时候都是处于这种休眠状态,并不会消耗大量CPU资源,更不会造成应用卡死。

3.12 MessageQueue 中如何等待消息?为何不使用 Java 中的 wait/notify 来实现阻塞等待呢?

直接回答在 MessageQueue 的 nativePollOnce 函数阻塞,直到等待时长到了或者有新的消息时才重新唤醒MessageQueue。其实在 Android 2.2 及其以前,确实是使用wait/notify来实现阻塞和唤醒,但是现在MessageQueue源码涉及很多native的方法,因此Java层的wait/notify自然不过用了,而Pipe管道通讯是很底层的linux跨进程通讯机制,满足native层开发需求。

3.13 你知道延时消息的原理吗?

首先是信息插入:会根据when属性(需要处理消息的相对时间)进行排序,越早的时间的Message插在链表的越前面;

在取消息处理时,如果时间还没到,就休眠到指定时间;如果当前时间已经到了,就返回这个消息交给 Handler 去分发,这样就实现处理延时消息了。

3.14 handler postDelay这个延迟是怎么实现的?

3.15 如何保证在msg.postDelay情况下保证消息次序?

详情对应2.5.1小节,和上一题有所联系。handler.postDelay不是延迟一段时间再把Message放到MessageQueue中,而是直接进入MessageQueue,根据when变量(相对时间)的大小排序在消息池的链表里找到合适的插入位置,如此也保证了消息的次序的准确性。也就是本质上以MessageQueue的时间顺序排列和唤醒的方式结合实现的。

3.16 更新UI的方式有哪些

这个题目放到这一节确实比较靠前,但因为本节介绍了其中的两个。所以也提一下。

  • Activity.runOnUiThread(Runnable)

  • View.post(Runnable),View.postDelay(Runnable, long)

  • Handler

  • AsyncTask

  • Rxjava

  • LiveData

3.17 线程、Handler、Looper、MessageQueue 的关系?

这里还是有必要说明一下,一个线程对应一个 Looper (可见2.1小节prepare()函数注释1的判断),同时对应一个 MessageQueue,对应多个 Handler。

3.18 多个线程给 MessageQueue 发消息,如何保证线程安全?

见2.5.1 enqueueMessage()在插入Message的时候使用synchronized机制加锁。

3.19 View.post 和 Handler.post 的区别?

//TODO:

3.20 你知道IdleHandler吗?

看看next()源码:

Message next() {
    ···
    for (;;) {
        //1:阻塞操作,当等待nextPollTimeoutMillis时长,或者消息队列被唤醒,都会返回 
        nativePollOnce(ptr, nextPollTimeoutMillis);
        ···
        synchronized (this) {
           //获取消息
            ···
        }

        // 此时没有信息需要处理就跑到这里
        for (int i = 0; i < pendingIdleHandlerCount; i++) {
            final IdleHandler idler = mPendingIdleHandlers[i];
            mPendingIdleHandlers[i] = null; // release the reference to the handler

            boolean keep = false;
            try {
                keep = idler.queueIdle(); //1
            } catch (Throwable t) {
                Log.wtf(TAG, "IdleHandler threw exception", t);
            }

            if (!keep) {
                synchronized (this) {
                    mIdleHandlers.remove(idler);
                }
            }
        }
        ...
    }
}

IdleHandler 是通过 MessageQueue.addIdleHandler 来添加到 MessageQueue 的,前面提到当 MessageQueue.next 当前没有需要处理的消息时就会进入休眠,而在进入休眠之前呢,会执行注释1,此时如果返回true,则调用该方法后继续保留,下次队列又空闲的时候继续调用。如果返回false,就会在注释2将当前的idler删除。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值