架构设计

架构设计分析(二)Android消息机制篇二 为什么不能在子线程

2020-06-26  本文已影响0人  宾格66

来点前奏说明

当你打开这个文档的时候,你已经做好准备了,话不多说开搞。
本文以Android 9.0 版本进行分析,当然你也可以在线看源码
在线源码查看
Android源码下载编译
9.0源码百度网盘下载链:https://pan.baidu.com/s/1OEek7vXE9FUhzVnOfTVJzg 提取码:d0ks
在此特别说明,我这篇主要分析流程和注释。我把英语注释也粘贴了,大家自己去翻译自己消化,个人意见重点是流程+注释,流程+注释,流程+注释。

为什么有Handler:
  • 主线程不能做耗时操作
  • 子线程不能更新UI
那么为什么不能在子线程更新UI呢
  • 如果你没看过源码或者分析过,我想你会回答谷歌这样设计的。
  • 如果继续问谷歌为什么这么设计呢,我猜想你内心已经开始骂娘了,我TM哪知道他为什么这么设计。
  • 这篇文章只是我自己对这个事情的看法,如果有误,欢迎各位评论指正。
子线程更新UI常见的报错信息
  • android.view.ViewRootImpl$CalledFromWrongThreadException: Only the original thread that created a view hierarchy can touch its views.
    at android.view.ViewRootImpl.checkThread(ViewRootImpl.Java:6581)
    at android.view.ViewRootImpl.requestLayout(ViewRootImpl.java:924)
堆栈代码部分

framework/base/core/java/android/view/ViewRootImpl.java

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

接着往下看异常日志,发现了ViewRootImpl中调用的地方是requestLayout方法

    @Override
    public void requestLayout() {
        if (!mHandlingLayoutInLayoutRequest) {
            checkThread();
            mLayoutRequested = true;
            scheduleTraversals();
        }
    }

scheduleTraversals看字面意思计划遍历

    void scheduleTraversals() {
        if (!mTraversalScheduled) {
            mTraversalScheduled = true;
            mTraversalBarrier = mHandler.getLooper().getQueue().postSyncBarrier();
            mChoreographer.postCallback(
                    Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);
            if (!mUnbufferedInputDispatch) {
                scheduleConsumeBatchedInput();
            }
            notifyRendererOfFramePending();
            pokeDrawLockIfNeeded();
        }
    }

framework/base/core/java/android/view/Choreographer.java 调postCallback方法

刚开始对这个类注释说明了一下,我翻译了一下。协调动画、输入和绘图的计时
Coordinates the timing of animations, input and drawing

postCallback方法的的第二个参数:TraversalRunnable,意思是遍历线程,是一个后台任务

    final class TraversalRunnable implements Runnable {
        @Override
        public void run() {
            doTraversal();
        }
    }

里面除了调用了doTraversal()方法,我们继续看doTraversal()方法

    void doTraversal() {
        if (mTraversalScheduled) {
            mTraversalScheduled = false;
            mHandler.getLooper().getQueue().removeSyncBarrier(mTraversalBarrier);

            if (mProfile) {
                Debug.startMethodTracing("ViewAncestor");
            }

            performTraversals();

            if (mProfile) {
                Debug.stopMethodTracing();
                mProfile = false;
            }
        }
    }
ViewRootImpl啥时候创建的?

可以看到里面调用了performTraversals()方法,View的绘制过程就是从performTraversals方法开始的。我们现在知道了,每一次访问UI,Android都会重新绘制View。ViewRoot会调用checkThread方法检查当前访问UI的线程是哪个,如果不是UI线程则会抛出异常。

为什么谷歌要提出:“UI更新一定要在UI线程里实现”这一规则呢?

目的在于提高移动端更新UI的效率和和安全性,以此带来流畅的体验。原因是:Android的UI访问是没有加锁的,多个线程可以同时访问更新操作同一个UI控件。也就是说访问UI的时候,android系统当中的控件都不是线程安全的,这将导致在多线程模式下,当多个线程共同访问更新操作同一个UI控件时容易发生不可控的错误,这是致命的。所以Android中规定只能在UI线程中访问UI,这相当于从另一个角度给Android的UI访问加上锁

FAQ

Question1:Handler Message MessageQueue对应关系?
Answer1:一个线程只能有一个Looper和MessageQueue 但可以接收多个Handler发过来的多个消息
一个Message只能属于一个Handler
一个Handler只能处理自己发送给Looper的消息。
一个MessageQueue对应多个Message

上一篇 下一篇

猜你喜欢

热点阅读