从源码角度来读Handler

1,507 阅读5分钟

最近打算从源码角度来读一下Handler,MessageQueue,Message,Looper,这四个面试必考项。所以今天先从Handler开始。

一.Handler的作用

源码定义

There are two main uses for a Handler: 
- (1) to schedule messages and
  runnables to be executed as some point in the future; and 
- (2) to enqueue
 an action to be performed on a different thread than your own.
  1. 在未来的某个时刻调用某事件

  2. 线程之间互相通信

这就是Handler设计出来最主要的两种用途

二.Handler的构造方法

1.public Handler()
2.public Handler(Callback callback)
3.public Handler(Looper looper)
4.public Handler(Looper looper, Callback callback)
5.public Handler(boolean async)
6.public Handler(Callback callback, boolean async)
7.public Handler(Looper looper, Callback callback, boolean async)

从Handler源码看,Handler构造函数一共7个,不过仔细观察可发现这七个归根到底都是围绕着三个参数而来,即looper,callback,async。

Looper大家应该都知道,就是负责消息进行循环的,每个Handler都必定要对应一个独一无二的Looper。这样你Handler sendMessage以后,你的Message才能进入到对应的Looper去做循环,然后被调用。

Callback,估计大家有点陌生,说实话我之前也没怎么用过。先放上源码

  /**
     * Callback interface you can use when instantiating a Handler to avoid
     * having to implement your own subclass of Handler.
     */
    public interface Callback {
        /**
         * @param msg A {@link android.os.Message Message} object
         * @return True if no further handling is desired
         */
        public boolean handleMessage(Message msg);
    }

看源码就是一个简单的接口。

然后再仔细看该接口的调用会明白这是在Handler调用自身handleMessage方法之前做的一次调用,若Callback参数不为Null,则处理消息时会走Callback的handleMessage,而不会走Handler的handleMessage方法。相关源码在接下来的第四模块Handler处理消息的方法中放出。这个具体使用场景欢迎补充。

async,官方文档说明

If true, the handler calls {@link Message#setAsynchronous(boolean)} for each {@link Message} that is sent to it or {@link Runnable} that is posted to it.

然后查找哪里使用到了这个参数,发现在消息进入MessageQueue时使用。代码如下

 private boolean enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis) {
        msg.target = this;
        if (mAsynchronous) {
            msg.setAsynchronous(true);
        }
        return queue.enqueueMessage(msg, uptimeMillis);
    }

相当于所有通过这个Handler来发送消息的Message,若Handler的async为true,则这些Message会被标记为async为true,那么这个Message参数为true或false有什么作用么? 参考这篇文章Android消息处理机制(Handler、Looper、MessageQueue与Message) 以及源码可知,默认的消息该值都为false,若为true,则Message不再受同步障碍影响,即使设置了同步障碍,这些消息也能不间断的被执行,反过来默认的消息若被设了同步障碍,则这个Looper取Message过程则会被同步障碍中断,原因又是一个类似死循环。因为自己没用过,看源码,说在系统View.invalidate代码中有使用,接下来可研究相关使用示例。

(刚看了相关代码感觉挺有意思的下面补充一下)

首先看ViewRootImpl 中scheduleTraversals的代码,刷新开始的地方

 void scheduleTraversals() {
        if (!mTraversalScheduled) {
            mTraversalScheduled = true;
            mTraversalBarrier = mHandler.getLooper().getQueue().postSyncBarrier();
            mChoreographer.postCallback(
                    Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);
            if (!mUnbufferedInputDispatch) {
                scheduleConsumeBatchedInput();
            }
            notifyRendererOfFramePending();
            pokeDrawLockIfNeeded();
        }
    }

其中

mHandler.getLooper().getQueue().postSyncBarrier();

代码往MessageQueue中加入了一个同步屏障(说白了同步屏障是一个特殊的Message)然后由于同步屏障的作用MessageQueue中那些非异步的消息都不进行获取操作了,这么做就是保障刷新的Message能够第一时间得到Looper的调用。 接着看mChoreographer.postCallback的内部代码

   Message msg = mHandler.obtainMessage(MSG_DO_SCHEDULE_CALLBACK, action);
                msg.arg1 = callbackType;
                msg.setAsynchronous(true);
                mHandler.sendMessageAtTime(msg, dueTime);

发现了这个是发送异步Message的代码。由于其他同步Message都无法调用,所以这个异步消息可以第一时间得到调用。 同时绘制代码如下

 void doTraversal() {
        if (mTraversalScheduled) {
            mTraversalScheduled = false;
            mHandler.getLooper().getQueue().removeSyncBarrier(mTraversalBarrier);

在绘制代码中通过 mHandler.getLooper().getQueue().removeSyncBarrier(mTraversalBarrier)这个代码把这个同步屏障给去除了,使整个Looper又恢复正常的调用。

三.Handler发消息的主要方法

Handler发消息的几种方式(之前看某个谈面试心得的时候说过)

1. public final boolean post(Runnable r)
2. public final boolean postDelayed(Runnable r, long delayMillis)
3. public final boolean sendEmptyMessage(int what)
4. public final boolean sendMessage(Message msg)
5. public boolean sendMessageAtTime(Message msg, long uptimeMillis) 
6. public final boolean sendMessageDelayed(Message msg, long delayMillis)

这些方法最后都是调用的 public boolean sendMessageAtTime(Message msg, long uptimeMillis) 方法。

其中Runnable r被getPostMessage 方法包装成了Message参数

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

四.Handler处理消息的方法

主要为dispatchMessage方法

 public void dispatchMessage(Message msg) {
        if (msg.callback != null) {
            handleCallback(msg);
        } else {
            if (mCallback != null) {
                if (mCallback.handleMessage(msg)) {
                    return;
                }
            }
            handleMessage(msg);
        }
    }

看代码首先第一步会去取msg中callback字段,该字段在第三部分中(Handler发消息的主要方法)已经提到,是在Handler.post(Runnable r)方法调用中,参数r被getPostMessage方法封装而来。 如果msg中callback为null,则走第二步,调用Handler中参数mCallback中的handlerMessage方法,这个mCallback则是在Handler构造时传入,可参考第二部分。若这个也没有,才会走我们熟悉的第三步调用Handler的复写handleMessage(msg)方法。

其他相关问题:

  1. 由于Looper.loop()方法会进入死循环,那么子线程在Looper.loop()之后的代码是否会被执行?

答:不会

protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_second);
        initView();
        new Thread(new Runnable() {
            public void run() {
                Looper.prepare();
                Handler handler = new Handler(){
                    @Override
                    public void handleMessage(Message msg) {
                        Toast.makeText(getApplicationContext(), "handler msg", Toast.LENGTH_LONG).show();
                    }
                };
                handler.sendEmptyMessage(1);
                Looper.loop();
                Toast.makeText(getApplicationContext(), "after loop", Toast.LENGTH_LONG).show();
            }
        }).start();
    }

如上代码进行了测试,after loop 不会被调用,这样就留下了一个坑,会导致子线程一直在循环中不会被回收。所以子线程用了Looper.loop以后一定要调用looper.quit() or looper.quitSafely()进行退出。否则你这个线程就不会被回收,后果就是资源泄漏。


2.The following Handler class should be static or leaks might occur: <classCanonicalName> 内存泄漏

原因:

由于Handler有可能会被Looper#mQueue#mMessages#target引用,而很有可能由于消息还未到达处理的时刻,导致引用会被长期持有,如果Handler是一个非静态内部类,就会持有一个外部类实例的引用,进而导致外部类很有可能出现无法及时gc的问题。

通用解决方法:

直接静态化内部类,这样内部类Handler就不再持有外部类实例的引用,再在Handler的构造函数中以弱引用(当所指实例不存在强引用与软引用后,GC时会自动回弱引用指向的实例)传入外部类供使用即可。

示例代码

public static class WeakUiHandler<T> extends Handler {
    WeakReference<T> classInstance; 

    public WeakUiHandler(T instance) {
        classInstance = new WeakReference<T>(instance);
    }

    public T getClassInstance() {
        return classInstance.get();
    }
}


参考资料:

  1. blog.dreamtobe.cn/2016/03/11/… 强烈推荐
  2. www.cnblogs.com/angeldevil/…