美文网首页
Handler源码解析

Handler源码解析

作者: 沐络 | 来源:发表于2022-04-26 14:36 被阅读0次

    一,Handler的使用

    1)创建全局静态常量

    class Cons{
         static final int MSG_WHAT_0x1000 = 0x1000;
         static final int MSG_WHAT_0x1001 = 0x1001;
    }
    

    2)创建Handler对象

    private static Handler handler = new Handler(new Handler.Callback() {
            @Override
            public boolean handleMessage(@NonNull Message msg) {
                switch (msg.what){
                    case MSG_WHAT_0x1000:{
                        Log.d("MainActivity","show message-->"+msg);
                    }
                    case MSG_WHAT_0x1001:{
    
                    }
                    break;
                    default:
                        throw new IllegalStateException("Unexpected value: " + msg.what);
                }
                return false;
            }
        });
    

    3)Handler消息的发送

    handler.sendMessage(handler.obtainMessage(MSG_WHAT_0x1000));
    

    4)移除消息

     @Override
        protected void onDestroy() {
            super.onDestroy();
            handler.removeCallbacksAndMessages(null);
        }
    

    如上便是谷歌官方推荐的Handler使用方式,上述代码可能对一些新手有一些疑问,在此罗列(大佬请绕道):
    Q1,创建Handler时使用了“new Handler.Callback()”而不是如下方式创建:

     private Handler handler = new Handler(){
            @Override
            public void handleMessage(@NonNull Message msg) {
                super.handleMessage(msg);
            }
        };
    

    Q2,创建Handler对象时,使用了static 对handler对象的修饰
    Q3,发送消息时使用了“handler.obtainMessage”而不是直接new
    Q4,在onDestroy中调用removeCallbacksAndMessages
    接下来就带着以上疑问进入源码分析。

    二,Handler源码流程分析

    Handler的源码分析在Framework层目前主要涉及到四个类:Handler,Message,MessageQueue,Looper。
    1,Looper及MessageQueue对象创建
    在AndroidStudio中多次Shift键或者在线源码找到ActivityThread.java类,它便是Android的主线程也称为UI线程,Looper的初始化便在其main函数中

    //见源码7310行
     public static void main(String[] args) {
            //省略若干代码
            (1)此处调用了创建了Looper对象
            Looper.prepareMainLooper();
            //省略若干代码
            (2)开启消息的轮询
            Looper.loop();
            throw new RuntimeException("Main thread loop unexpectedly exited");
        }
    

    根据(1)处的代码,进入到Looper类的prepareMainLooper函数中

    //见Looper源码119行
     public static void prepareMainLooper() {
            //调用内部函数进行Looper对象的创建
            prepare(false);
            synchronized (Looper.class) {
                if (sMainLooper != null) {
                    throw new IllegalStateException("The main Looper has already been prepared.");
                }
                sMainLooper = myLooper();
            }
        }
        
    private static void prepare(boolean quitAllowed) {
            if (sThreadLocal.get() != null) {
                throw new RuntimeException("Only one Looper may be created per thread");
            }
           //创建一个Looper对象,并存入到ThreadLocal中
            sThreadLocal.set(new Looper(quitAllowed));
        }
        
     private Looper(boolean quitAllowed) {
            //创建了一个MessageQueue
            mQueue = new MessageQueue(quitAllowed);
            mThread = Thread.currentThread();
        }
    

    上述便是Looper的创建过程了,创建了主线程中唯一的Looper对象,并存入到了ThreadLocal,在其构造函数中并创建了一个MessageQueue的消息队列。
    关于ThreadLocal,它内部有一个ThreadLocalMap,类似与HashMap的数据结构,其键是当前ThreadLocal,值是要存放的属性。
    在Thread.java源码中可以看到其内部有一个 ThreadLocal.ThreadLocalMap threadLocals = null;的内部变量,而我们创建的ThreadLocal最后都会存放到该Map中。
    ThreadLocal存放值时其源码如下:

      public void set(T value) {
            //获取当前线程
            Thread t = Thread.currentThread();
            //获取当前线程中的ThreadLocalMap
            ThreadLocalMap map = getMap(t);
            //如果map不为空,则直接存入当前值
            if (map != null)
                map.set(this, value);
            else
            //创建一个ThreadLocalMap并存入当前线程中
                createMap(t, value);
        }
    

    2,开启Looper的轮询
    在ActivityThread的main函数中可知,在创建完成Looper后,又调用了Looper.loop()函数,主要目的就是开启消息的轮询。
    进入Looper的loop函数

      public static void loop() {
            //拿到当前Looper对象
            final Looper me = myLooper();
            if (me == null) {
                throw new RuntimeException("No Looper; Looper.prepare() wasn't called on this thread.");
            }
            //获取Looper中的MessageQueue,在Looper创建时,会连带创建一个MessageQueue
            final MessageQueue queue = me.mQueue;
            //开启死循环
            for (;;) {
                //(1)调用MessageQueue的函数不停的从消息队列中取出消息
                Message msg = queue.next(); // might block
                if (msg == null) {
                    // No message indicates that the message queue is quitting.
                    return;
                }
                //省略...
                try {
                    //(2)调用Message中的target(即Handler对象)进行消息的消费
                    msg.target.dispatchMessage(msg);
                   }
            //省略...
            }
        }
    

    (1)MessageQueue中的next函数一探究竟,发现其内部也是一个死循环,并返回一个Message对象。

     Message next() {
            //死循环,从队列中取出消息进行消费
            for (;;) {
            //省略...
            }
        }
    

    如上只是消息的消费的过程,接下来,进入分发过程。
    3,Handler消息的发送
    根据示例代码中,创建一个消息时,都会调用handler对象的send或post函数,进入handler源码。

        public final boolean sendMessage(@NonNull 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);
        }
    
        
        public final boolean postDelayed(@NonNull Runnable r, long delayMillis) {
            return sendMessageDelayed(getPostMessage(r), delayMillis);
        }
        
        public final boolean postDelayed(Runnable r, int what, long delayMillis) {
            return sendMessageDelayed(getPostMessage(r).setWhat(what), delayMillis);
        }
        
      public final boolean sendMessageDelayed(@NonNull Message msg, long delayMillis) {
            if (delayMillis < 0) {
                delayMillis = 0;
            }
            return sendMessageAtTime(msg, SystemClock.uptimeMillis() + delayMillis);
        }
    
     public boolean sendMessageAtTime(@NonNull 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);
        }
    
    

    从上诉代码可知,不管是send或者post的消息最后都调用了enqueueMessage函数

    private boolean enqueueMessage(@NonNull MessageQueue queue, @NonNull Message msg,
                long uptimeMillis) {
                //关键代码,把当前handler对象赋值给Message
            msg.target = this;
            msg.workSourceUid = ThreadLocalWorkSource.getUid();
    
            if (mAsynchronous) {
                msg.setAsynchronous(true);
            }
            //把消息按照执行时间存放到MessageQueue中
            return queue.enqueueMessage(msg, uptimeMillis);
        }
    

    进入到enqueueMessage中,发现我们发送的消息,最后都会调用msg.target = this 把当前Handler对象存放到Message中,在上述Looper的loop轮询中调用的msg.target.dispatchMessage(msg);,就是此时存入的handler对象。

    接下来我们就进入Handler中的dispatchMessage函数

     public void dispatchMessage(@NonNull Message msg) {
            //判断Message中的runable对象是否为空
            if (msg.callback != null) {
                handleCallback(msg);
            } else {
                //此处就回到了第一章节中的Q1,它首先判断了我们传入的callback不为空,先调用callback的接口实现,
                if (mCallback != null) {
                    if (mCallback.handleMessage(msg)) {
                        return;
                    }
                }
                //如果callback为空的话,调用内部重写函数,即传统的使用方式,最后执行。
                handleMessage(msg);
            }
        }
    

    到此整个Handler的流程大概跑通了
    4,Message源码解析

    public final class Message implements Parcelable {
        /**
         * User-defined message code so that the recipient can identify
         * what this message is about. Each {@link Handler} has its own name-space
         * for message codes, so you do not need to worry about yours conflicting
         * with other handlers.
         */
        public int what;
    
        /**
         * arg1 and arg2 are lower-cost alternatives to using
         * {@link #setData(Bundle) setData()} if you only need to store a
         * few integer values.
         */
        public int arg1;
    
        /**
         * arg1 and arg2 are lower-cost alternatives to using
         * {@link #setData(Bundle) setData()} if you only need to store a
         * few integer values.
         */
        public int arg2;
    
        /**
         * An arbitrary object to send to the recipient.  When using
         * {@link Messenger} to send the message across processes this can only
         * be non-null if it contains a Parcelable of a framework class (not one
         * implemented by the application).   For other data transfer use
         * {@link #setData}.
         *
         * <p>Note that Parcelable objects here are not supported prior to
         * the {@link android.os.Build.VERSION_CODES#FROYO} release.
         */
        public Object obj;
    
        /**
         * Optional Messenger where replies to this message can be sent.  The
         * semantics of exactly how this is used are up to the sender and
         * receiver.
         */
        public Messenger replyTo;
    
        /**
         * Indicates that the uid is not set;
         *
         * @hide Only for use within the system server.
         */
        public static final int UID_NONE = -1;
    
        /**
         * Optional field indicating the uid that sent the message.  This is
         * only valid for messages posted by a {@link Messenger}; otherwise,
         * it will be -1.
         */
        public int sendingUid = UID_NONE;
    
        /**
         * Optional field indicating the uid that caused this message to be enqueued.
         *
         * @hide Only for use within the system server.
         */
        public int workSourceUid = UID_NONE;
    
        /** If set message is in use.
         * This flag is set when the message is enqueued and remains set while it
         * is delivered and afterwards when it is recycled.  The flag is only cleared
         * when a new message is created or obtained since that is the only time that
         * applications are allowed to modify the contents of the message.
         *
         * It is an error to attempt to enqueue or recycle a message that is already in use.
         */
        /*package*/ static final int FLAG_IN_USE = 1 << 0;
    
        /** If set message is asynchronous */
        /*package*/ static final int FLAG_ASYNCHRONOUS = 1 << 1;
    
        /** Flags to clear in the copyFrom method */
        /*package*/ static final int FLAGS_TO_CLEAR_ON_COPY_FROM = FLAG_IN_USE;
    
        @UnsupportedAppUsage
        /*package*/ int flags;
    
        /**
         * The targeted delivery time of this message. The time-base is
         * {@link SystemClock#uptimeMillis}.
         * @hide Only for use within the tests.
         */
        @UnsupportedAppUsage
        @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE)
        public long when;
    
        /*package*/ Bundle data;
    
        @UnsupportedAppUsage
        /*package*/ Handler target;
    
        @UnsupportedAppUsage
        /*package*/ Runnable callback;
    
        //单链表
        /*package*/ Message next;
    
    
        /** @hide */
        public static final Object sPoolSync = new Object();
       //缓存池
        private static Message sPool;
        private static int sPoolSize = 0;
    
    
        private static final int MAX_POOL_SIZE = 50;
    
        private static boolean gCheckRecycle = true;
    
        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 static Message obtain(Message orig) {
            Message m = obtain();
            m.what = orig.what;
            m.arg1 = orig.arg1;
            m.arg2 = orig.arg2;
            m.obj = orig.obj;
            m.replyTo = orig.replyTo;
            m.sendingUid = orig.sendingUid;
            m.workSourceUid = orig.workSourceUid;
            if (orig.data != null) {
                m.data = new Bundle(orig.data);
            }
            m.target = orig.target;
            m.callback = orig.callback;
    
            return m;
        }
    

    如上代码,可知Message其是一个单链表的数据结构。

    三,调用流程图

    image.png

    其执行流程如下:


    image.png

    四,答疑

    Q1,在已在dispatchMessage源码进行进行解答,这也就是为啥谷歌官方推荐使用callback的方式创建handler
    Q2,在创建Handler是,大多时候都是使用的内部类的方式创建,而我们使用Handler时,基本都是在Activity/Fragment中使用的,内部类持有了上下文,会导致内存泄漏的问题,因此使用static进行修饰,当然也可以使用弱引用的方式。

    Q3,发送消息时使用了“handler.obtainMessage”而不是直接new?
    在Message的源码中,我们发现存在缓存池的概念,调用obtainMessage时,会先从缓存池中取,如果取不到则进行创建并加入缓存池中,这里使用到了设计模式中的享元设计,类似于RecyclerView的复用机制,因为大量的创建Message对象,存在资源浪费的情况,复用Message,可以节约内存开销。

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

    Q4,在onDestroy中调用removeCallbacksAndMessages
    答:主要是在页面销毁时,即时的回收消息,防止内存泄漏。

    五,面试题解析

    1, Looper 的 loop() 死循环为什么不卡死?
    为了让主线程持续处理用户的输入,loop() 是死循环,持续调用 MessageQueue#next() 读取合适的 Message。

    但当没有 Message 的时候,会调用 pollOnce() 并通过 Linux 的 epoll 机制进入等待并释放资源。同时 eventFd 会监听 Message 抵达的写入事件并进行唤醒。

    这样可以空闲时释放资源、不卡死线程,同时能持续接收输入的目的。

    彩蛋1:loop() 后的处理为什么不可执行

    因为 loop() 是死循环,直到 quit 前后面的处理无法得到执行,所以避免将处理放在 loop() 的后面。

    2,异步 Message 或同步屏障
    异步 Message:设置了 isAsync 属性的 Message 实例

    可以用异步 Handler 发送
    也可以调用 Message#setAsynchronous() 直接设置为异步 Message
    同步屏障:在 MessageQueue 的某个位置放一个 target 属性为 null 的 Message,确保此后的非异步 Message 无法执行,只能执行异步 Message

    原理:当 MessageQueue 轮循 Message 时候发现建立了同步屏障的时候,会去跳过其他 Message,读取下个 async 的 Message 并执行,屏障移除之前同步 Message 都会被阻塞

    总结:异步消息一般是系统内部使用的,当handler收到异步消息时,会优先处理异步消息,等到异步消息处理完后,才会处理同步消息,例如vsync信号

    3,IdleHandler 空闲 Message
    适用于期望空闲时候执行,但不影响主线程操作的任务

    系统应用:

    Activity destroy 回调就放在了 IdleHandler 中
    ActivityThread 中 GCHandler 使用了 IdleHandler,在空闲的时候执行 GC 操作
    App 应用:

    发送一个返回 true 的 IdleHandler,在里面让某个 View 不停闪烁,这样当用户发呆时就可以诱导用户点击这个 View
    将某部分初始化放在 IdleHandler 里不影响 Activity 的启动

    相关文章

      网友评论

          本文标题:Handler源码解析

          本文链接:https://www.haomeiwen.com/subject/diocyrtx.html