Handler

作者: 小虫虫奇遇记 | 来源:发表于2020-08-09 10:30 被阅读0次

    Q1:Handler.postDelayed(runnable, time),假设delay time == 3min,如果把手机时间调整为比当前时间快3min, runnable会立马执行吗?

    答:不会。postDelayed最终会调用到sendMessageDelayed,注意此时用到的时间被处理成 SystemClock.uptimeMillis() + delayMillis,表示系统启动到当前的毫秒数+延时毫秒数。所以修改系统时间并不会改变这个数值,也就是说修改系统时间不影响handler的延时任务

    // Handler.java
    public final boolean sendMessageDelayed(Message msg, long delayMillis)
        {
            if (delayMillis < 0) {
                delayMillis = 0;
            }
            return sendMessageAtTime(msg, SystemClock.uptimeMillis() + delayMillis);
        }
    

    Q2:如果消息队列中的消息都剩下延时任务,最近的延时也要等到2s后,在此期间消息队列还会一直循环取消息吗?

    答:不会 ,Looper.loop()消息循环取消息的过程,重点在于nextPollTimeoutMillis的赋值,如果有消息未到执行时间,nextPollTimeoutMillis 赋值为需要等待的时间,传递给nativePollOnce方法,告知native空闲等待。nativePollOnce该函数可以看做睡眠阻塞的入口,该函数是一个native函数

    // MessageQueue.java
    Message next() {
            // Return here if the message loop has already quit and been disposed.
            // This can happen if the application tries to restart a looper after quit
            // which is not supported.
            final long ptr = mPtr;
            if (ptr == 0) {
                return null;
            }
    
            int pendingIdleHandlerCount = -1; // -1 only during first iteration
            int nextPollTimeoutMillis = 0;
            for (;;) {
                 //如果nextPollTimeoutMillis !=0 ,执行空闲等待阻塞循环;
                if (nextPollTimeoutMillis != 0) {
                    Binder.flushPendingCommands();
                }
                //告知native底层,到达nextPollTimeoutMillis 的时候,再唤醒循环
                nativePollOnce(ptr, nextPollTimeoutMillis);
                //synchronized同步,所以没消息的情况下,message.next()可能会阻塞
                synchronized (this) {
                    // Try to retrieve the next message.  Return if found.
                    final long now = SystemClock.uptimeMillis();
                    Message prevMsg = null;
                    Message msg = mMessages;
                    //优先处理异步消息;异步消息没有设置target且msg.setAsynchronous(true);
                    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) {
                            // 消息没到执行时间,则将nextPollTimeoutMillis设置为需要空闲等待阻塞时间。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) {
                                prevMsg.next = msg.next;
                            } else {
                                mMessages = msg.next;
                            }
                            msg.next = null;
                            if (DEBUG) Log.v(TAG, "Returning message: " + msg);
                            msg.markInUse();
                            return msg;
                        }
                    } else {
                        // No more messages.
                        nextPollTimeoutMillis = -1;
                    }
    
                    // Process the quit message now that all pending messages have been handled.
                    if (mQuitting) {
                        dispose();
                        return null;
                    }
                    ....
                // While calling an idle handler, a new message could have been delivered
                // so go back and look again for a pending message without waiting.
                nextPollTimeoutMillis = 0;
            }
        }
    

    Q3.卡顿检测原理:

      /**
         * Run the message queue in this thread. Be sure to call
         * {@link #quit()} to end the loop.
         */
        public static void loop() {
              ....  
            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.println(">>>>> Dispatching to " + msg.target + " " +
                            msg.callback + ": " + msg.what);
                }
             ....
                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 (logging != null) {
                    logging.println("<<<<< Finished to " + msg.target + " " + msg.callback);
                }
    
                msg.recycleUnchecked();
            }
        }
    

    可以看到在msg.target.dispatchMessage(msg);执行前后,都调用了logging.print方法,这个logging 还可以自定义设置自己的logging,

    Looper.getMainLooper().setMessageLogging(mBlockCanaryCore.monitor);

    class MyLooperMonitor implements Printer {
        ....
        @Override
        public void println(String x) {
            if (!mPrintingStarted) {
                mStartTimestamp = System.currentTimeMillis();
                mStartThreadTimestamp = SystemClock.currentThreadTimeMillis();
                mPrintingStarted = true;
                startDump();      //收集卡顿堆栈信息 thread.getStackTrace
            } else {
                final long endTime = System.currentTimeMillis();
                mPrintingStarted = false;
                if (isBlock(endTime)) {    //判断消息执行时间是否超时,如果endTime -    mStartTimestamp超过3s,视为卡顿。
    notifyBlockEvent(endTime);
                }
                stopDump();
            }
        }
    

    本地检测卡顿工具:

    adb shell dumpsys gfxinfo <PACKAGE_NAME>
    可打印出帧率,FPS 等数据。

    Q4:ThreadLocal会造成内存泄漏吗?

    ThreadLocal:线程变量副本;每个线程保存自己的副本,互不干扰。

    引用关系:Thread--- 内部变量:ThreadLocalMap map--->entry--->WeakReference<ThreadLocal> + value

    image.png
    public class Thread implements Runnable {
        // 每个Thread实例都持有一个ThreadLocalMap的属性
        ThreadLocal.ThreadLocalMap threadLocals = null;
    }
    
    //ThreadLocal.java  
    //ThreadLocalMap内部类 ,持有Entry, entry持有threadlocal的弱引用和对value的引用
      static class ThreadLocalMap {
            /**
             * The entries in this hash map extend WeakReference, using
             * its main ref field as the key (which is always a
             * ThreadLocal object).  Note that null keys (i.e. entry.get()
             * == null) mean that the key is no longer referenced, so the
             * entry can be expunged from table.  Such entries are referred to
             * as "stale entries" in the code that follows.
             */
            static class Entry extends WeakReference<ThreadLocal<?>> {
                /** The value associated with this ThreadLocal. */
                Object value;
    
                Entry(ThreadLocal<?> k, Object v) {
                    super(k);
                    value = v;
                }
            }
    
        ThreadLocalMap(ThreadLocal<?> firstKey, Object firstValue) {
                table = new Entry[INITIAL_CAPACITY];
                int i = firstKey.threadLocalHashCode & (INITIAL_CAPACITY - 1);
                table[i] = new Entry(firstKey, firstValue);
                size = 1;
                setThreshold(INITIAL_CAPACITY);
            }
    
    
    }
    
    

    为什么使用弱引用?会造成内存泄漏吗?如何避免?多线程?

    1. 假设threadlocal使用强引用,对外部强引用 threadlocalInstance = null 后,如果线程还没结束,由于threadlocalmap的entry还持有threadlocal的强引用,导致threadlocalInstance依然不能被回收,这样就无法真正达到业务逻辑的目的,出现逻辑错误;

    2. 使用弱引用,如果threadLocal外部强引用被置为null(threadLocalInstance=null)的话,因为threadLocalMap.entry只持有threadlocal的弱引用,threadLocal实例就没有一条引用链路可达,很显然在GC (垃圾回收)的时候势必会被回收,因此entry就存在key为null的情况,无法通过一个Key为null去访问到该entry的value。同时,就存在了这样一条引用链:threadRef->currentThread->threadLocalMap->entry->valueRef->valueMemory,导致在垃圾回收的时候进行可达性分析的时候,value可达从而不会被回收掉,但是该value永远不能被访问到,这样就存在了内存泄漏**。当然,如果线程执行结束后,threadLocal,threadRef会断掉,因此threadLocal,threadLocalMap,entry都会被回收掉。可是,在实际使用中我们都是会用线程池去维护我们的线程,比如在Executors.newFixedThreadPool()时创建线程的时候,为了复用线程是不会结束的,所以threadLocal内存泄漏就值得我们关注.

       val sThreadLocal = ThreadLocal<String>()
            val pool = Executors.newSingleThreadExecutor()
            for (i in 0 until 10) {
                if (i == 0) {
                    pool.execute(Runnable {
                       sThreadLocal.set("lijing")
    //                 sThreadLocal.remove()
                    })
                } else {
                    pool.execute(Runnable {
                        Log.d("sThreadLocal", Thread.currentThread().name + sThreadLocal.get())
                    })
                }
    
            }
    

    假设Entry弱引用threadLocal,尽管会出现内存泄漏的问题,但是在threadLocal的生命周期里(set,getEntry,remove)里,都会针对key为null的脏entry进行处理。

    从以上的分析可以看出,使用弱引用的话在threadLocal生命周期里会尽可能的保证不出现内存泄漏的问题,达到安全的状态。

    ThreadLocal最佳实践

    每次使用完ThreadLocal,都调用它的remove()方法,清除数据。
    在使用线程池的情况下,没有及时清理ThreadLocal,不仅是内存泄漏的问题,更严重的是可能导致业务逻辑出现问题。所以,使用ThreadLocal就跟加锁完要解锁一样,用完就清理。
    参考

    相关文章

      网友评论

          本文标题:Handler

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