您当前的位置: 首页 >  面试
  • 0浏览

    0关注

    674博文

    0收益

  • 0浏览

    0点赞

    0打赏

    0留言

私信
关注
热门博文

Android面试:ViewRootImpl相关

沙漠一只雕得儿得儿 发布时间:2021-03-27 16:19:28 ,浏览量:0

Android窗口机制系列:

Android窗口机制(一)初识Android的窗口结构Android窗口机制(二)Window,PhoneWindow,DecorView,setContentView源码理解Android窗口机制(三)Window和WindowManager的创建与ActivityAndroid窗口机制(四)ViewRootImpl与View和WindowManagerAndroid窗口机制(五)最终章:WindowManager.LayoutParams和Token以及其他窗口Dialog,Toast

在DecorView在handleResumeActivity方法中被绑定到了WindowManager,也就是调用了windowManager.addView(decorView)。而WindowManager的实现类是WindowManagerImpl,而它则是通过WindowManagerGlobal代理实现addView的,在WindowManagerGlobal的addView中,最后是调用了ViewRootImpl的setView方法,那么这个ViewRootImpl到底是什么。

本文:https://www.jianshu.com/p/9da7bfe18374

checkThread的解释:Android在子线程中操作UI:弹出Toast、改变TextView内容

https://blog.csdn.net/cpcpcp123/article/details/104122690

ViewRootImpl

ViewRootImpl是一个视图层次结构的顶部,它实现了View与WindowManager之间所需要的协议,作为WindowManagerGlobal中大部分的内部实现。这个好理解,在WindowManagerGlobal中实现方法中,都可以见到ViewRootImpl,也就说WindowManagerGlobal方法最后还是调用到了ViewRootImpl。addView,removeView,update调用顺序 WindowManagerImpl -> WindowManagerGlobal -> ViewRootImpl

我们看下前面调用到了viewRootImpl的setView方法

  public void setView(View view, WindowManager.LayoutParams attrs, View panelParentView) {
                ...
                // Schedule the first layout -before- adding to the window  
                // manager, to make sure we do the relayout before receiving  
                // any other events from the system.
                requestLayout();
                ...
                try {
                ...
                    res = mWindowSession.addToDisplay(mWindow, mSeq, mWindowAttributes,
                            getHostVisibility(), mDisplay.getDisplayId(),
                            mAttachInfo.mContentInsets, mAttachInfo.mStableInsets,
                            mAttachInfo.mOutsets, mInputChannel);
                } 
    }

在setView方法中, 首先会调用到requestLayout(),表示添加Window之前先完成第一次layout布局过程,以确保在收到任何系统事件后面重新布局。requestLayout最终会调用performTraversals方法来完成View的绘制。

接着会通过WindowSession最终来完成Window的添加过程。在下面的代码中mWindowSession类型是IWindowSession,它是一个Binder对象,真正的实现类是Session,也就是说这其实是一次IPC过程,远程调用了Session中的addToDisPlay方法。

    @Override
    public int addToDisplay(IWindow window, int seq, WindowManager.LayoutParams attrs,
            int viewVisibility, int displayId, Rect outContentInsets, Rect outStableInsets,
            Rect outOutsets, InputChannel outInputChannel) {
        return mService.addWindow(this, window, seq, attrs, viewVisibility, displayId,
                outContentInsets, outStableInsets, outOutsets, outInputChannel);
    }
View通过ViewRootImpl来绘制

ViewRootImpl调用到requestLayout()来完成View的绘制操作,我们看下源码

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

View绘制,先判断当前线程

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

如果不是当前线程则抛出异常,这个异常是不是感觉很熟悉啊。没错,当你在子线程更新UI没使用handler的话就会抛出这个异常

android.view.ViewRootImpl$CalledFromWrongThreadException: Only the original thread that created a view hierarchy can touch its views.

抛出地方就是这里,一般在子线程操作UI都会调用到view.invalidate,而View的重绘会触发ViewRootImpl的requestLayout,就会去判断当前线程。

接着看,判断完线程后,接着调用scheduleTraversals()

  void scheduleTraversals() {
        if (!mTraversalScheduled) {
            ...
            mChoreographer.postCallback(
                    Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);
          ...
        }
    }

scheduleTraversals中会通过handler去异步调用mTraversalRunnable接口

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

接着

  void doTraversal() {
            ...
            performTraversals();
            ...
    }

可以看到,最后真正调用绘制的是performTraversals()方法,这个方法很长核心便是

private void performTraversals() {  
        ......  
        performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);
        ...
        performLayout(lp, desiredWindowWidth, desiredWindowHeight);
        ......  
        performDraw();
        }
        ......  
    }  

而这个方法各自最终调用到的便是

        ......  
        int childWidthMeasureSpec = getRootMeasureSpec(mWidth, lp.width);  
        int childHeightMeasureSpec = getRootMeasureSpec(mHeight, lp.height);  
        ....
mView.measure(childWidthMeasureSpec, childHeightMeasureSpec);  
        ......  
mView.layout(0, 0, mView.getMeasuredWidth(), mView.getMeasuredHeight());  
        ......  
mView.draw(canvas);  

会开始触发测量绘制。 performTraversals方法会经过measure、layout和draw三个过程才能将一个View绘制出来,所以View的绘制是ViewRootImpl完成的,另外当手动调用invalidate,postInvalidate,requestInvalidate也会最终调用performTraversals,来重新绘制View。

View与WindowManager联系

那么View和WindowManager之间是怎么通过ViewRootImpl联系的呢。

从第三篇文章中我们知道,WindowManager是继承于ViewManager接口的,而ViewManager提供了添加View,删除View,更新View的方法。就拿setContentView来说,当Activity的onCreate调用到了setContentView后,view就会被绘制了吗?肯定不是,setContentView只是把需要添加的View的结构添加保存在DecorView中。此时的DecorView还并没有被绘制(没有触发view.measure,layout,draw)。

DecorView真正的绘制显示是在activity.handleResumeActivity方法中DecorView被添加到WindowManager时候,也就是调用到windowManager.addView(decorView)。而在windowManager.addView方法中调用到windowManagerGlobal.addView,开始创建初始化ViewRootImpl,再调用到viewRootImpl.setView,最后是调用到viewRootImpl的performTraversals来进行view的绘制(measure,layout,draw),这个时候View才真正被绘制出来。

这也就是为什么我们在onCreate方法中调用view.getMeasureHeight() = 0的原因,我们知道activity.handleResumeActivity最后调用到的是activity的onResume方法,但是按上面所说在onResume方法中调用就可以得到了吗,答案肯定是否定的,因为ViewRootImpl绘制View并非是同步的,而是异步(Handler)。我们可以在OnwindowFocusChanged或者view.post中获取view的宽高。

综上小结
  • 之所以说ViewRoot是View和WindowManager的桥梁,是因为在真正操控绘制View的是ViewRootImpl,View通过WindowManager来转接调用ViewRootImpl
  • 在ViewRootImpl未初始化创建的时候是可以进行子线程更新UI的,而它创建是在activity.handleResumeActivity方法调用,即DecorView被添加到WindowManager的时候
  • ViewRootImpl绘制View的时候会先检查当前线程是否是主线程,是才能继续绘制下去

 

关注
打赏
1657159701
查看更多评论
立即登录/注册

微信扫码登录

0.0378s