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

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

作者: 宾格66 | 来源:发表于2020-06-26 16:50 被阅读0次

    来点前奏说明

    当你打开这个文档的时候,你已经做好准备了,话不多说开搞。
    本文以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

    相关文章

      网友评论

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

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