美文网首页
Handler要点一览

Handler要点一览

作者: taoyyyy | 来源:发表于2020-03-11 12:58 被阅读0次

    Handler

    发消息

    无论是通过postRunnable、postDelay还是sendMessage,最终都调用到了android.os.Handler#sendMessageAtTime,最终消息通过具体执行时间入队,MessageQueue取消息的时候也是到这个具体的时间才会去执行这个消息。

    处理消息

    Looper.loop通过MessageQueue取到消息后,最后是通过android.os.Handler#dispatchMessage来处理。

        public void dispatchMessage(Message msg) {
            //Handler#postRunnable时会将这个Runnable对象封装到msg.callback中
            if (msg.callback != null) {
                handleCallback(msg);
            } else {
            //实例化Handler时可以传入一个Callback对象来优先处理msg
                if (mCallback != null) {
                    if (mCallback.handleMessage(msg)) {
                        return;
                    }
                }
                //实例化Handler对象时可以重写此方法处理msg
                handleMessage(msg);
            }
        }
        
        private static void handleCallback(Message message) {
            message.callback.run();
        }
        public Handler(Callback callback, boolean async) {
            ...
            mQueue = mLooper.mQueue;
            mCallback = callback;
            mAsynchronous = async;
        }
         public interface Callback {
                public boolean handleMessage(Message msg);
         }
        }
    

    关于android.os.Handler#mCallback对象,可以注意到若mCallback.handleMessage(msg)返回true则消息就处理完了,不会再让Handler#handleMessage去处理,这点在插件化中有用到,如代理ActivityThread#H。

    MessageQueue

    MessageQueue本质上是个链表,以Message具体的执行时间为顺序排序各个消息。

    MessageQueue中的消息分类

    1. 同步消息
      正常情况下我们通过Handler发送的Message都属于同步消息,除非我们在发送的时候执行该消息是一个异步消息。
      同步消息会按顺序排列在队列中,除非指定Message的执行时间,否咋Message会按顺序执行。
    2. 异步消息
      想要往消息队列中发送异步消息,我们必须在初始化Handler的时候通过构造函数public Handler(boolean async)中指定Handler是异步的,这样Handler在讲Message加入消息队列的时候就会将Message设置为异步的。
    3. 障栅
      障栅(Barrier) 是一种特殊的Message,它的target为null(只有障栅的target可以为null,如果我们自己视图设置Message的target为null的话会报异常),并且arg1属性被用作障栅的标识符来区别不同的障栅。
      障栅的作用是用于拦截队列中同步消息,放行异步消息。
      在道路拥挤的时候会决定哪些车辆可以先通过,这些可以通过的车辆就是异步消息。

    Looper

    Looper.prepare

    Looper 中 prepare() 方法为当前线程创建一个 Looper 对象,并将其存储在该线程的ThreadLocal对象中。Looper对象在创建时会初始化与之关联的MessageQueue。

        public static void prepare() {
            prepare(true);
        }
    
        private static void prepare(boolean quitAllowed) {
            if (sThreadLocal.get() != null) {
                throw new RuntimeException("Only one Looper may be created per thread");
            }
            
            // 将 Looper 对象保存到当前线程的 ThreadLocalMap 当中
            sThreadLocal.set(new Looper(quitAllowed));
        }
        
        
    private Looper(boolean quitAllowed) {
        mQueue = new MessageQueue(quitAllowed);
        mThread = Thread.currentThread();
    }
    

    Looper.loop

        public static void loop() {
        
    // 通过 Thread Local 获取当前线程的 Looper
            final Looper me = myLooper();
            if (me == null) {
                throw new RuntimeException("No Looper; Looper.prepare() wasn't called on this thread.");
            }
            final MessageQueue queue = me.mQueue;
    
            // Make sure the identity of this thread is that of the local process,
            // and keep track of what that identity token actually is.
            Binder.clearCallingIdentity();
            final long ident = Binder.clearCallingIdentity();
    
            // Allow overriding a threshold with a system prop. e.g.
            // adb shell 'setprop log.looper.1000.main.slow 1 && stop && start'
            final int thresholdOverride =
                    SystemProperties.getInt("log.looper."
                            + Process.myUid() + "."
                            + Thread.currentThread().getName()
                            + ".slow", 0);
    
            boolean slowDeliveryDetected = false;
    
            for (;;) {
                Message msg = queue.next(); // might block
                if (msg == null) {
                    // No message indicates that the message queue is quitting.
                    return;
                }
    
                // This must be in a local variable, in case a UI event sets the logger
                final Printer logging = me.mLogging;
                if (logging != null) {
                
                    // 用 logging 打印日志,默认为 null,可通过 setMessageLogging() 方法来指定,BlocakCanary利用了这个Printer对象。
                    logging.println(">>>>> Dispatching to " + msg.target + " " +
                            msg.callback + ": " + msg.what);
                }
    
                final long traceTag = me.mTraceTag;
                long slowDispatchThresholdMs = me.mSlowDispatchThresholdMs;
                long slowDeliveryThresholdMs = me.mSlowDeliveryThresholdMs;
                if (thresholdOverride > 0) {
                    slowDispatchThresholdMs = thresholdOverride;
                    slowDeliveryThresholdMs = thresholdOverride;
                }
                final boolean logSlowDelivery = (slowDeliveryThresholdMs > 0) && (msg.when > 0);
                final boolean logSlowDispatch = (slowDispatchThresholdMs > 0);
    
                final boolean needStartTime = logSlowDelivery || logSlowDispatch;
                final boolean needEndTime = logSlowDispatch;
    
                if (traceTag != 0 && Trace.isTagEnabled(traceTag)) {
                    Trace.traceBegin(traceTag, msg.target.getTraceName(msg));
                }
    
                final long dispatchStart = needStartTime ? SystemClock.uptimeMillis() : 0;
                final long dispatchEnd;
                try {
                    msg.target.dispatchMessage(msg);
                    dispatchEnd = needEndTime ? SystemClock.uptimeMillis() : 0;
                } finally {
                    if (traceTag != 0) {
                        Trace.traceEnd(traceTag);
                    }
                }
                if (logSlowDelivery) {
                    if (slowDeliveryDetected) {
                        if ((dispatchStart - msg.when) <= 10) {
                            Slog.w(TAG, "Drained");
                            slowDeliveryDetected = false;
                        }
                    } else {
                        if (showSlowLog(slowDeliveryThresholdMs, msg.when, dispatchStart, "delivery",
                                msg)) {
                            // Once we write a slow delivery log, suppress until the queue drains.
                            slowDeliveryDetected = true;
                        }
                    }
                }
                if (logSlowDispatch) {
                    showSlowLog(slowDispatchThresholdMs, dispatchStart, dispatchEnd, "dispatch", msg);
                }
    
                if (logging != null) {
                    logging.println("<<<<< Finished to " + msg.target + " " + msg.callback);
                }
    
                // Make sure that during the course of dispatching the
                // identity of the thread wasn't corrupted.
                final long newIdent = Binder.clearCallingIdentity();
                if (ident != newIdent) {
                    Log.wtf(TAG, "Thread identity changed from 0x"
                            + Long.toHexString(ident) + " to 0x"
                            + Long.toHexString(newIdent) + " while dispatching to "
                            + msg.target.getClass().getName() + " "
                            + msg.callback + " what=" + msg.what);
                }
    
                msg.recycleUnchecked();
            }
        }
    

    Looper.quit与quitSafely

    在子线程中,如果手动为其创建了Looper,那么在所有消息处理完成之后应该调用 quit() 方法终止消息循环,不然 Looper 就会一直处于等待状态。

    Looper与ThreadLocal

    Android 系统中便通过 ThreadLocal 对象来存储不同线程中的 Looper。

    ThreadLocal原理

    private ThreadLocal<String> mThreadLocal = new ThreadLocal<>();
    mThreadLocal.set("Thread_A");
    Log.d("ThreadLocalValue",mThreadLocal.get());
    
        public T get() {
            Thread t = Thread.currentThread();
            ThreadLocalMap map = getMap(t);
            if (map != null) {
                ThreadLocalMap.Entry e = map.getEntry(this);
                if (e != null) {
                    @SuppressWarnings("unchecked")
                    T result = (T)e.value;
                    return result;
                }
            }
            return setInitialValue();
        }
    

    同一ThreadLocal对象会存储不同线程的Thread对象和存储值的键值对集合。

    Looper死循环相关

    Looper#loop会死循环从消息队列中拿消息处理。
    MessageQueue#next会死循环拿到新消息,这是真正会阻塞的地方。

    死循环的意义

    让主线程可以一直存活以便处理处理各种消息

    死循环为何不会导致ANR

    linux特殊的io机制,使得没有消息的时候死循环不会占用CPU

    Handler内存泄漏的原因

    在你设定的delay到达之前,会有一条MessageQueue -> Message -> Handler -> Activity的链,导致你的Activity被持有引用而无法被回收。

    Handler之同步屏障机制(sync barrier)

    https://blog.csdn.net/asdgbc/article/details/79148180

    • 同步屏障的工作原理:MessageQueue.next函数中,如果发现头部消息是同步屏障之后,next函数将会忽略所有的同步消息,返回异步消息。
    • 如何发送异步消息:通常我们使用Handler发消息时,这些消息都是同步消息,如果我们想发送异步消息,那么在创建Handler时使用以下构造函数中的其中一种(async传true)
    public Handler(boolean async);
    public Handler(Callback callback, boolean async);
    public Handler(Looper looper, Callback callback, boolean async);
    
    • 如何发送和移除同步屏障消息:android.os.MessageQueue#postSyncBarrier(),android.os.MessageQueue#removeSyncBarrier
    • 同步屏障的应用:Android应用框架中为了更快的响应UI刷新事件在ViewRootImpl.scheduleTraversals中使用了同步屏障
    void scheduleTraversals() {
        if (!mTraversalScheduled) {
            mTraversalScheduled = true;
            //设置同步障碍,确保mTraversalRunnable优先被执行
            mTraversalBarrier = mHandler.getLooper().getQueue().postSyncBarrier();
            //内部通过Handler发送了一个异步消息
            mChoreographer.postCallback(
                    Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);
            if (!mUnbufferedInputDispatch) {
                scheduleConsumeBatchedInput();
            }
            notifyRendererOfFramePending();
            pokeDrawLockIfNeeded();
        }
    }
    

    参考:https://www.zhihu.com/question/19703357

    相关文章

      网友评论

          本文标题:Handler要点一览

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