Android | 异步消息处理机制(源码分析+面试题)

参考文献:

1 概述

主线程不能执行耗时操作,因为会阻塞,在子线程里进行耗时操作;子线程不能更新UI,用handler发送一个更新UI的消息,handler分发消息,处理消息

子线程为何不能访问UI?

  • 源码角度:当访问UI时,ViewRootImpl会调用checkThread()方法检查当前线程是哪个线程,如果不是UI线程会抛出异常;
  • 线程安全角度:访问UI不是线程安全的;
    • 访问UI为什么不加锁:逻辑复杂、效率低;

Handler作用:

  • 线程间通信(例如,子线程通知主线程更新UI);
  • 执行计划任务;

 下面源码分析基于Android 8.0;

2 Message 消息

 Message消息,是多线程间通信的实体,是Handler发送和处理的对象。Message对象实现了Parcelable接口,说明Message对象支持序列化/反序列化操作。

2.1 属性

    //msg ID
    public int what;
    //存储int类型的数据域
    public int arg1;
    //存储int类型的数据域
    public int arg2;
    //存储Object类型数据域
    public Object obj;
    //存储Bundle类型数据域
    /*package*/ Bundle data;

    /*package*/ static final int FLAG_IN_USE = 1 << 0;
    //消息标识,当消息对象进入消息队列或回收时设置为FLAG_IN_USE,msg.obtain时设置为0
    /*package*/ int flags;
    
    //处理消息的时间
    /*package*/ long when;
    //发送和处理消息的Handler
    /*package*/ Handler target;
    //post的Runnable
    /*package*/ Runnable callback;

    // 链式结构,指向下一个Message对象,用于维护链表结构的消息池(消息队列)
    /*package*/ Message next;
    
    //信号量,消息池的加锁对象
    private static final Object sPoolSync = new Object();
    //消息池的表头,由它维护了一个链式消息池,当消息被回收的时候,会加入到这个消息池中
    private static Message sPool;
    //消息池大小
    private static int sPoolSize = 0;
    //消息池最大容量50,消息队列的最大容量是50
    private static final int MAX_POOL_SIZE = 50;
  • Message可传输int , Object ,Bundle类型的数据
  • 如果你的message只需要携带简单的int,请优先使用Message.arg1Message.arg2来传递信息,这比用Bundle更省内存;
  • 擅用message.what来标识信息,以便用不同方式处理message;
  • Message维护了一个全局的消息池(消息队列),消息队列最大容量是50;消息被回收后,会放入到消息池中,并将flag字段设置为FLAG_IN_USE;

2.2 静态obtain()方法

    public static Message obtain() {
        synchronized (sPoolSync) {//对消息池加锁
            if (sPool != null) {
                Message m = sPool;
                sPool = m.next;
                m.next = null;
                m.flags = 0; // flags设为0
                sPoolSize--;//从链表删除
                return m;
            }
        }
        return new Message();//若消息池为空,直接new
    }

obtain方法用于获取一个消息对象,如果当前消息池为空,直接new,否则从消息池头部取一个消息对象进行复用;
obtain方法还有好几个重载方法,但最终都会调用该该无参方法。

2.3 recycle()方法

//可手动回收消息
public void recycle() {
        if (isInUse()) {
            if (gCheckRecycle) {
                throw new IllegalStateException("This message cannot be recycled because it "
                        + "is still in use.");
            }
            return;
        }
        recycleUnchecked();
    }
//真正回收Message的方法,looper.loop()在从消息队列取出并处理消息后调用这个方法
    void recycleUnchecked() {
        flags = FLAG_IN_USE; //修改标记??
        //为了无差别(handler发送的所有消息)复用消息对象,清空所有域
        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对象被处理或从消息队列移除后,可以手动调用recycle()方法回收消息对象;当然recycleUnchecked()方法才是真正回收消息的方法,looper.loop()在从消息队列取出并处理消息后调用这个方法进行消息回收;这个方法首先会将flag标记为FLAG_IN_USE,并把清空所有属性;并在消息池没有达到最大限定值的情况下,把这个对象插入消息池的表头。同样,在操作消息池的时候需要先对sPoolSync信号量加锁。

回收消息放入消息池

3 MessageQueue消息队列

MessageQueue是一个常量类,不允许被继承;
 消息队列用来存放Handler发送过来的消息,内部通过单链表的数据结构来维护消息列表,等待Looper的抽取。

3.1 消息出队next()

    Message next() {
                //...
        int pendingIdleHandlerCount = -1; // -1 only during first iteration
        int nextPollTimeoutMillis = 0;
        //死循环从队列取Message,直到返回一个Message,或者MessageQueue退出
        for (;;) {
            //...
            synchronized (this) {//消息队列加锁
                // Try to retrieve the next message.  Return if found.
                final long now = SystemClock.uptimeMillis();
                Message prevMsg = null;
                Message msg = mMessages;
                //取队头消息,若该消息不为空且者是屏障消息(target为空),则继续遍历,直到取到一个异步消息为止
                //屏障消息:target为空时是屏障消息;用于区分同步消息和异步消息;如果设置了屏障消息,只执行异步消息,不执行同步消息,直到移除了屏障;如果没设置屏障消息,同步消息和异步消息都执行
                if (msg != null && msg.target == null) {
                    // Stalled by a barrier.  Find the next asynchronous message in the queue.
                    do {
                        prevMsg = msg;
                        msg = msg.next;
                    } while (msg != null && !msg.isAsynchronous());
                }
                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 {
                        // Got a message.
                        mBlocked = false;
                        if (prevMsg != null) {
                            //preMsg不空,说明此时队列头结点是一个target为空的屏障消息,同时msg此时是异步消息。
                            prevMsg.next = msg.next;//直接从链表取下该消息
                        } else {//此时msg是队列头结点,直接删除队头即可
                            mMessages = msg.next;
                        }
                        msg.next = null;//断开next链接
                        if (DEBUG) Log.v(TAG, "Returning message: " + msg);
                        msg.markInUse();//修改标记
                        return msg;//取道非空消息退出
                    }
                } else {//队列为空,next方法阻塞,继续循环,等待新消息到来
                    // No more messages.
                    nextPollTimeoutMillis = -1;
                }
                //若消息队列已退出,返回true退出死循环
                if (mQuitting) {
                    dispose();
                    return null; //返回null后Looper.loop()方法也会结束循环
                }
                                //...
                if (pendingIdleHandlerCount <= 0) {
                    // No idle handlers to run.  Loop and wait some more.
                    mBlocked = true;//当队列为空时,next()方法会阻塞,继续循环,直到有新消息到达队列
                    continue;
                }
                                //...
        }
    }
  • next()方法用于将队列头部消息出队并返回
  • 该方法内部有个死循环,如果消息队列中没消息,next方法会阻塞,继续循环,直到取道新消息;如果消息队列中有消息,先判断执行时间是否到了,如果时间没到则等待,继续循环如果时间到了就将消息出队返回
  • 在循环过程中会对消息队列加锁,所以该方法是线程安全的;

3.2 消息入队enqueueMessage()

    boolean enqueueMessage(Message msg, long when) {
        if (msg.target == null) {//入队的消息的target,必须不为空,否则会抛异常
            throw new IllegalArgumentException("Message must have a target.");
        }
        if (msg.isInUse()) {//消息在回收之前,不可以被重复使用
            throw new IllegalStateException(msg + " This message is already in use.");
        }
        synchronized (this) {
            if (mQuitting) {//如果消息队列在退出状态 ,则直接回收消息,返回false
                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;
            }
            //把消息标记为在使用状态,设置when
            msg.markInUse();
            msg.when = when;
            Message p = mMessages;//此时p是链表头部
            boolean needWake;
            if (p == null || when == 0 || when < p.when) {
                //如果队列为空或者when等于0,或者when小于队头Message的when,则直接把消息插入队头
                msg.next = p;
                mMessages = msg;
                needWake = mBlocked;
            } else {
                needWake = mBlocked && p.target == null && msg.isAsynchronous();
                Message prev;
                for (;;) {
                    prev = p;//prev是p的前驱节点,依次遍历
                    p = p.next;
                    if (p == null || when < p.when) {
                        break;//当p已经到队尾或者找到一个节点msg.when < p.when时退出循环
                    }
                    if (needWake && p.isAsynchronous()) {
                        needWake = false;
                    }
                }
                //链表插入操作,把msg插入到p节点前边,并把p的前驱节点的next改为msg
                msg.next = p; // invariant: p == prev.next
                prev.next = msg;
            }
            //...
        }
        return true;//插入成功,返回true
    }
  • 该方法用于将消息入队,其实就是单链表的插入操作;
  • 该方法对链表队列操作时,依然是进行了加锁同步,所以是线程安全的;
  • 队列是一个(消息执行时间)when升序链表,所以插入也必须找到合适的节点进行插入;如果待插入Message不设置when或when=0,直接插入队列头部;否则遍历队列结点,直到找到第一个大于when的结点,插入到该结点的前面
消息入队

4 Looper消息泵

 通过Looper.loop()不断地从MessageQueue中抽取Message,将消息分发给目标处理者(Handler);

4.1 主要属性和构造器

    //线程本地变量,每个线程有一个独立的Looper对象,不存在线程安全问题
    //如果不调用prepare()方法,sThreadLocal.get()返回null
    static final ThreadLocal<Looper> sThreadLocal = new ThreadLocal<Looper>();
    private static Looper sMainLooper;  // 主线程的Looper,由Looper.class维护
    final MessageQueue mQueue;//looper的MessageQueue
    final Thread mThread;//(创建)Looper线程
        //私有构造器,不允许外部调用
        private Looper(boolean quitAllowed) {
        mQueue = new MessageQueue(quitAllowed);
        mThread = Thread.currentThread();
        }
  • Looper的构造器是私有的,不能在Looper类外部new Looper,所以在Looper类外部必须调用prepare()方法来创建一个Looper对象
  • Looper内部有一个MessageQueue属性

4.2 创建Looper

Looper.prepare()创建Looper,而不是new

//公有方法,开发者只能调用这个方法为当前线程创建Looper,允许退出
public static void prepare() {
    prepare(true);
}
//私有方法,开发者无法调用,同一个线程只允许调用一次prepare(),否则会抛出异常
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));//将Looper对象设置为线程本地变量
}
//主线程的Looper初始化,虽然是公有方法,我们无法调用,
//因为系统启动的时候已经调用过了,如果再次调用,会抛异常
public static void prepareMainLooper() {
    prepare(false);
    synchronized(Looper.class) {
        if (sMainLooper != null) {
            throw new IllegalStateException("The main Looper has already been prepared.");
        }
        //把得到的主线程Looper赋值给sMainLooper 
        sMainLooper = myLooper();
    }
}
//获取当前线程的Looper对象
public static@Nullable Looper myLooper() {
    return sThreadLocal.get();//获取线程本地变量
}
  • 调用Looper.prepare()为当前线程创建Looper对象,并将Looper对象设置为线程本地变量
  • 调用Looper.myLooper()方法来获取当前线程的Looper对象;
  • 主线程的Looper允许MessageQueue退出,而其他线程不允许;
  • 一个线程只能调用一次Looper.prepare()方法,否则会抛出异常,所以在prepare()创建Looper对象之前,应该先调用Looper.myLooper()方法判断是否为空;同时也说明了一个线程只有一个Looper对象
  • 主线程的Looper在ActivityThread中的main()方法中创建的,所以主线程不需要手动创建Looper
//主线程中不需要自己创建Looper
public static void main(String[] args) {
        //...
        Looper.prepareMainLooper();//为主线程创建Looper,该方法内部又调用 Looper.prepare()
        //...
        Looper.loop();//开启消息轮询
        //...
    }
  • 另外可以在任何地方调用Looper.getMainLooper();获取主线程的Looper;

 但是子线程就不一样了,子线程在创建Handler对象前必须手动调用Looper.prepare()方法创建Looper对象;

//子线程中创建Looper的标准写法
new Thread(new Runnable() {
    @Override
    public void run() {
        if(Looper.myLooper()==null){//保证一个线程只有一个Looper
            Looper.prepare();//创建Looper
        }
        Handler handler=new Handler();
        Looper.loop();//开启消息轮询
    }
}).start();

4.3 开启消息轮询loop()

 //代码省去打印等其他无关逻辑
public static void loop() {
    //获取当前线程的sThreadLocal变量,即Looper对象
    final Looper me = myLooper();
    //如果当前线程没有调用过prepare()方法,则me为null,抛出异常
    if (me == null) {
        throw new RuntimeException("No Looper; Looper.prepare() wasn't called on this thread.");
    }
    //从me里获得本线程的MessageQueue对象
    final MessageQueue queue = me.mQueue;
    Binder.clearCallingIdentity();
    final long ident = Binder.clearCallingIdentity();
    //开启消息轮询
    for (;;) {
        //从消息队列取消息
        Message msg = queue.next();  // 当消息队列为空且未退出时,next方法会阻塞
        if (msg == null) {
            //next返回null表明消息队列已退出
            return;//结束轮询,loop方法唯一出口
        }
        try {
            //target是Message的Handler,调用它的dispatchMessage()方法来分发消息
            msg.target.dispatchMessage(msg);
        } finally {
            if (traceTag != 0) {
                Trace.traceEnd(traceTag);
            }
        }
        final long newIdent = Binder.clearCallingIdentity();
        msg.recycleUnchecked();//当消息被处理完后,回收当前消息
    }
}
  • 通过调用Looper.loop()方法开启消息轮询;该方法会调用queue.next()方法从队列中取头部消息
  • 该方法会循环调用msg.target.dispatchMessage(msg);来进行消息分发,分发给消息的target,也就是消息对应的的Handler对象;
  • 当消息被处理完后,会调用msg.recycleUnchecked();回收消息,当前消息放入消息池,以便以后复用;
  • handler是在它关联的looper线程(创建Looper对象的线程)中处理消息的;

5 Handler 消息处理器

5.1 主要属性和构造器

5.1.1 主要属性

//是否发现潜在的内存泄漏,默认为false
private static final boolean FIND_POTENTIAL_LEAKS = false;
private static final String TAG = "Handler";
//静态全局变量,主线程的Handler
private static Handler MAIN_THREAD_HANDLER = null;
//绑定的Looper对象
final Looper mLooper;
//绑定的MessageQueue消息队列,通过looper获取
final MessageQueue mQueue;
//回调接口
final Callback mCallback;
//是否是异步的,如果是异步的,在发送消息的时候,
//会调用Message.setAsynchronous(true)把消息设为异步消息
final boolean mAsynchronous;
  • 由此可见一个Handler持有一个Looper类型的属性;即一个Handler对应一个唯一的Looper;而对应的MessageQueue消息队列,通过Looper属性获取;

5.1.2 构造器

public Handler(Callback callback, boolean async) {
        if (FIND_POTENTIAL_LEAKS) {
            //如果为true,如果Handler实现类是匿名类或内部类或非static类,会给出警告,告知开发者存在内存泄漏的风险
            final Class<? extends Handler> klass = getClass();
            if ((klass.isAnonymousClass() || klass.isMemberClass() || klass.isLocalClass()) &&
                    (klass.getModifiers() & Modifier.STATIC) == 0) {
                Log.w(TAG, "The following Handler class should be static or leaks might occur: " +
                    klass.getCanonicalName());
            }
        }
        //获取当前线程的线程本地变量,即Looper对象;
        mLooper = Looper.myLooper();
        if (mLooper == null) {//创建Handler对象前要调用Looper.prepare
            throw new RuntimeException(
                "Can't create handler inside thread that has not called Looper.prepare()");
        }
        //mQueue直接拿Looper里的MessageQueue类型的引用对象
        mQueue = mLooper.mQueue;
        mCallback = callback;
        mAsynchronous = async;
    }
    
   //该构造器可为当前Handler指定Looper对象,所以Handler对象和Looper对象不一定是在同一线程创建的
   public Handler(Looper looper, Callback callback, boolean async) {
        mLooper = looper;
        mQueue = looper.mQueue;
        mCallback = callback;
        mAsynchronous = async;
    }
    
    public Handler() {this(null, false);}
        
    public Handler(Callback callback) {this(callback, false);}
    
    public Handler(Looper looper) {this(looper, null, false);}
  • Handler对应的MessageQueue对象来自其关联的Looper对象;

  • 其他构造方法都会调用前面俩构造器中一个;早常用的构造器是Handler()Handler(Callback);

  • Handler(Looper looper)用于为当前Handler指定关联的Looper对象;

  • Handler关联的Looper对象既可以来自当前线程(创建Handler实例的线程)的本地变量,也可以在构造器里指定;前面一种情况,Handler和Looper是在同一线程里实例化的,后面一种情况不一定;

5.1.3obtainMessage

public final Message obtainMessage() {
    return Message.obtain(this);
}

 使用该方法获取handler当前处理的Message;Handler中有一系列obtanMessage()重载方法,最后调用的还是该方法;需要注意的是,在Message中,obtain()方法是静态方法,在Handler中,是非静态的,需要通过具体的Handler实例对象来获得,但是禁止子类进行覆写;

5.2 发送消息

 在Handler中,可以发送一个Runnable对象,也可以发送一个Message对象;通过sendMessage(Message)方式发送一个Message对象;通过post(Runnable)方式发送一个Runnable对象,这个Runnable对象最终也会被包装成一个Message对象发送;

5.2.1 post方式

//立即post一个Runnable对象到MessageQueue中,此时Runnable对象被包装成Message后入队(when == 当前系统时间,可能是队头,也可能不是队头,队列中已经有when值小于当前时间的Message)
public final boolean post(Runnable r) {
    return sendMessageDelayed(getPostMessage(r), 0);
}
//Runnable包装成Message后加入到MessageQueue中,但此时
//Message.when=uptimeMillis,uptimeMills是消息的执行时间,
public final boolean postAtTime(Runnable r, long uptimeMillis) {
    return sendMessageAtTime(getPostMessage(r), uptimeMillis);
}
//同上,只是又传入了token对象,存储在Message.obj中
public final boolean postAtTime(Runnable r, Object token, long uptimeMillis) {
    return sendMessageAtTime(getPostMessage(r, token), uptimeMillis);
}
//Runnable包装成Message后加入到MessageQueue中,
//但是Message.when = now + delayMillis,
//表示延迟delayMills后执行
public final boolean postDelayed(Runnable r, long delayMillis) {
    return sendMessageDelayed(getPostMessage(r), delayMillis);
}
//Runnable包装成Message后加入到MessageQueue中,此时when=0,所以一定是在MessageQueue的队头
public final boolean postAtFrontOfQueue(Runnable r) {
    return sendMessageAtFrontOfQueue(getPostMessage(r));
}
//把Runnable对象包装成Message对象,可见只是把Runnable对象赋值给了Message的callback域
private static Message getPostMessage(Runnable r) {
    Message m = Message.obtain();
    m.callback = r;
    return m;
}
//上述方法的重载方法,把token赋值给了Message的obj域,可以用这个方法进行传Object数据
private static Message getPostMessage(Runnable r, Object token) {
    Message m = Message.obtain();
    m.obj = token;
    m.callback = r;
    return m;
}

5.2.2 sendMessage方式

public final boolean sendMessage(Message msg) {
    return sendMessageDelayed(msg, 0);
}

public final boolean sendEmptyMessage(int what) {
    return sendEmptyMessageDelayed(what, 0);
}

public final boolean sendEmptyMessageDelayed(int what, long delayMillis) {
    Message msg = Message.obtain();
    msg.what = what;
    return sendMessageDelayed(msg, delayMillis);
}
//延迟发送,把当前时间加上延迟时间后调用了sendMessageAtTime()方法
public final boolean sendMessageDelayed(Message msg, long delayMillis) {
    if (delayMillis < 0) {
        delayMillis = 0;
    }
    return sendMessageAtTime(msg, SystemClock.uptimeMillis() + delayMillis);
}
//发送消息的方法,对queue判空后,调用enqueueMessage进行实际入队
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);
}
//实际对消息入队的方法,在该方法中,会把Message的target域进行赋值,
//如果mAsynchronous是true,则会调用setter方法把消息设置为异步消息,
//调用的入队方法其实是调用的MessageQueue的enqueueMessage方法
private boolean enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis) {
    msg.target = this;
    if (mAsynchronous) {
        msg.setAsynchronous(true);
    }
    return queue.enqueueMessage(msg, uptimeMillis);
}
//...
//省略其他方法,基本上跟post系列方法是一一对应的
  • 几个时间关系:when = now + delay,when 表示分发消息(dispatchMessage)的时间,now表示当前(相对系统启动)时间SystemClock.uptimeMillis(),delay表示延迟时间delayMillis;

post(runnable)方式和sendMessage(msg)方式发送消息的联系和区别:

  • 联系:调用链都是handler.sendMessageAtTime()->messageQueue.enqueueMessage()将Message发送到消息队列;
  • 区别:
    • 消息内容不同:sendMessage发送的消息侧重于传数据,而handleCallback侧重于传任务(Runnable);
    • 处理消息的方式不同:一般情况,sendMessage发送的消息最终会调用handler或callback的handleMessage方法来处理;而post(runnable) 发送的消息最终会调用handler.handleCallback方法来处理;

5.3 处理消息

        //消息分发
    public void dispatchMessage(Message msg) {
        if (msg.callback != null) {//post的Runnable参数
            handleCallback(msg);
        } else {
            if (mCallback != null) {//Handler(Callback)构造器,Handler无需派生子类
                if (mCallback.handleMessage(msg)) {//一般为true,若为false,还要执行handleMessage
                    return;
                }
            }
            handleMessage(msg);//优先级最低
        }
    }
    
    private static void handleCallback(Message message) {//处理消息方式1,优先级最高
        message.callback.run();//执行post的Runnable参数地run回调方法,因此Runnable run里可以有一些更新UI的操作
    }
    
    public interface Callback {//优先级次之
        public boolean handleMessage(Message msg);//处理消息方式2,在调用Handler(Callback)构造器实例化Handler时实现该方法
    }
    
   //Handler子类必须实现这个空方法来接收消息
    public void handleMessage(Message msg) {//处理消息方式3,优先级最低
    }
  • 有两类发送消息的方式: sendMessage(msg)方式和post(Runnable r)方式;
  • 处理消息方式有三种:handleCallback方式、callback.handleMessage方式、handler.handleMessage方式;并且优先级递减;

6 四要素之间的关系

6.1 四要素之间的关系

四要素ER图
  • 一个线程中可创建多个Handler对象;但是一个线程中只能创建一个Looper对象(因为一个线程中只能调用一次Looper.preapre(),否则会报异常);
  • 一个Looper对象可以对应多个线程,比如主线程的mainLooper,供主线程和所属子线程(Looper.getMainLooper())共同使用;
  • Looper类中有一个final MessageQueue mQueue属性;
  • Handler类中有一个属性final Looper mLooper属性,Handler关联的消息队列通过Looper获取;
  • 一个消息队列中有多个Message对象,不同消息的target可以不同,所以消息队列中的消息可以来自不同的Handler对象;
  • Message类有一个 Handler target属性,这是消息对象关联的Handler对象;

6.2 异步消息处理机制的原理(图非常重要)

 以下面应用场景为例:在主线程里实例化Looper和Handler;在子线程(工作线程)处理耗时任务,由于要将任务执行结果在UI上展示,需要更新UI;在子线程中创建一个更新UI的Message对象,并使用Handler对象的引用发送该消息;最后在主线程里处理消息,更新UI;下面是sample代码:

public class MainActivity extends AppCompatActivity {
    @BindView(R.id.textView) TextView textView;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);setContentView(R.layout.activity_main);
        ButterKnife.bind(this);
    }

    @OnClick(R.id.button)
    public void getData() {
        new Thread(new Runnable() {
            @Override
            public void run() {
                //执行耗时操作...
                Message msg=new Message();msg.what=7;msg.obj= "网络数据";
                //处理消息时回调handler.handleMessage
//                handler1.sendMessage(msg);
                //处理消息时回调callback.handleMessage
//                handler2.sendMessage(msg);
                //此处使用handler1,2,3 post消息都可以,但是不会执行handleMessage
                handler3.post(new Runnable() {
                    @Override
                    public void run() {
                        //不会开启新线程执行,handleCallback执行run里的代码,所以不会报错
                        textView.setText("耗时操作处理结果");
                    }
                });
            }
        }).start();
    }

    Handler handler3=new Handler();

    //匿名内部类向上转型Handler()方式,派生子类
    Handler handler1=new Handler(){
        @Override
        public void handleMessage(Message msg) {
            switch (msg.what){
                case 7:
                    textView.setText(msg.obj.toString());//更新UI
                    break;
            }
        }
    };
    
    //Handler(Callback)方式,不派生子类
    Handler handler2=new Handler(new Handler.Callback() {
        @Override
        public boolean handleMessage(Message msg) {
            textView.setText(msg.obj.toString());//更新UI
            return true;//修改为true
        }
    });
}
异步消息处理机制原理图

7 常见面试问题

(1) 为什么创建 Message 对象推荐使用 Message.obtain()获取而不是new方式?

 Handler 机制在 Android 系统中使用太频繁,为了提神效率,为Message设置了一个静态的消息池,当消息被处理完或移除后,会放入到消息池;下次需要使用Message时从消息池中取出消息进行复用

(2) 简述MessageQueue 如何入队和出队?

  • 消息入队:调用enquueMessage(msg,when);如果消息没设置when或者when是0,直接将消息放到队列头部;否则遍历队列链表,找到第一个大于当前消息when的消息结点,插入到该节点前面;最后会形成一个按when升序的单链表
  • 消息出队:调用next()方法,直接取出队列头部消息并返回;

(3) 发送消息两种主要方式 sendMessage(msg)方式和post(Runnable r)方式的区别?

post(runnable)方式和sendMessage(msg)方式发送消息的联系和区别:

  • 联系:调用链都是handler.sendMessageAtTime()->messageQueue.enqueueMessage()将Message发送到消息队列;
  • 区别:
    • 消息内容不同:sendMessage发送的消息侧重于传数据,而handleCallback侧重于传任务(Runnable);
    • 处理消息的方式不同:一般情况,sendMessage发送的消息最终会调用handler或callback的handleMessage方法来处理;而post(runnable) 发送的消息最终会调用handler.handleCallback方法来处理;

(4) 处理消息有哪几种方式,他们之间优先级?

  • 处理消息方式有三种:handler.handleCallback方式、callback.handleMessage方式、handler.handleMessage方式;并且优先级递减;

(5) Handler发送、处理消息有哪几种方式?

  • 结合有以下三种常见的 Handler发送、处理消息 的方式:
        //方式1:send+派生方式
        //发送消息sendMessage;构造器Handler();处理消息handler.handleMessage;
        //注意这种方式由于存在Handler子类内部类,可能存在内存泄漏的情况,需要处理这种情况
        handler1.sendMessage(msg);
        //匿名内部类向上转型Handler()方式,需要派生子类
    Handler handler1=new Handler(){
        @Override
        public void handleMessage(Message msg) {
            switch (msg.what){
                case 1:
                    textView.setText(msg.obj.toString());//更新UI
                    break;
            }
        }
    };
    
    //方式2:send+Callback方式
    //发送消息sendMessage;构造器Handler(Callback);处理消息callback.handleMessage;
    handler2.sendMessage(msg);
    //Handler(Callback)方式,不派生子类
    Handler handler2=new Handler(new Handler.Callback() {
        @Override
        public boolean handleMessage(Message msg) {
            textView.setText(msg.obj.toString());//更新UI
            return true;//修改为true
        }
    });
    
    //方式3:post(Runnable r)方式
    //发送消息post(Runnable r);构造器Handler();处理消息handleCallback;
    //此处使用handler1,2,3 post消息都可以,但是不会执行handleMessage
    handler3.post(new Runnable() {
        @Override
        public void run() {
      //不会开启新线程,handleCallback执行run里的代码,所以不会报错
                textView.setText("耗时操作处理结果");
        }
        });
    Handler handler3=new Handler();

(6) 异步消息处理机制的原理(Handler发送消息、处理消息的流程)?(高频题也是本章核心和概要,很重要)

异步消息处理机制原理图
  • Handler消息处理器 作用:发送消息(任务),处理消息;
  • Looper消息泵 作用:调用Looper.loop()方法进行消息轮询;
  • MessageQueue消息队列 作用:存放消息的场所,是一个按when值递增的单链表;queue.enqueue(msg,when)用于消息入队;queue.next()方法用于消息出队,取队首消息;

(7) post(Runnable r)方式是否会开启新线程?

  这种方式不会开启新线程;

  • Runnable对象会包装成Message对象,r作为Message对象的callback属性;
  • 然后调用handler.sendMessageDelay()->handler.sendMessageAtTime()->messageQueue.enqueueMessage()将Message对象发送到消息队列;
  • Looper在开启消息轮询后,到一定时间会从消息队列中取出该消息对象,交给对应的target(Handler对象)进行消息分发;
  • 然后调用handler.handleCallback()方法处理消息,在这个方法里Runnable任务会得到执行;

(8) 区分两个callback?区分两个handleMessage

 两个callback:

  • MessageRunnable callback属性:使用post(Runnable r)里的Runnable对象初始化;
  • Handlerfinal Callback mCallback属性:在Handler(Callback callabck)构造器进行初始化;

 两个handleMessage方法,对应处理消息的两种方式:

  • handler.handleMessage:send+派生方式调用该方法来处理消息;
  • callback.handleMessage:send+Callback方式调用该方法来处理消息;

(9) 为什么Handler会造成内存泄漏?如何解决?

(i) 内存泄漏的原因?
  • 一般造成内存泄漏的原因:长生命周期对象引用短生命周期对象
  • Handler造成Activity内存泄漏的原因:Handler生命周期比Activity长,非静态内部类默认持有外部类的引用,导致Activity对象无法回收(Activity对象先回收时,Handler对象可能还在处理消息,此时Handler对象还持有Activity对象的引用,导致Activity对象无法回收)。
(i) 解决办法?

 把Handler子类定义为静态(static)内部类;同时用WeakReference包装外部类的对象activity;

  • 为什么Handler子类要定义为静态(static)内部类?  
     因为静态内部类不持有外部类的引用,所以使用静态的Handler不会导致Activity内存泄露。
  • 为什么Handler子类定义为静态(static)内部类同时,还要用WeakReference包装外部类的对象activity ?
     因为我们需要访问外部类的非静态成员,可以通过强引用"activity. "访问,如果直接使用强引用访问,显然会导致activity泄露。
    MyHandler mHandler=new MyHandler(this);
    
    private static class  MyHandler extends Handler{
        //static和WeakReference是为了解决内存泄漏
        private WeakReference<MainActivity> weakReference;
        public MyHandler(MainActivity activity) {
           weakReference = new WeakReference<>(activity);
        }

        @Override
        public void handleMessage(Message msg) {
            switch (msg.what){
                case 1:
                MainActivity activity=weakReference.get();
                if(activity != null){//判空是为了避免空引用异常
                     activity.textView.setText(msg.obj.toString());
                }
                break;
            }
        }
    }

(10) 为何Looper.loop()死循环不会造成应用卡死?

Looper.loop()不会造成应用卡死,因为里面使用了Linux 的epoll机制

(11) 创建Handler前要注意什么?

 创建Handler对象前必须调用Looper.prepare()方法创建一个Looper对象;值得一体的是,子线程中必须手动调用Looper.prepare()方法,而主线程中可以不调用;因为主线程ActivityThread的main方法中默认调用了Looper.prepareMainLooper()方法,这个方法会调用Looper.prepare()方法创建一个主线程Looper对象;

(12) 异步消息处理机制是如何保证消息处理器的唯一性(即某条消息的发送者和处理者是同一Handler对象)?

 在Handler的enqueueMessage方法中会把自引用赋值给被发送的Message的target属性;而在Looper的loop方法中会调用msg.target.dispatchMessage(msg)来分发、处理消息;

        //Handler.java
    private boolean enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis) {
        msg.target = this;
                //...
    }
    //Looper.java
    public static void loop() {
            //...
            msg.target.dispatchMessage(msg);
            //...
    } 

(13) 子线程中是否可以创建Handler对象?

 子线程中可以创建Handler对象,但是子线程在创建Handler对象前必须手动调用Looper.prepare()方法创建Looper对象;

new Thread(new Runnable() {
    @Override
    public void run() {
        if(Looper.myLooper()==null){//保证一个线程只有一个Looper
            Looper.prepare();//创建Looper
        }
        Handler handler1=new Handler();
        Looper.loop();//开启消息轮询
        
        //主线程Looepr对象早已创建,并早已开启消息轮询
        Handler handler2=new Handler(Looper.prepareMainLooper());
    }
}).start();

ps:如果在主线程中使用子线程中创建的Handler对象的引用发送消息,最后消息是在子线程中处理的;这样就实现了主线程向子线程发送消息,而在本文6.2节中的sample代码中实现了子线程向主线程发送消息;所以两个线程可以通过Handler进行双向通信

(14) Handler 与 Looper 是如何关联的?

  • 对于有Looper参数的构造器Handler(looper):直接通过构造器参数设置关联的Looepr对象;
  • 对于无Looper参数的Handler构造器:无论是Handler()还是Handler(callback)都会调用下面的构造器,在这个构造其中会为当前Handler关联当前线程的Looper对象;
    public Handler(Callback callback, boolean async) {
                //...
        mLooper = Looper.myLooper();//获取当前线程的Looepr对象
                //...
    }
    public static @Nullable Looper myLooper() {
        return sThreadLocal.get();//获取当前线程的本地变量
    }

(15) Thread 与 Looper 是如何关联的?

 Looper 与 Thread 之间是通过 ThreadLocal 关联的,这个可以看 Looper.prepare(quitAllowed)方法:

// Looper.java:93
private static void prepare(boolean quitAllowed) {
        //...
    sThreadLocal.set(new Looper(quitAllowed));//设置当前线程本地变量
}

 Looper 类有一个 ThreadLocal 类型的 sThreadLocal静态属性,Looper通过它的 get 和 set 方法来赋值和取值;
 由于 ThreadLocal是与当前线程是绑定的,所以我们只要把 Looper 与 ThreadLocal 绑定了,那 Looper 和 Thread 也就关联上了;

(16) 如何在子线程中获取当前线程的 Looper?

Looper.myLooper();//获取当前线程的Looepr对象

 内部原理就是sThreadLocal.get()

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

(16) 如何在任意线程获取主线程的 Looper?

Looper.getMainLooper();//获取主线程的Looper对象

 这个在我们开发 API时特别有用,毕竟你不知道开发者在使用你的API时会在哪个线程初始化Looper,所以我们在创建 Handler 时每次都通过指定主线程的 Looper 的方式保证API正常运行。所以一般使用主线程Looper来进行异步消息处理;

(17) 如何判断当前线程是不是主线程?

//方式1
Looper.myLooper() == Looper.getMainLooper();
//方式2
Looper.getMainLooper().getThread() == Thread.currentThread();
//方式3:方式2简化版
Looper.getMainLooper().isCurrentThread();

(18) Looper.loop() 方法会退出吗?

 不会自动退出,但是我们可以手动调用 looper.quit()looper.quitSafely()方法退出消息轮询
 这两个方法都会调用MessageQueue#quit(boolean) 方法MessageQueue#mQuitting属性置为true,标记消息队列已退出;消息队列退出后,MessageQueue#next() 方法发现已经调用过 MessageQueue#quit(boolean) 时会 return null ;然后Looper.loop() 方法退出消息轮询;

 如果looper.quit()looper.quitSafely(),MessageQueue#quit(boolean)都不手动调用并且消息队列为空,消息队列不会退出;next()方法会一直死循环(有的说法称为next方法阻塞),loop()方法会在Message msg = queue.next();处阻塞等待新消息到达消息队列,继续消息轮询。所以建议当所有Message都被处理完之后手动调用looper.quit()looper.quitSafely()方法退出消息轮询,避免loop()方法一直阻塞等待

        //Looper.java#322
    public void quit() {//Looper退出
        mQueue.quit(false);
    }
    //Looper.java#338
    public void quitSafely() {
        mQueue.quit(true);
    }
    //Looper.java#137
    public static void loop() {//开启消息轮询
            //...
        for (;;) {
            Message msg = queue.next(); // 当消息队列为空且未退出时,next方法会阻塞
            if (msg == null) {
                //next返回null表明消息队列已退出
                return;//结束轮询,loop方法唯一出口
            }
                        //...
            msg.target.dispatchMessage(msg);
                //...
        }
    }
    
    //MessageQueue.java#416
    void quit(boolean safe) {
            //...
        mQuitting = true;
        //...
    }
    //MessageQueue.java#310
    Message next() {
        //...
        for (;;) {
             synchronized (this) {
                                //...
                Message msg = mMessages;
                                //...
                if (msg != null) {
                    if (now < msg.when) {
                        //...
                    }else{
                        //...
                        return msg;//取到非空消息退出
                    }
                }else{
                    // No more messages.
                    nextPollTimeoutMillis = -1;
                }
                //若消息队列已退出,返回true退出死循环
                if (mQuitting) {
                    dispose();
                    return null;
                }
        }

(19) MessageQueue#next()方法在消息队列为空时会阻塞,如何恢复?

 使用Handler的sendMessage、post 等一系列方法发送消息,这些发送消息的方法会调用MessageQueue#enqueueMessage将新消息入队,从而使得next()方法不再阻塞;

(20) IdleHandler作用和使用场景?

 把页面启动时的复杂逻辑交给IdleHandler去处理,这样可以让主线程Handler先处理完相关UI逻辑后再去处理复杂逻辑,可以减少页面启动白屏时间,从而优化页面启动

(21) 子线程为何不能访问UI?

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

推荐阅读更多精彩内容