关于Handler的面试专题

2022-02-02  本文已影响0人  天上飘的是浮云

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

一、Handler源码吃透

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

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


Handler架构图.png
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
    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。

    public static void prepareMainLooper() {
        prepare(false);
        synchronized (Looper.class) {
            if (sMainLooper != null) {
                throw new IllegalStateException("The main Looper has already been prepared.");
            }
            sMainLooper = myLooper();
        }
    }
  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));
    }
    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消息队列有什么变化?

    public final boolean postDelayed(@NonNull Runnable r, long delayMillis) {
        return sendMessageDelayed(getPostMessage(r), delayMillis);
    }
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如何创建?哪种方式更好?

    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,你的理解是?

    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));
    }
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;
  }
}
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();
    }
}
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;
            }
            ...
      }  
}
  Message next() {
        ...
        for (;;) {
            ...
            nativePollOnce(ptr, nextPollTimeoutMillis);
            synchronized (this) {
                ...
                 if (mQuitting) {
                    dispose();
                    return 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机制

epoll B+树 epoll底层逻辑 Handler java-底层调用关系

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

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

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

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

  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;
        ...
    }
ActivityThread.java
    public static void main(String[] args) {
        Looper.prepareMainLooper();
        ...
        Looper.loop();
    }
new Thread(){
      public void run() {
        Looper.prepare();
        new Handler(){
          ...
        }
        Looper.loop();
    }
}

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

Handler架构图.png
上一篇下一篇

猜你喜欢

热点阅读