美文网首页Android开发经验谈Android技术知识
字节跳动面试官:Android 中为什么需要 Handler?

字节跳动面试官:Android 中为什么需要 Handler?

作者: 5893d0835188 | 来源:发表于2020-10-14 09:34 被阅读0次

    要理解 Handler,就得先理解 Android 的 Message 机制.

    这里以用户滑动微信朋友圈为例,讲解一下 Android 的 Message 机制是怎么运行的,Message 机制中的各个核心组件都做了什么

    Message 产生

    用户滑动屏幕,产生了一系列 input 事件 (一个 Down 事件,若干个 Move 事件,一个 Up 事件),这些事件被系统包装成了一系列 Message(一个 Down Message,若干个 Move Message,一个 Up Message)

    Message 是用来传递信息的,上述 Message 中就包含了这些 input 事件的信息,比如 x 坐标,y 坐标。

    MessageQueue 存放 Message

    Message 产生后,有一个问题就是这些 Message 怎么发给应用?我要滑动朋友圈,那么这些个 Message 就得传给微信,让微信去处理,微信将这些事件给到朋友圈的 List 控件,让 List 产生新内容,并且实现上下滑动。

    首先想到的能不能直接把这些 Message 给到朋友圈的 List 控件(SystemServer 可以直接 Binder 发给 List 控件),可以是可以,但是麻烦;SystemServer 直接给朋友圈的 List 控件发 input message,那 SystemServer 得先知道有这么个控件,问题是应用有哪些控件,SystemServer 是不知道的,难道要遍历所有的控件,每个控件都发一个重复的 Message?这显然不是我们想要的。

    SystemServer 不能直接发给控件,那么能不能直接发给应用,让应用自己去处理呢?答案是肯定的,现在的 Android 也是这么做的, 你应用准备一个 MessageQueue(消息队列),我有 Message 就放到这个 MessageQueue 里面,你应用自己去处理,岂不美哉,这就是 MessageQueue 出现的原因

    Looper 派发 Message

    应用准备了一个 MessageQueue 之后,SystemServer 把之前包装好的一系列 Input Message(一系列 Message(一个 Down Message,若干个 Move Message,一个 Up Message))放到了微信的 MessageQueue 里面,剩下的就让微信自己去读取 MessageQueue 里面的内容,自己更新 UI 去

    问题是 MessageQueue 只是用来存放 Message 的,得有人来管理这个 MessageQueue。比如 MessageQueue 里面进了几个 Message,这些 Message 该到发给谁去处理?

    这里就引入了 Looper,Looper 来决定这个 Message 该发给谁去处理,Looper 会按照 Message 在 MessageQueue 里面的顺序,一个一个取出 Message,根据 Message 自带的信息(我想被谁处理 - target),发给对应的人去处理

    这个例子里面,这些 Message 的 target 就是微信的主线程的 handler

    Handler 处理 Message

    这时候,Handler 出场了,上面说 Looper 把 Message 发给对应的人去处理,这个就是 Handler。Handler 就是用来处理 Message 的,作为 Message 机制的最后一环,Handler 读取 Message 内容后,根据内容来做相关的处理。

    这个例子里面,一系列 Input Message 最终会由微信的主线程 Handler 来处理,经过复杂的事件传递和事件分发流程,传给对应的 List 控件,List 控件根据 Input Message 里面的内容,计算出自己下一帧的各个 Item 的位置,更新自己的 Item 和 Item 内的内容,从而产生 List 滑动效果,朋友圈滑动的流程就完成了

    Message 机制总结

    有了上面的 Message 机制的案例,理解下面这张图就顺理成章了,如上面几个标题所示

    1. Message 承载内容
    2. MessageQueue 存放 Message
    3. Looper 派发 Message
    4. Handler 处理 Message

    <noscript><img src="https://pic4.zhimg.com/50/v2-8377d4ed99bff66d01cdc99f9391cf99_hd.jpg?source=1940ef5c" data-rawwidth="974" data-rawheight="547" data-size="normal" data-default-watermark-src="https://pic4.zhimg.com/50/v2-ac14259857a986793c650747a77f8ddf_hd.jpg?source=1940ef5c" class="origin_image zh-lightbox-thumb" width="974" data-original="https://pic2.zhimg.com/v2-8377d4ed99bff66d01cdc99f9391cf99_r.jpg?source=1940ef5c"/></noscript>

    [图片上传失败...(image-7f2c84-1602639159720)]

    <figcaption>Message 机制</figcaption>

    App 主线程

    那么,App 主线程是怎么回事?下面是 App 进程创建后,ActivityThread.main 方法被调用的逻辑

    frameworks/base/core/java/android/app/ActivityThread.java
    public static void main(String[] args) {
           ......
           // 创建 Looper、Handler、MessageQueue
           Looper.prepareMainLooper();
           ......
           ActivityThread thread = new ActivityThread();
           thread.attach(false, startSeq);
    
           if (sMainThreadHandler == null) {
               // 指定主线程的 handler 为 H
               sMainThreadHandler = thread.getHandler();
          }
           ......
           // 开始准备接收消息
           Looper.loop();
      }
    }
    
    // 准备主线程的 Looper
    frameworks/base/core/java/android/os/Looper.java
    public static void prepareMainLooper() {
        prepare(false);
        synchronized (Looper.class) {
            if (sMainLooper != null) {
                throw new IllegalStateException("The main Looper has already been prepared.");
            }
            sMainLooper = myLooper();
        }
    }
    
    // prepare 方法中会创建一个 Looper 对象
    frameworks/base/core/java/android/os/Looper.java
    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 对象创建的时候,同时创建一个 MessageQueue
    frameworks/base/core/java/android/os/Looper.java
    private Looper(boolean quitAllowed) {
        mQueue = new MessageQueue(quitAllowed);
        mThread = Thread.currentThread();
    }
    

    通过上面的流程,主线程的 Looper、MessageQueue 都已经创建好了,这就打下了 Message 机制的基础,后续有 Message 进来,就会进入主线程的 MessageQueue ,然后 Looper 会把他派发给对应的 Handler

    对于主线程来说,这个 Handler 就在 ActivityThread 里面,下面截取一小段 Handler 的方法,大家可以看一下,熟悉代码的应该知道,BIND_APPLICATION 就是大家经常在 Systrace 中看到的新进程启动时候的那一段

    frameworks/base/core/java/android/app/ActivityThread.java
    class H extends Handler {
        public static final int BIND_APPLICATION        = 110;
        public static final int EXIT_APPLICATION        = 111;
        public static final int RECEIVER                = 113;
        public static final int CREATE_SERVICE          = 114;
        public static final int SERVICE_ARGS            = 115;
        public static final int STOP_SERVICE            = 116;
    
        public void handleMessage(Message msg) {
            switch (msg.what) {
                case BIND_APPLICATION:
                    Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "bindApplication");
                    AppBindData data = (AppBindData)msg.obj;
                    handleBindApplication(data);
                    Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
                    break;
            }
        }
    }
    

    所以之前也有人会问,ActivityThread 是主线程么?为什么没有继承 Thread ?看了上面的流程,答案就很清楚了,ActivityThread 并不是一个 Thread ,只不过他有个叫 H 的 Handler 来处理主线程的 Message 而已;另外 Activity、Service、BroadcastReceiver 组件的生命周期都是在主线程执行的,通过上面的讲解应该就比较好理解了:都是在 ActivityThread 的 H 这个 Handler 里面处理的.

    线程间通信

    App 主线程初始化完成后,Message 机制跑起来了,那么子线程呢?其实也是一样的,子线程也有一套 Message 机制,有它自己的 MessageQueue、Looper、Handler。

    那么线程间的 Message 通信就好理解了,Looper 在派发 Message 的时候,会根据 Message 自己的意愿(target,即目标 Handler),派发给对应的 Handler 去处理,这里的 Handler 既可以是子线程的 Handler ,也是主线程的 Handler,也可以是 App 进程里面其他的 Handler,只要知道且指定这个 Handler 即可

    比如非常典型的一个例子就是 AsyncTask,如果你在主线程创建了 AsyncTask ,那么在 AsyncTask 执行在子线程完耗时任务后,就会给主线程的 Handler 发 Message,来更新主线程的 UI

    子线程能不能更新 UI

    既然主线程和子线程都是 Message 机制,子线程到底能不能更新 UI 呢?答案是可以的,但是会出问题,比如主线程正在执行 doFrame ,Measure 方法刚刚走完,你子线程把 View 的宽高给改掉了,那么主线程后续执行 Layout 的时候,View 的位置摆放就可能会出问题(当然我没有试过,感兴趣的小伙伴可以把 checkThread 限制代码去掉,然后在子线程更新 UI 试试)

    所以 Android 为了避免这种情况,在有 UI 更新的地方,都加了 checkThread() ,来确保只有主线程才能更新 UI

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

    本文在开源项目:https://github.com/Android-Alvin/Android-LearningNotes 中已收录,里面包含不同方向的自学编程路线、面试题集合/面经、及系列技术文章等,资源持续更新中...

    作者:高爷
    链接:https://www.zhihu.com/question/421330220/answer/1480860373
    来源:知乎

    相关文章

      网友评论

        本文标题:字节跳动面试官:Android 中为什么需要 Handler?

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