美文网首页Android开发Android开发经验谈Android开发
对线面试官系列:Android Handler面试连环问题解析,

对线面试官系列:Android Handler面试连环问题解析,

作者: 愿天堂没Android | 来源:发表于2022-03-23 21:21 被阅读0次

    先来一个自己画的Handler机制整体流程图,本文不会带着你走一遍源码,只会对重点需要注意的地方以及一些细节的处理做出解释,让你更好的了解Handler机制整体的运作

    img
    • Handler通过sendMessage()发送Message到MessageQueue队列;
    • Looper通过loop(),不断提取出达到触发条件的Message,并将Message交给target来处理;
    • 经过dispatchMessage()后,交回给Handler的handleMessage()来进行相应地处理。
    • 将Message加入MessageQueue时,处往管道写入字符,可以会唤醒loop线程;
    • 如果MessageQueue中没有Message,并处于Idle状态,则会执行IdelHandler接口中的方法,往往用于做一些清理性地工作。

    下边放几个需要注意的Handler知识点:

    1. Handler 的背后有 Looper、MessageQueue 支撑,Looper 负责消息分发,MessageQueue 负责消息管理;
    2. 在创建 Handler 之前一定需要先创建 Looper;
    3. Looper 有退出的功能,但是主线程的 Looper 不允许退出;
    4. 异步线程的 Looper 需要自己调用 Looper.quit(); 退出;
    5. Runnable 被封装进了 Message,可以说是一个特殊的 Message;
    6. Handler.handleMessage() 所在的线程是 Looper.loop() 方法被调用的线程,也可以说成 Looper 所在的线程,并不是创建 Handler 的线程,Handler新建时持有的Looper在哪个线程,最后Handler.handleMessage()就在哪个线程执行
    7. 使用内部类的方式使用 Handler 可能会导致内存泄露,即便在 Activity.onDestroy 里移除延时消息,必须要写成静态内部类;

    为什么需要使用Handler

    因为Android系统不允许在非UI线程更新UI,因为如果多个线程同时改变View的状态会造成最终View状态的不确定性,如果给每个View的操作都上锁的话那么势必会造成性能的损耗,所以干脆规定只能在UI线程去更新UI,而Handler就是用来进行线程切换操作的。 使用方法

    class LooperThread extends Thread {
        public Handler mHandler;
    
    
        public void run() {
            Looper.prepare();  
            mHandler = new Handler() {  
                public void handleMessage(Message msg) {
                    //TODO 定义消息处理逻辑.
                }
            };
            Looper.loop();  
        }
    }
    

    主线程中可以使用Handler的原因是在ActivityThread中程序的入口main方法中调用了Looper.prepare();和Looper.loop();

    Looper.prepareMainLooper();
    ActivityThread thread = new ActivityThread();
    thread.attach(false);
    if (sMainThreadHandler == null) {
        sMainThreadHandler = thread.getHandler();
    }
    if (false) {
        Looper.myLooper().setMessageLogging(new
                LogPrinter(Log.DEBUG, "ActivityThread"));
    }
    // End of event ActivityThreadMain.
    Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
    Looper.loop();
    

    Looper

    • Looper.prepare() Looper.prepare()在每个线程只允许执行一次,该方法给当前线程通过TL绑定一个线程所属的唯一一个实例。
    private static void prepare(boolean quitAllowed) {
        //看当前线程是否已通过TL绑定对应的实例,有的话抛异常,所以prepare方法只允许调用一次
        if (sThreadLocal.get() != null) {
            throw new RuntimeException("Only one Looper may be created per thread");
        }
        //创建Looper对象,并通过TL建立与线程的绑定关系
        sThreadLocal.set(new Looper(quitAllowed));
    }
    
    • ThreadLocal 我们看一下ThreadLocal.set方法
    public void set(T value) {
       Thread t = Thread.currentThread();//获取当前线程
       ThreadLocalMap map = getMap(t);//获取当前线程所属的ThreadLocalMap实例,键值对结构
       if (map != null)
           map.set(this, value); //以当前ThreadLocal作为键,Looper作为值建立绑定关系
       else
           createMap(t, value);
       }
    }
    

    ThreadLocal.get方法

    public T get() {
        Thread t = Thread.currentThread();//获取当前线程
        ThreadLocalMap map = getMap(t);//获取当前线程所属的ThreadLocalMap实例,键值对结构
        if (map != null) {
            ThreadLocalMap.Entry e = map.getEntry(this);//通过当前ThreadLocal作为键取出对应的值
            if (e != null) {
                @SuppressWarnings("unchecked")
                T result = (T)e.value;
                return result;
            }
        }
        return setInitialValue();
    }
    

    为什么要选择ThreadLocal建立绑定关系?

    因为我们是要让每一个线程都有且只有一个唯一的Looper实例,这时就可以使用ThreadLocal给每个线程绑定一个唯一实例的特性很方便的建立绑定关系。如果不采用ThreadLocal去实现,那么只能使用一个LooperManager管理类然后通过其中的Map去统一管理,那么这样无疑是很麻烦的。ThreadLocal只是作为主键,如果是Thread作为主键,那么很显然一个线程只能与一个对应的对象建立绑定关系,这显然是非常不合理的。

    • Looper.loop() loop()进入循环模式,主要进行了
      如下几点
      :
    1. 获取当前线程的Looper实例
    2. 通过Looper获取MessageQueue实例
    3. 开启死循环并在其中调用MessageQueue的next方法不断轮询MessageQueue的头结点
    public static void loop() {
        final Looper me = myLooper();  //获取TLS存储的Looper对象 -->sThreadLocal.get()
        final MessageQueue queue = me.mQueue;  //获取Looper对象中的消息队列
    
    
        Binder.clearCallingIdentity();
        //确保在权限检查时基于本地进程,而不是调用进程。
        final long ident = Binder.clearCallingIdentity();
    
    
        for (;;) { //进入loop的主循环方法
            Message msg = queue.next(); //可能会阻塞 
            if (msg == null) { //没有消息则退出循环,调用Looper.quit()方法后返回空的message,随即退出
                return;
            }
    
            Printer logging = me.mLogging;
            if (logging != null) {
                logging.println(">>>>> Dispatching to " + msg.target + " " +
                        msg.callback + ": " + msg.what);
            }
            msg.target.dispatchMessage(msg); //用于分发Message 
            if (logging != null) {
                logging.println("<<<<< Finished to " + msg.target + " " + msg.callback);
            }
    
            final long newIdent = Binder.clearCallingIdentity();
            msg.recycleUnchecked(); 
        }
    }
    
    • Looper.quit() 用于终止loop循环,主线程中不允许调用(能调用的话相当于主程序退出了,应用就直接挂掉了),子线程中要退出时需要主动调用,否则会造成子线程中一直处于死循环状态无法退出。调用后会改变MessageQueue中的mQuitting标志位,next方法中如果检测到mQuitting为true则直接返回null,loop方法中检测到message是null则直接return终止死循环从而结束逻辑使得线程可以退出。
    public void quit() {
        mQueue.quit(false); //全部消息移除
    }
    
    
    public void quitSafely() {
        mQueue.quit(true); //只移除没有执行的消息
    }
    

    如何做到延迟发送消息

    • 会根据Message发送时的时间戳确定Message在MessageQueue中的位置。
    1. 放入Message时会根据msg.when这个时间戳进行顺序的排序,如果非延迟消息则msg.when为系统当前时间,延迟消息则为系统当前时间+延迟时间(如延迟发送3秒则为SystemClock.uptimeMillis() + 3000)
    2. 将Message放入MessageQueue时会以msg.when对msg进行排序确认当前msg处于单链表中的位置,分为几种情况: (1)头结点为null(代表MessageQueue没有消息),Message直接放入头结点。 (2) 头结点不为null时开启死循环遍历所有节点,退出死循环的条件是: 1.遍历出的节点的next节点为null(说明当前链表已经遍历到了末尾,将放入的Message放入next节点). 2.遍历出的节点的when大于放入message的when(说明当前message是一个比放入message延迟更久的消息,将放入的Message放入当前遍历的Message节点之前).
    • 当我们发送消息的时候其实最后都会调用到sendMessageAtTime这个方法,这个方法其实最终会把你的Handler对象赋值给Message实体,我们最终发送消息都是发送的Message实体,然后调用MessageQueue的enqueueMessage方法
    public final boolean sendMessageDelayed(Message msg, long delayMillis)
    {
        if (delayMillis < 0) {
            delayMillis = 0;
        }
        return sendMessageAtTime(msg, SystemClock.uptimeMillis() + delayMillis);
    }
    
    public boolean sendMessageAtTime(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);
    }
    
    private boolean enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis) {
        msg.target = this;
        if (mAsynchronous) {
            msg.setAsynchronous(true);
        }
        return queue.enqueueMessage(msg, uptimeMillis);
    }
    
    • MessageQueue 是一个单链表结构,其中的Message节点是以Message放入MessageQueue的时间去进行顺序确定的(小的在前大的再后),这样就完成了消息的延迟发送
    boolean enqueueMessage(Message msg, long when) {
        // 每一个普通Message必须有一个target
        if (msg.target == null) {
            throw new IllegalArgumentException("Message must have a target.");
        }
        if (msg.isInUse()) {
            throw new IllegalStateException(msg + " This message is already in use.");
        }
        synchronized (this) {
            if (mQuitting) {  //正在退出时,回收msg,加入到消息池
                msg.recycle();
                return false;
            }
            msg.markInUse();
            msg.when = when;
            Message p = mMessages;
            boolean needWake;
            if (p == null || when == 0 || when < p.when) { //p为null(代表MessageQueue没有消息) 或者msg的触发时间是队列中最早的, 则进入该分支并将加入的message放入头结点
                msg.next = p;
                mMessages = msg;
                needWake = mBlocked; //当阻塞时需要唤醒
            } else {
                needWake = mBlocked && p.target == null && msg.isAsynchronous();
                Message prev;
                for (;;) {                             //开启死循环遍历message
                    prev = p;
                    p = p.next;
                    if (p == null || when < p.when) {  //退出条件为当前message的下一个节点为null或者当前节点的message执行时间大于你放入message的执行时间
                        break;
                    }
                    if (needWake && p.isAsynchronous()) {
                        needWake = false;
                    }
                }
                msg.next = p;                           //进行赋值
                prev.next = msg;
            }
            //消息没有退出,我们认为此时mPtr != 0
            if (needWake) {                        
                nativeWake(mPtr);                        //往管道中写数据,唤醒阻塞,nativePollOnce方法阻塞解除
            }
        }
        return true;
    }
    

    MessageQueue的机制

    • Message的入列和出列其实是一个很典型的**生产者-消费者模型**,其中使用了epoll机制,当没有消息的时候会进行阻塞释放CPU时间片避免死循环造成性能的浪费。虽然是不断循环取出头结点的Message进行分发处理但是如果没有消息时它是阻塞在 nativePollOnce这个native方法中的当我们enqueue插入Message时会触发nativeWake这个方法去唤醒,从而nativePollOnce阻塞解除继续遍历MessageQueue取出头结点去处理。
    • Looper.loop()在一个线程中调用next()不断的取出消息,另外一个线程则通过enqueueMessage向队列中插入消息,所以在这两个方法中使用了synchronized (this) {}同步机制,其中this为MessageQueue对象,不管在哪个线程,这个对象都是同一个,因为Handler中的mQueue指向的是Looper中的mQueue,这样防止了多个线程对同一个队列的同时操作(如增加的同时正在轮询获取Message,有可能造成MessageQueue中最终结果的不确定性)。
    Message next() {
        final long ptr = mPtr;
        if (ptr == 0) { //当消息循环已经退出,则直接返回
            return null;
        }
        int pendingIdleHandlerCount = -1; // 循环迭代的首次为-1
        int nextPollTimeoutMillis = 0;
        for (;;) {
            if (nextPollTimeoutMillis != 0) {
                Binder.flushPendingCommands();
            }
            //阻塞操作,当等待nextPollTimeoutMillis时长,或者消息队列被唤醒,都会返回
            //nextPollTimeoutMillis 为-1,一直阻塞,在调用nativeWake(enqueue Message或Looper.quit()退出Looper)时会被唤醒解除阻塞
            //nextPollTimeoutMillis 为0,不阻塞
            //nextPollTimeoutMillis 为>0,阻塞到对应时间后解除,如为10000则阻塞十秒后解除,用于处理延迟消息
            nativePollOnce(ptr, nextPollTimeoutMillis);
            synchronized (this) {
                final long now = SystemClock.uptimeMillis();
                Message prevMsg = null;
                Message msg = mMessages;
                if (msg != null && msg.target == null) {
                    //当消息Handler为空时,查询MessageQueue中的下一条异步消息msg,则退出循环。
                    do {
                        prevMsg = msg;
                        msg = msg.next;
                    } while (msg != null && !msg.isAsynchronous());
                }
                if (msg != null) {
                    if (now < msg.when) {                    
                        //说明是延迟消息,计算延迟的时间
                        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;
                        //设置消息的使用状态,即flags |= FLAG_IN_USE
                        msg.markInUse();
                        return msg;   //成功地获取MessageQueue中的下一条即将要执行的消息
                    }
                } else {
                    //没有消息,阻塞
                    nextPollTimeoutMillis = -1;
                }
                //消息正在退出,返回null
                if (mQuitting) {
                    dispose();
                    return null;
                }
                //当消息
    

    Message分发的三个的优先级

    当遍历出Message后Message会获取其中的Handler并调用Handler的dispatchMessage进行分发,这时也会有三个优先级。

    1. Message的回调方法:message.callback.run(),优先级最高; 对应handler.post(new Runnable)的方式发送消息
    2. Handler的回调方法:Handler.mCallback.handleMessage(msg),优先级仅次于1; 对应新建Handler时传进CallBack接口 Handler handler=new Handler(new Handler.Callback()....(通常我们可以利用 Callback 这个拦截机制来拦截 Handler 的消息,场景如:Hook ActivityThread.mH,在 ActivityThread 中有个成员变量 mH ,它是个 Handler,又是个极其重要的类,几乎所有的插件化框架都使用了这个方法。)
    3. Handler的默认方法:Handler.handleMessage(msg),优先级最低。 对应新建Handler并复写handleMessage方法
    public void dispatchMessage(Message msg) {
        if (msg.callback != null) {             //callback是一个runnable对象
            handleCallback(msg);
        } else {
            if (mCallback != null) {            //mCallback是新建Handler时传进去的Callback接口
                if (mCallback.handleMessage(msg)) {
                    return;
                }
            }
            handleMessage(msg);                //默认空实现,一般我们会自己复写实现这个方法
        }
    }
    
    private static void handleCallback(Message message) {
        message.callback.run();
    }
    
    1. 将 Runnable post 到主线程执行(很多第三方框架都使用的这种方式方便的完成主线程的切换,这也是为什么有handler.post(new Runnable)这种方式去发送消息)。
    2. 利用 Looper 判断当前线程是否是主线程。
    public final class MainThread {
        private MainThread() {
        }
    
        private static final Handler HANDLER = new Handler(Looper.getMainLooper());
    
        public static void run(@NonNull Runnable runnable) {
            if (isMainThread()) {
               runnable.run();
            }else{
                HANDLER.post(runnable);
            }
        }
    
        public static boolean isMainThread() {
            return Looper.myLooper() == Looper.getMainLooper();
        }
    
    }
    

    Handler线程调度的实质

    Handler的实质其实就是共享内存,我们看一个例子。

    public class Demo {
        List mList= new ArrayList()<Message>;
        public static void main(String[] args) {
            //子线程
            new Thread(new Runnable() {
                @Override
                public void run() {
                    try {
                        Thread.sleep(2000l);
                        mList.add(new Message());
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
    
    
                }
            }).start();
            //主线程开启死循环不断遍历list取头结点
            for (;;) {
                //主线程中处理
                Message message = mList.get(0);
                if (message != null) {
                    //处理
                }
            }
    
        }
    }
    

    我们为了将数据最终从子线程切换到主线程中去其实只要拿到mList这个实例即可,这个mList对应的其实就是MessageQueue,而我们要获取MessageQueue只要获取对应的Looper即可,当我们Handler新建的时会根据Handler所在线程获取到其线程正在轮询消息的Looper对象,Handler中的mQueue指向的是其所在线程的Looper中的mQueue(当然也可以手动指定一个其他线程的Looper,不指定的话默认为当前线程的Looper),由此便可在发送Message时将任务放到Looper所在线程中处理。

    public Handler(Callback callback, boolean async) {
        ...
        mLooper = Looper.myLooper(); //threadLocal.get获取线程对应的Looper
        if (mLooper == null) {
            throw new RuntimeException(
                "Can't create handler inside thread that has not called Looper.prepare()");
        }
        mQueue = mLooper.mQueue;     //通过Looper获取MessageQueue
        mCallback = callback;
        mAsynchronous = async;
    }
    

    Looper.loop是一个死循环,拿不到需要处理的Message就会阻塞,那在UI线程中为什么不会导致ANR?

    首先我们来看造成ANR的原因:

    1. 当前的事件没有机会得到处理(即主线程正在处理前一个事件,没有及时的完成或者looper被某种原因阻塞住了)。
    2. 当前的事件正在处理,但没有及时完成。

    我们再来看一下APP的入口ActivityThread的main方法:

    public static void main(String[] args) {
      
            ...
    
            Looper.prepareMainLooper();
    
            ActivityThread thread = new ActivityThread();
            thread.attach(false);
    
            if (sMainThreadHandler == null) {
                sMainThreadHandler = thread.getHandler();
            }
    
            Looper.loop();
    
            throw new RuntimeException("Main thread loop unexpectedly exited");
        }
    

    我们知道Android 的是由事件驱动的,looper.loop() 不断地接收事件、处理事件,每一个点击触摸或者说Activity的生命周期都是运行在 Looper的控制之下,如果它停止了,应用也就停止了。真正的阻塞是因为轮询出message后在处理message消息的时候由于执行了耗时操作导致了ANR,而不是死循环导致的阻塞,没有消息处理的时候消息队列是阻塞在nativePollOnce方法中的,**这个方法使用的是epoll管道机制,Linux底层执行后会释放CPU避免不断死循环造成的CPU浪费。**

    Handler 引起的内存泄露原因以及最佳解决方案

    当我们用Handler发送延时消息时,如果在延时期间用户关闭了 Activity,那么该 Activity 会泄露。这个泄露是因为 Message 会持有 Handler,而又因为 Java 的特性,内部类会持有外部类,使得 Activity 会被 Handler 持有,这样最终就导致 Activity 泄露。 解决该问题的最有效的方法是:将 Handler 定义成静态的内部类(静态内部类不会持有外部类引用,但是静态内部类调用不到外部类的非静态属性和方法,所以我们需要在内部类中使用弱引用持有Activity,使用弱引用调用到Activity中的方法),并及时移除所有消息。 泄漏时的引用链 Activity->Handler->Message->MessageQueue ,延迟消息会一直在MessageQueue中等待处理,在等待的过程中有可能会造成内存泄漏。 示例代码如下:

    private static class SafeHandler extends Handler {
    
        private WeakReference<HandlerActivity> ref;
    
        public SafeHandler(HandlerActivity activity) {
            this.ref = new WeakReference(activity);
        }
    
        @Override
        public void handleMessage(final Message msg) {
            HandlerActivity activity = ref.get();
            if (activity != null) {
                activity.handleMessage(msg);
            }
        }
    }
    

    并且再在 Activity.onDestroy() 前移除消息,加一层保障:

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

    这样双重保障,就能完全避免内存泄露了。 注意:单纯的在 onDestroy 移除消息并不保险,因为 onDestroy 并不一定执行(如报异常)。

    IdleHandler 是什么

    主要是为了去充分的利用阻塞时间,让原先处于阻塞的这段时间不进行阻塞而是去执行IdleHandler中的任务。

    从下边MessageQueue的源码可知道,IdleHandler即在MessageQueue应该被阻塞之前去调用(当然前提是你要讲自定义的IdleHandler加入到集合中)IdleHandler接口表示当MessageQueue发现当前没有更多消息可以处理的时候, 则顺便干点别的事情的callback函数(即如果发现idle了, 那就找点别的事干). callback函数有个boolean的返回值, 表示是否keep. 如果返回false, 则它会在调用完毕之后从mIdleHandlers中移除. IdleHandler 可以用来提升提升性能,主要用在我们希望能够在当前线程消息队列空闲时做些事情(譬如UI线程在显示完成后,如果线程空闲我们就可以提前准备其他内容)的情况下,**不过最好不要做耗时操作**

    Message next() {
        final long ptr = mPtr;
        if (ptr == 0) { //当消息循环已经退出,则直接返回
            return null;
        }
        int pendingIdleHandlerCount = -1; // 循环迭代的首次为-1
        int nextPollTimeoutMillis = 0;
        for (;;) {
            if (nextPollTimeoutMillis != 0) {
                Binder.flushPendingCommands();
            }
            //阻塞操作,当等待nextPollTimeoutMillis时长,或者消息队列被唤醒,都会返回
            //nextPollTimeoutMillis 为-1,一直阻塞,在调用nativeWake(enqueue Message或Looper.quit()退出Looper)时会被唤醒解除阻塞
            //nextPollTimeoutMillis 为0,不阻塞
            //nextPollTimeoutMillis 为>0,阻塞到对应时间后解除,如为10000则阻塞十秒后解除,用于处理延迟消息
            nativePollOnce(ptr, nextPollTimeoutMillis);
            synchronized (this) {
                final long now = SystemClock.uptimeMillis();
                Message prevMsg = null;
                Message msg = mMessages;
                if (msg != null && msg.target == null) {
                    //当消息Handler为空时,查询MessageQueue中的下一条异步消息msg,则退出循环。
                    do {
                        prevMsg = msg;
                        msg = msg.next;
                    } while (msg != null && !msg.isAsynchronous());
                }
                if (msg != null) {
                    if (now < msg.when) {                    
                        //说明是延迟消息,计算延迟的时间
                        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;
                        //设置消息的使用状态,即flags |= FLAG_IN_USE
                        msg.markInUse();
                        return msg;   //成功地获取MessageQueue中的下一条即将要执行的消息
                    }
                } else {
                    //没有消息,阻塞
                    nextPollTimeoutMillis = -1;
                }
                //消息正在退出,返回null
                if (mQuitting) {
                    dispose();
                    return null;
                }
                //如果当前MessageQueue头结点为空(没有消息要处理了)或者当前系统时间<消息触发时间
                if (pendingIdleHandlerCount < 0 && (mMessages == null || now < mMessages.when)) {
                //看是否加入了idleHandler
                    pendingIdleHandlerCount = mIdleHandlers.size();
                }
                if (pendingIdleHandlerCount <= 0) {
                    //没有idle handlers 需要运行,则循环并等待。
                    mBlocked = true;
                    continue;
                }
                if (mPendingIdleHandlers == null) {
                    mPendingIdleHandlers = new IdleHandler[Math.max(pendingIdleHandlerCount, 4)];
                }
                mPendingIdleHandlers = mIdleHandlers.toArray(mPendingIdleHandlers);
            }
            //只有第一次循环时,会运行idle handlers,执行完成后,重置pendingIdleHandlerCount为0.
            for (int i = 0; i < pendingIdleHandlerCount; i++) {
                final IdleHandler idler = mPendingIdleHandlers[i];
                mPendingIdleHandlers[i] = null; //去掉handler的引用
                boolean keep = false;
                try {
                    //如果queueIdle()返回false则当前idlehandler只能运行一次
                    keep = idler.queueIdle();  //idle时执行的方法
                } catch (Throwable t) {
                    Log.wtf(TAG, "IdleHandler threw exception", t);
                }
                if (!keep) {
                    synchronized (this) {
                        //queueIdle返回false,移除idlehandler
                        mIdleHandlers.remove(idler);
                    }
                }
            }
            //重置idle handler个数为0,以保证不会再次重复运行
            pendingIdleHandlerCount = 0;
            //当调用一个空闲handler时,一个新message能够被分发,因此无需等待可以直接查询pending message.
            nextPollTimeoutMillis = 0;
        }
    }
    

    IdleHandler在FrameWork中的使用

    ActivityThread.GcIdler 是在内存不足时,强行 GC;比如在进程进入后台时执行GC

    final GcIdler mGcIdler = new GcIdler();
    
    public void processInBackground() {
        mH.removeMessages(H.GC_WHEN_IDLE);
        mH.sendMessage(mH.obtainMessage(H.GC_WHEN_IDLE));
    }
    /...
    case GC_WHEN_IDLE:
        scheduleGcIdler();
        break;
    
    void scheduleGcIdler() {
        if (!mGcIdlerScheduled) {
            mGcIdlerScheduled = true;
            Looper.myQueue().addIdleHandler(mGcIdler);
        }
        mH.removeMessages(H.GC_WHEN_IDLE);
    }
    void unscheduleGcIdler() {
        if (mGcIdlerScheduled) {
            mGcIdlerScheduled = false;
            Looper.myQueue().removeIdleHandler(mGcIdler);
        }
        mH.removeMessages(H.GC_WHEN_IDLE);
    }
    
    final class GcIdler implements MessageQueue.IdleHandler {
        @Override
        public final boolean queueIdle() {
            doGcIfNeeded();
            return false;
        }
    }
    
    void doGcIfNeeded() {
        mGcIdlerScheduled = false;
        final long now = SystemClock.uptimeMillis();
        //Slog.i(TAG, "**** WE MIGHT WANT TO GC: then=" + Binder.getLastGcTime()
        //        + "m now=" + now);
        if ((BinderInternal.getLastGcTime()+MIN_TIME_BETWEEN_GCS) < now) {
            //Slog.i(TAG, "**** WE DO, WE DO WANT TO GC!");
            BinderInternal.forceGc("bg");
        }
    }
    

    通过IdleHandler优化页面启动速度

    参考链接:yq.aliyun.com/articles/66…

    思路:在Activity启动时会执行到handleResumeActivity这个Message中,在这个消息中首先会执行OnResume,接着会将DecorView添加到WindowManager中从而创建出ViewRootImpl,最后ViewRootImpl执行performTraversals开始View的测绘。从这里可以看出如果我们在OnResume中进行一些操作的话是会影响Activity启动速度的,所以我们可以通过IdleHandler将原先在OnResume中执行的任务放到IdleHandler中去处理从而加快页面展示。

    @Override
    protected void onResume() {
        super.onResume();
        Looper.myQueue().addIdleHandler(() -> {
            initializeData();
            return false;
        });
        Looper.myQueue().addIdleHandler(() -> {
            resumeData();
            return true;
        });
    }
    
    
    img

    同步屏障

    同步屏障是由系统发送,一般用于刷新UI(如16ms刷新一次界面)。当设置了同步屏障之后,next函数将会忽略所有的同步消息,返回异步消息。换句话说就是,设置了同步屏障之后,Handler只会处理异步消息。再换句话说,同步屏障为Handler消息机制增加了一种简单的优先级机制,异步消息的优先级要高于同步消息。

    • 如何判断是否为同步屏障消息?****当Message的target为null时(Message不持有Handler)则当前消息为异步消息,也就是同步屏障。

    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();
        }
    }
    

    作者:lvzishen
    转载来源于:https://juejin.cn/post/6844903795445465095
    如有侵权,请联系删除!

    相关文章

      网友评论

        本文标题:对线面试官系列:Android Handler面试连环问题解析,

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