美文网首页
关于Handler的面试专题

关于Handler的面试专题

作者: 天上飘的是浮云 | 来源:发表于2022-02-02 17:00 被阅读0次

    Handler老生常谈,总感觉会用,搞清楚了,但是呢,又总感觉缺了下什么,记录下。好记性不如烂键盘。

    一、Handler源码吃透

    首先,我们需要确定前提的是一个Thread线程只有一个Looper,一个MessageQueue,多个Handler对象。

    Handler机制的整体架构类似于一个传送带装置。


    Handler架构图.png
    • Handler:类似于给传送带放置货物(sendMessage()),从传送带上取货物(处理货物)(dispatchMessage())
    • Message:传送带上的货物
    • Looper:使传送带循环转起来的电机(Looper.loop())
    • MessageQueue:传送带
    1.1 Handler

    在一个线程中可以有很多个Handler,它们都可以发送Message,发送的的话,有很多的sendMessage方法,最终是由MessageQueue.enqueueMessage处理。并在处理时,由他们各自的handleMessage()函数处理。

        public void dispatchMessage(@NonNull Message msg) {
            if (msg.callback != null) {
                handleCallback(msg);
            } else {
                if (mCallback != null) {
                    if (mCallback.handleMessage(msg)) {
                        return;
                    }
                }
                handleMessage(msg);
            }
        }
    
     private boolean enqueueMessage(@NonNull MessageQueue queue, @NonNull Message msg,
                long uptimeMillis) {
            msg.target = this;
            msg.workSourceUid = ThreadLocalWorkSource.getUid();
    
            if (mAsynchronous) {
                msg.setAsynchronous(true);
            }
            return queue.enqueueMessage(msg, uptimeMillis);
        }
    
    1.2 Message
    • Message是一个单向链表结构的实体类,保持了很多信息。
    • 它有一个next变量指向下一个MessageZ节点,并保持着一个缓存池。当被释放时,调用它的recycle()函数。但是它并不会被释放,而是缓存起来,等待下一次调用obtain()方法时,重新赋值使用。
    • 另外,它的target变量将会持有外部Handler,这也是内存泄漏的主要因素。
        public void recycle() {
            ...
            recycleUnchecked();
        }
    
      void recycleUnchecked() {
            what = 0;
           ...
            data = null;
    
            synchronized (sPoolSync) {
                if (sPoolSize < MAX_POOL_SIZE) {
                    next = sPool;
                    sPool = this;
                    sPoolSize++;
                }
            }
        }
    
        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();
        }
    
    1.3 MessageQueue

    它通过enqueueMessage()方法来将Message对象按时间when的先后加入链表中,通过next()方法从链表中取出Message。它始终有一个mMessage指向链表头结点。

    boolean enqueueMessage(Message msg, long when) {
            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) {
                    IllegalStateException e = new IllegalStateException(
                            msg.target + " sending message to a Handler on a dead thread");
                    Log.w(TAG, e.getMessage(), e);
                    msg.recycle();
                    return false;
                }
    
                msg.markInUse();
                msg.when = when;
                Message p = mMessages;
                boolean needWake;
                if (p == null || when == 0 || when < p.when) {
                    // New head, wake up the event queue if blocked.
                    msg.next = p;
                    mMessages = msg;
                    needWake = mBlocked;
                } else {
                    // Inserted within the middle of the queue.  Usually we don't have to wake
                    // up the event queue unless there is a barrier at the head of the queue
                    // and the message is the earliest asynchronous message in the queue.
                    needWake = mBlocked && p.target == null && msg.isAsynchronous();
                    Message prev;
                    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;
                }
    
                // We can assume mPtr != 0 because mQuitting is false.
                if (needWake) {
                    nativeWake(mPtr);
                }
            }
            return true;
        }
    
    1.4 Looper

    每个Thread线程只有一个Looper对象(它是有ThreadLocal来保证的。),而每个Looper对象也只有一个MessageQueue。

    • Looper.prepareMainLooper()来创建主线程的Looper对象
        public static void prepareMainLooper() {
            prepare(false);
            synchronized (Looper.class) {
                if (sMainLooper != null) {
                    throw new IllegalStateException("The main Looper has already been prepared.");
                }
                sMainLooper = myLooper();
            }
        }
    
    • Looper.prepare()创建子线程中的Looper对象,先从ThreadLocal中获取,有就抛出异常,一个线程只能创建一个。没有就会实例化一个Looper对象,并set到ThreadLocal中。
      private static void prepare(boolean quitAllowed) {
            if (sThreadLocal.get() != null) {
                throw new RuntimeException("Only one Looper may be created per thread");
            }
            sThreadLocal.set(new Looper(quitAllowed));
        }
    
    • Looper.loop()开启死循环来,不断遍历MessageQueue看是否有消息,有就通过msg.target(实际就是Handler)来dispatchMessage()处理消息,最后回收Message。
        public static void loop() {
            final Looper me = myLooper();
            ...
            final MessageQueue queue = me.mQueue;
            ...
            for (;;) {
                Message msg = queue.next(); 
                ...
                try {
                    msg.target.dispatchMessage(msg);
                   ...
                } catch (Exception exception) {
                   ...
                }
    
                msg.recycleUnchecked();
            }
        }
    

    二、Looper死循环为什么不会导致应用卡死?

    基于前面我们已经知道了,App的启动流程,最终会Zygote进程孵化出App进程。而ActivityThread就是App的进程所在,在它的main()方法中,实例化了一个主线程的Looper对象,并将其存储在主线程的ThreadLocal.ThreadLocalMap中。
    我们知道,如果main()方法执行完,那么意味着进程就结束了。但是这是App进程,我们想要它结束吗?Android并不想让App进程退出,所以这里Looper.loop()死循环阻塞也是这个作用,保持App进程。
    而Android是以事件为驱动的系统,但没有事件来时,就应该去展示静态的界面。

        public static void main(String[] args) {
            ...
            Looper.prepareMainLooper();
            ....
            Looper.loop();
        }
    

    三、使用Handler的postDelay消息队列有什么变化?

      1. Hanlder的postDelayed()有三个重载方法,它们都会去调用sendMessageDelayed()方法。
        public final boolean postDelayed(@NonNull Runnable r, long delayMillis) {
            return sendMessageDelayed(getPostMessage(r), delayMillis);
        }
    
      1. 在sendMessageDelayed()它将延迟时间与系统启动时间相加,作为一个消息需要处理的时间。通过这个时间值插入到MessageQueue中。
    public final boolean sendMessageDelayed(@NonNull Message msg, long delayMillis) {
            if (delayMillis < 0) {
                delayMillis = 0;
            }
            return sendMessageAtTime(msg, SystemClock.uptimeMillis() + delayMillis);
        }
    

    四、如何保证多个Handler线程安全?

    一个线程只有一个Looper和一个MessageQueue实例,可以有多个Handler对象。在MessageQueue中像enqueueMessage()、next()方法都加了synchronized(this)同步锁,保证线程安全。

    五、Message如何创建?哪种方式更好?

      1. 使用new Message()方法创建
      1. 使用Message.obtain()方式创建
        我们知道在系统中可能会发送无数个Message,使用第一种方式的话会导致频繁创建和频繁销毁,这将导致内存抖动。所以Handler中推进使用第二种方式来创建,它优先使用缓冲池中的Message,如果缓冲池没有可用的才回去实例化一个message。但Message对象被Handler处理完后,不是直接销毁,而是将其重置,并缓存到缓存池中,以供下一次使用。
        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();
        }
    
        void recycleUnchecked() {
            // Mark the message as in use while it remains in the recycled object pool.
            // Clear out all other details.
            flags = FLAG_IN_USE;
            what = 0;
            arg1 = 0;
            arg2 = 0;
            obj = null;
            replyTo = null;
            sendingUid = UID_NONE;
            workSourceUid = UID_NONE;
            when = 0;
            target = null;
            callback = null;
            data = null;
    
            synchronized (sPoolSync) {
                if (sPoolSize < MAX_POOL_SIZE) {
                    next = sPool;
                    sPool = this;
                    sPoolSize++;
                }
            }
        }
    

    六、关于ThreadLocal,你的理解是?

      1. ThreadLocal在Handler体系中用来保证线程中只有一个Looper对象,在调用Looper.prepare()方法时,会先从sThreadLocal.get()中获取,如果能够获取到,会抛出异常,获取不到才会实例化一个Looper对象,并通过ThreadLocal.set()方法保存起来。
        private static void prepare(boolean quitAllowed) {
            if (sThreadLocal.get() != null) {
                throw new RuntimeException("Only one Looper may be created per thread");
            }
            sThreadLocal.set(new Looper(quitAllowed));
        }
    
      1. ThreadLocal这种机制原理,并不是ThreadLocal自己实现的,而是通过它的内部类ThreadLocalMap来实现,它是一个Map,以ThreadLocal为Key,泛型(T)为值。这里就是Looper对象为值了。
    public class ThreadLocal<T> {
          static class ThreadLocalMap {
            static class Entry extends WeakReference<ThreadLocal<?>> {
                Entry(ThreadLocal<?> k, Object v) {
                    super(k);
                    value = v;
                }
            }
    
            private Entry[] table;
      }
    }
    
      1. 而每个Thread线程中都会有一个ThreadLocal.ThreadLocalMap的变量,所以当调用ThreadLocal.get()方法时,都会去获取当前线程,并获取它的ThreadLocal.ThreadLocalMap对象,再从中以ThreadLocal为key来获取Looper
    Thread.java
    class Thread implements Runnable {
      ThreadLocal.ThreadLocalMap threadLocals = null;
    }
    
    ThreadLocal.java
    public class ThreadLocal<T> {
       public T get() {
            Thread t = Thread.currentThread();
            ThreadLocalMap map = getMap(t);
            if (map != null) {
                ThreadLocalMap.Entry e = map.getEntry(this);
                if (e != null) {
                    T result = (T)e.value;
                    return result;
                }
            }
            return setInitialValue();
        }
    
        public void set(T value) {
            Thread t = Thread.currentThread();
            ThreadLocalMap map = getMap(t);
            if (map != null)
                map.set(this, value);
            else
                createMap(t, value);
        }
    }
    

    七、为什么不能在子线程更新UI?

    举个例子:TextView.setText() --> TextView.checkForRelayout() ---> View.requestLayout() ---> ViewRootImpl.requestLayout() ---> ViewRootImpl.checkThread()
    每次都需要检测当前线程是否是主线程,主线程至于是哪里?什么时间设置的需要另外追踪?

    ViewRootImpl.java
       void checkThread() {
            if (mThread != Thread.currentThread()) {
                throw new CalledFromWrongThreadException(
                        "Only the original thread that created a view hierarchy can touch its views.");
            }
        }
    

    解惑:
    1、为什么子线程不能操作UI

    当一个线程第一次启动时,Android同时会启动一个对应的主线程,这个主线程就是UI线程(ActivityThread)。UI线程负责处理和UI相关的事件,如用户按键点击、屏幕触摸等。系统不会为每个组件都单独创建一个线程,在同一进程中UI组件都会在UI线程中实例化。系统对每个组件调用都是从UI线程分发出去的。所以响应系统回调的方法永远都是在UI线程里运行。如onKeyDown()的回调

    1. 那为什么选择一个主线程干这些活呢?换个说法,Android为什么使用单线程模型,它有什么好处?

    现代GUI线程就是使用了单线程模型(采用一个专门的线程从队里中抽取事件,并把它们转发给应用程序定义的事件处理器)。单线程化不单单存在于Android中,Qt、XWindows都是单线程化。当然,也有人试图用多线程的GUI,最终由于竞争条件和死锁导致的稳定性问题等。又回到了单线程化的事件队列模型上来。单线程模型通过限制来达到线程安全。

    1. 在子线程中更新UI抛出异常的原因?(ViewRootImpl构造方法会初始化ViewRoot的mThread,更新Ui时会对比mThread和Thread.currentThread())
    ViewRootImpl.java
    public ViewRootImpl(Context context, Display display) {
        ...
        mThread = Thread.currentThread();
        ......
    }
    
       void checkThread() {
            if (mThread != Thread.currentThread()) {
                throw new CalledFromWrongThreadException(
                        "Only the original thread that created a view hierarchy can touch its views.");
            }
        }
    
    1. 非UI线程是可以刷新UI的,前提是它要拥有自己的ViewRootImpl。可以通过WindowManager.addView()来实现,在WindowManagerImpl.addView() ---> WindowManagerGlobal.addView()内部将会实例化ViewRootImpl
    class NonUiThread extends Thread{
          @Override
          public void run() {
             Looper.prepare();
             TextView tx = new TextView(MainActivity.this);
             tx.setText("non-UiThread update textview");
     
             WindowManager windowManager = MainActivity.this.getWindowManager();
             WindowManager.LayoutParams params = new WindowManager.LayoutParams(
                 200, 200, 200, 200, WindowManager.LayoutParams.FIRST_SUB_WINDOW,
                     WindowManager.LayoutParams.TYPE_TOAST,PixelFormat.OPAQUE);
             windowManager.addView(tx, params); 
             Looper.loop();
         }
     }
    
    1. 那么主线程什么时候创建的ViewRootImpl呢?本把主线程赋值的?实在onResume()的时候,对应到ActivityThread.handleResumeActivity()方法。
    ActivityThread.java
    final void handleResumeActivity(IBinder token,
                boolean clearHide, boolean isForward, boolean reallyResume) {
           ...
            ActivityClientRecord r = performResumeActivity(token, clearHide);
            ......
            if (r.window == null && !a.mFinished && willBeVisible) {
                r.window = r.activity.getWindow();
                View decor = r.window.getDecorView();
                decor.setVisibility(View.INVISIBLE);
                ViewManager wm = a.getWindowManager();
                WindowManager.LayoutParams l = r.window.getAttributes();
                a.mDecor = decor;
                l.type = WindowManager.LayoutParams.TYPE_BASE_APPLICATION;
                l.softInputMode |= forwardBit;
                if (a.mVisibleFromClient) {
                    a.mWindowAdded = true;
                    wm.addView(decor, l);
                }
        ......
    }
    

    八、子线程中维护Looper在消息队列无消息的时候处理方案?

    子线程中我们创建了Handler,并调用了Looper.prepare()和loop()。一旦调用loop()就开启了阻塞。如果消息队列无消息时,子线程仍然会阻塞,只有我们手动调用了子线程中Looper.quit()后才会解除阻塞,往后执行。还可以释放Message内存。

    new Thread(){
          public void run() {
            Looper.prepare();
            new Handler(){
              ...
            }
            Looper.loop();
        }
    }
    
      1. 看到loop()函数中只有msg==null的时候才会return,解除阻塞。
    Looper.java
    public static void loop() {
            for (;;) {
                Message msg = queue.next(); // might block
                if (msg == null) {
                    // No message indicates that the message queue is quitting.
                    return;
                }
                ...
          }  
    }
    
      1. 当消息队列无消息时,nativePollOnce()会阻塞等待下一个消息到来,如果调用了quit()就会唤醒return null。
      Message next() {
            ...
            for (;;) {
                ...
                nativePollOnce(ptr, nextPollTimeoutMillis);
                synchronized (this) {
                    ...
                     if (mQuitting) {
                        dispose();
                        return null;
                    }
                    ...
              }
          }
    }
    
      1. 当我们手动调用子线程Looper.quit()时,它调用的是MessageQueue.quit()。首先,主线程的Looper不允许退出;其次,它会移除和释放MessageQueue中的message内存;另外注意到nativeWake它将通知唤醒MesageQueue.next()的阻塞,往后执行,返回null。
        void quit(boolean safe) {
            if (!mQuitAllowed) {
                throw new IllegalStateException("Main thread not allowed to quit.");
            }
    
            synchronized (this) {
               ...
                if (safe) {
                    removeAllFutureMessagesLocked();
                } else {
                    removeAllMessagesLocked();
                }
    
                // We can assume mPtr != 0 because mQuitting was previously false.
                nativeWake(mPtr);
            }
        }
    

    九、什么事epoll机制

      1. Handler消息框架队列需要解决时间排序问题和阻塞问题。
      1. 事件不仅仅是应用层的东西,底层,驱动层都有事件,所以单单依靠java层面的队列如BlockingQueue无法解决
      1. 消息I/O有阻塞I/O和非阻塞I/O:阻塞I/O是一堆事件得一个一个挨个处理,这种不可忍。非阻塞I/O每个事件和事件处理器都类似有单独通道连接处理。
      1. Android系统事件和App事件处理类似C/S模型,C/S模型通信可以用Socket来解决,Socket采用的是select模型。
      1. Select模型(非阻塞I/O)可以处理非阻塞,但是它需要轮询来获取对应的事件处理器(这里对应哪个App来处理事件),另外它的事件需要拷贝。
      1. epoll机制(异步I/O),Looper.prepare()--> new MessageQueue() ---> nativeInit()时调用底层的epoll_create()函数为每个App添加文件描述符,并将其添加到B+树(红黑树)中,但调用Looper.loop()开启死循环时,MessgeQueue.next()就会调用nativePollOnce()来阻塞,底层为epoll_wait()。当有事件(epoll_ctl)来时,事件携带了文件描述符,就可以通过红黑树快速定位哪个App处理事件。并将其加入缓冲队列中,等待一个一个处理分发。


        Android事件响应模型
    epoll B+树 epoll底层逻辑 Handler java-底层调用关系

    十、一个线程有几个looper? 如何保证,又可以有几个Handler

    一个线程只有一个Looper和一个messageQueue,通过ThreadLocal保证。可以有多个Handler.

    十一、handler内存泄漏的原因,其他内部类为什么没有这个问题

      1. 根本原因:长生命周期对象持有短生命周期对象
      1. 内部类会持有外部类的引用,GCRoot链: MainActivity --> Handler ---> Message ---> MessageQueue ---> Looper ---> Thread
      1. 解决内存泄漏: 本质是断开GCRootl链,1:将Handler定义为static,它将不在持有外部类引用。 2:Activity onDestroy()时,Handler.removeCallbacksAndMessages移除所有的Message,因为Message持有Handler。

    十二、为什么主线程可以new Handler 其他子线程可以吗 怎么做?

      1. Handler机制需要有四个要素:Looper,MessageQueue,Message,Handler。实例化Handler之间需要先实例化Looper,不然会抛出异常。它需要执行在prepare()和loop()之间
      public Handler(@Nullable Callback callback, boolean async) {
            ...
            mLooper = Looper.myLooper();
            if (mLooper == null) {
                throw new RuntimeException(
                    "Can't create handler inside thread " + Thread.currentThread()
                            + " that has not called Looper.prepare()");
            }
            mQueue = mLooper.mQueue;
            ...
        }
    
      1. 之前我们说过在ActivityThread的main方法中,系统已经帮我们调用了prepareMainLooper()实例化了主线程的Looper对象,并且调用了loop()。主线程中所有代码都执行在它们俩之间。
    ActivityThread.java
        public static void main(String[] args) {
            Looper.prepareMainLooper();
            ...
            Looper.loop();
        }
    
      1. 子线程如果我们需要new Handler(),则需要先Looper.prepare()创建子线程Looper对象。并且如果子线程需要退出的话,需要手动调用Looper.quit()方法。
    new Thread(){
          public void run() {
            Looper.prepare();
            new Handler(){
              ...
            }
            Looper.loop();
        }
    }
    

    十三、Handler中的生产者-消费者设计模式你理解不?

    Handler架构图.png

    相关文章

      网友评论

          本文标题:关于Handler的面试专题

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