参考文献:

  • Android异步消息处理机制源码剖析
  • Handler全家桶之 —— Handler 源码解析
  • 你真的懂Handler吗?Handler问答
  • android的消息处理机制(图+源码分析)——Looper,Handler,Message

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信号量加锁。

Android异步消息处理机制(源码分析+面试题)_第1张图片

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的结点,插入到该结点的前面

Android异步消息处理机制(源码分析+面试题)_第2张图片

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
//主线程中不需要自己创建Looperpublic 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 主要属性

//是否发现潜在的内存泄漏,默认为falseprivate static final boolean FIND_POTENTIAL_LEAKS = false;private static final String TAG = "Handler";//静态全局变量,主线程的Handlerprivate 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 四要素之间的关系

Android异步消息处理机制(源码分析+面试题)_第3张图片

  • 一个线程中可创建多个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        }    });}

Android异步消息处理机制(源码分析+面试题)_第4张图片

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发送消息、处理消息的流程)?(高频题也是本章核心和概要,很重要)

Android异步消息处理机制(源码分析+面试题)_第5张图片

  • 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:93private 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:203public static @Nullable Looper myLooper() {    return sThreadLocal.get();}

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

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

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

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

//方式1Looper.myLooper() == Looper.getMainLooper();//方式2Looper.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为什么不加锁:逻辑复杂、效率低;

更多相关文章

  1. Android -- SharedPreferences保存基本数据、序列化对象、List数
  2. android 线程中的ui问题 Handler的基本使用 关于获取动态时间在u
  3. android UI线程向子线程发送Message
  4. Android webview与js交换JSON对象数据示例
  5. 探究Android异步消息的处理之Handler详解
  6. Android的子线程能更新UI吗?
  7. android 线程大集合
  8. [原]采用MQTT协议实现Android消息推送
  9. Android中Intent传递对象的两种方法(Serializable,Parcelable)

随机推荐

  1. mysql从入门到优化(3)事务的基本操作
  2. 可重入读写锁ReentrantReadWriteLock的使
  3. SVG基础知识
  4. 大白话讲述分布式系统中的CAP理论
  5. 一个古老而又经典的算法-汉诺塔问题
  6. mysql从入门到优化(1)基本操作上
  7. 学习分布式不会BASE理论?看这篇文章保证能
  8. 一个面试必问的java基础知识点:java中的异
  9. java小白到架构师学习路线【3.0版】
  10. 面试官:你了解java中的四种引用嘛?