美文网首页
Android Handler源码阅读(技术记录/回忆)

Android Handler源码阅读(技术记录/回忆)

作者: CrazyDevp | 来源:发表于2020-10-09 12:37 被阅读0次

    Handler是Android中最常用线程通讯方式之一、也是非UI线程与线程通讯的主要方式。

    你可能有个疑问基础api中AsyncTask、runOnUiThread()还是第三方的RxJava、Eventbus内部都是直接或间接使用Handler实现对UI线程进行更新(参照源码)。

    //-- runOnUiThread
    public final void runOnUiThread(Runnable action) {
            if (Thread.currentThread() != mUiThread) {
                mHandler.post(action);
            } else {
                action.run();
            }
        }
    //-- Eventbus
    package org.greenrobot.eventbus;
    import android.os.Handler;
    public class HandlerPoster extends Handler 
    // Rxjava
    Flowable.create(..., BUFFER).subscribeOn(Schedulers.io()).observeOn(AndroidSchedulers.mainThread());
    

    那么既然Handler这么牛逼那么它内部是怎么实现更新UI的,以及为什么它能够更新UI线程呢?我相信做Android的或多或少的看过相关的资料,了解Handler的核心机制由Looper、Handler、MessageQueue、Message四大金刚完成;
    1.Looper发动机负责转动整个消息队列
    2.Handler执行者取队列任务干活的工具人
    3.MessageQueue任务链表队列、一个挨着一个的那种
    4.Message任务详细信息,以及任务所需要的数据(序列化Parcelable)
    那么我们现在一个一个来解读(没有比代码更让我们程序员感到亲切的了,以下...为忽略非关键代码)源码阅读

    1.Looper

        // sThreadLocal.get() will return null unless you've called prepare().
        // 每个线程里有且只可能有一个Looper、而所有的线程对应的Looper都存在这里面
        static final ThreadLocal<Looper> sThreadLocal = new ThreadLocal<Looper>();
        // UI线程的主Looper,在ActivityThread中的main函数启动的时候创建
        private static Looper sMainLooper;  // guarded by Looper.class
        private static Observer sObserver;
    
        // 这么快就出现了伙计,当前Looper的消息队列。
        final MessageQueue mQueue;
        // 当前Looper所在的线程
        final Thread mThread;
        ...
        private Looper(boolean quitAllowed) {
            // 参数为是否可安全退出(是否执行完任务再退出)
            mQueue = new MessageQueue(quitAllowed);
            mThread = Thread.currentThread();
        }
        ...
        private static void prepare(boolean quitAllowed) {
            // 一个线程里面只能存一个Looper,所以多次prepare会Exception
            if (sThreadLocal.get() != null) {
                throw new RuntimeException("Only one Looper may be created per thread");
            }
            sThreadLocal.set(new Looper(quitAllowed));
        }
    
        // 初始化主线程的Looper,不需要我们调用
        public static void prepareMainLooper() {
            ...
        }
        
        // 永动机启动
        public static void loop() {
            final Looper me = myLooper();
            ...
            Binder.clearCallingIdentity();//(native方法切换进程身份pid/uid这里不详细讲解了)
            final long ident = Binder.clearCallingIdentity(); // 你没看错第一次调用是检查、第二次调用才是切换进程id跟用户id,不然没办法访问ui的
            ...
            final MessageQueue queue = me.mQueue;// 取队列
            ...
            // 上来就是一个死循环,屌不屌
            for (;;) {
                // 取消息队列取到就往下执行,取不到轮循(并非空转、后面会讲next方法的机制)
                Message msg = queue.next(); // might block
                if (msg == null) {
                    // No message indicates that the message queue is quitting.
                    return;
                }
                ...
                    // Handler通过dispatchMessage分发Message
                    msg.target.dispatchMessage(msg);
                ...
                // 切换原来的身份
                final long newIdent = Binder.clearCallingIdentity();
                ...
                // 用完了的Message回收
                msg.recycleUnchecked();
            }
        }
    

    上面是我提炼出来的核心代码(我认为)
    1).prepare:在当前线程初始化Looper(同一线程禁止重复调用\线程与Looper为1:1关系)
    2).loop:检查并切换pid/uid,通过死循(并非一直空转)环轮询MessageQueue,通过Message中的Handler分发消息,切换回自己的pid/uid,Message对象回收进对象池
    3).quiteSafely: 安全退出/非安全退出

    2. Handler

    // 平时如果想用主线程的Looper更新UI用它用它用它getMain(),不用再new了新版本sdk才支持getMain
       private static Handler MAIN_THREAD_HANDLER = null;
     
       public Handler(@Nullable Callback callback, boolean async) {
            ...
            // 获取当前所在线程的Looper
            mLooper = Looper.myLooper();
            if (mLooper == null) {
                throw new RuntimeException(...);
            }
            // 取Looper中的消息队列
            mQueue = mLooper.mQueue;
            mCallback = callback;
            mAsynchronous = async;
        } 
        // 执行消息回调
        public void dispatchMessage(@NonNull Message msg) {
            //优先执行message中的Callback,想不到吧我也有callback
            if (msg.callback != null) {
                handleCallback(msg);
            } else {
                // 否则执行Handler中的callback
                if (mCallback != null) {
                    if (mCallback.handleMessage(msg)) {
                        return;
                    }
                }
                // 最后才轮到handMessage我最常用的方式确是优先级最低的,衰。
                handleMessage(msg);
            }
        }
        ...
        // Message对象池取对象,没有则创建
        public static Message obtain(...){...}
        ...
        // 直接将发送自己创建Message实体
        public boolean sendxxx(){}
        // 发送回调接口让Handler帮我们创建Message
        public boolean postxxx(){}
        ...
        // 不管你是那条道,终点都在这
        private boolean enqueueMessage(@NonNull MessageQueue queue, @NonNull Message msg,
                long uptimeMillis) {
            // 发送消息前将自己作为执行者赋值给Message以便于后面轮到自己的时候有人执行。
            msg.target = this;
            ...
            // 加入到消息队列,队列会根据时间排序入列
            return queue.enqueueMessage(msg, uptimeMillis);
        }
        // 没它啥都干不了
        final Looper mLooper;
        // 执行者负责根据需求创建任务放入列表
        final MessageQueue mQueue;
        // 远程服务binder中的service,用于进程通讯 对就是用来进程通讯的
        IMessenger mMessenger;
        // 远程服务的实现
        private final class MessengerImpl extends IMessenger.Stub {
            public void send(Message msg) {
                msg.sendingUid = Binder.getCallingUid();
                Handler.this.sendMessage(msg);
            }
        }
    

    Handler的主要职能就是对外暴露通讯接口(通讯起点\终点),Message对象构造回收。

    1. send/post 通讯起点
    2. obain 对象复用
    3. enqueueMessage入列消息
    4. dispatchMessage 通讯终点

    3. Message

    // 通讯的数据包
        Bundle data;
        // 执行者
        Handler target;
        // 下一位
        Message next;
        // 对象池(链表)中的头部
        private static Message sPool;
        // 对象池中对象数量
        private static int sPoolSize = 0;
        // 对象池中的存储上限
        private static final int MAX_POOL_SIZE = 50;
        ...//这里还有各种状态机、基础数据、进程uid等等
    
        // 使用缓存的空Message,没有则创建
         public static Message obtain() {
            synchronized (sPoolSync) {
                if (sPool != null) {
                    // 取对象池(链表)中的对象,同时链表链表前移
                    ...
                }
            }
            return new Message();
        }
        // 其实就是深拷贝
        ...obtain(...){...}
        // 使用完清理回收
        void recycleUnchecked() {
            // 清空状态、数据、回调
            flags = FLAG_IN_USE;
            ...
            // 加入到缓存队列中,修改计数器,溢出则不入列
            synchronized (sPoolSync) {
                if (sPoolSize < MAX_POOL_SIZE) {
                    ...
                }
            }
        }
    
    1. obtain:对象池出列
    2. recycleUnchecked:对象池出列
    3. flags:状态机

    4. MessageQueue

        // 消息
        Message mMessages;
        // 未执行的 补充操作队列(UI刷新的dispatch执行完了 可以补充做一些处理)
        private final ArrayList<IdleHandler> mIdleHandlers = new ArrayList<IdleHandler>();
        // 文件描述符
        private SparseArray<FileDescriptorRecord> mFileDescriptorRecords;
        // 正在执行的 补充操作队列
        private IdleHandler[] mPendingIdleHandlers;
        private boolean mQuitting;
    
        // 是否阻塞
        private boolean mBlocked;
        ...
        MessageQueue(boolean quitAllowed) {
            mQuitAllowed = quitAllowed;
            // 创建消息队列-底层队列c/c++
            mPtr = nativeInit();
        }
        ...
        // 核心方法取队列
        Message next() {
            // 底层native的消息队列头指针
            final long ptr = mPtr;
            if (ptr == 0) {
                return null;// java队列与native队列必须同步,否则返回空结束loop轮询
            }
    
            int pendingIdleHandlerCount = -1; // -1 only during first iteration
            // 下次执行的时间
            int nextPollTimeoutMillis = 0;
            // 又一个死循环不停的在queue中取消息
            for (;;) {
                if (nextPollTimeoutMillis != 0) {
                    // 调用native方法查看当前是否需要挂起当前队列(CPU繁忙的情况下)
                    Binder.flushPendingCommands();
                }
                // 同步native消息队列出列下一个要执行的消息
                nativePollOnce(ptr, nextPollTimeoutMillis);
    
                synchronized (this) {
                    final long now = SystemClock.uptimeMillis();
                    Message prevMsg = null;
                    Message msg = mMessages;
                    // 可执行的同步消息
                    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 {
                            //大于则将该消息标记为可执行 出列 返回
                            mBlocked = false;
                            if (prevMsg != null) {
                                prevMsg.next = msg.next;
                            } else {
                                mMessages = msg.next;
                            }
                            msg.next = null;
                            msg.markInUse();// 标记消息状态并返回该消息
                            return msg;
                        }
                    } else {
                        // No more messages.
                        nextPollTimeoutMillis = -1;
                    }
    
                    // 进程或者线程结束 销毁队列包括native队列结束轮循
                    if (mQuitting) {
                        dispose();
                        return null;
                    }
                    ...// 取当前未执行的 补充操作队列
                }
    
                ...// 执行补充操作队列
            }
        }
        // 添加消息到队列
        boolean enqueueMessage(Message msg, long when) {
            ...// 消息状态判断
    
            synchronized (this) {
                if (mQuitting) {
                    ...// 队列已经销毁退出
                }
    
                msg.markInUse();
                msg.when = when;
                Message p = mMessages;
                // 是否需要唤醒
                boolean needWake;
                // 空队列或者执行时间小于头部Message的执行时间
                if (p == null || when == 0 || when < p.when) {
                    // 是否需要唤醒
                    msg.next = p;
                    mMessages = msg;
                    needWake = mBlocked;
                } else {
                    // 唤醒条件 是否阻塞 头部没有执行者 添加的消息为异步消息
                    needWake = mBlocked && p.target == null && msg.isAsynchronous();
                    Message prev;
                    // 根据Message when执行时间顺序插入到链表中
                    for (;;) {
                        prev = p;
                        p = p.next;
                        if (p == null || when < p.when) {
                            break;
                        }
                        if (needWake && p.isAsynchronous()) {
                            needWake = false;
                        }
                    }
                    msg.next = p; // invariant: p == prev.next
                    prev.next = msg;
                }
    
                // 唤醒队列,传入native队列的头指针
                if (needWake) {
                    nativeWake(mPtr);
                }
            }
            return true;
        }
        ...//还有一些其它的代码跟队列无关,native文件描述符监听,看命名跟dns tcp网络相关这里就不过多解读了
    

    MessageQueue作为一个队列内部的大量逻辑跟native相关,因为涉及到cpu资源利用等一系列性能问题所以使用Java队列+Native队列,比如防止死循环的空转导致大量CPU资源消耗、CPU资源紧张的时消息未到执行时间则挂起等等,ideHandler(跟Handler无关)在App启动时候因为cpu资源紧张但又想不让队列挂起所做的补充操作,文件描述符监听等等代码。
    1). next: 消息出列
    2). enqueueMessage: 消息入列

    最后我们常常使用Handler导致内存泄漏,是因为Message会持有Handler/Callback的引用,而这两者经常是内部或者匿名类会持有外部类的引用(如Acitivty),在Message为delay操作时Acitivty退出销毁因为被Message引用而导致内存泄漏;而自线程有能够访问ui线程主要还是通过native中切换线程uid来实现的(Linux系统下线程资源访问通过uid识别),而这个由native实现 感兴趣的小伙伴可以看看Binder. clearCallingIdentity的源码。

    今天就记录这么多了,第一次写技术长文有些瑕疵希望有心看到这篇记录的不要介意。

    下次我来补充讲一下ThreadLocal相关的内容,因为这个东东很多小伙伴也有许多疑问。

    相关文章

      网友评论

          本文标题:Android Handler源码阅读(技术记录/回忆)

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