重温Android开发艺术探索之四 View工作原理

834 阅读2分钟

View 的工作原理

ViewRoot 和DecorView

ViewRoot对应ViewRootImpl,是连接WindowManager 和DecorView的纽带。View的三大流程都是通过ViewRoot完成的,如图所示, ![ViewRootImpl 的代码结构图]

并且当系统一些常量发生改变时也是有它监听到了处理的 在ActivityThread的attach方法中有添加这个监听,源码如下

ViewRootImpl.addConfigurationChange(Configuration newConfig),通过mH 发送消息 what = 118 的值到H 中处理 调用了 ActivityThread 的 handleConfigurationChanged

可以说ViewRootImpl控制这整个View的绘制流程,是非常重要的,当Activity被创建时,会将DecorView添加到Window中,同时会创建ViewRootImpl对象.

ActivityThread#handleResumeActivity wm.addView(decor,l),通过WindowManager 将decor添加到window中,l是LayoutParams,核心代码如下:api25的源码

                WindowManager wm;
                if (r.window == null && !a.mFinished && willBeVisible) {
                    r.window = r.activity.getWindow();
                    View decor = r.window.getDecorView();
                    decor.setVisibility(4);
                    wm = a.getWindowManager();
                    LayoutParams l = r.window.getAttributes();
                    a.mDecor = decor;
                    l.type = 1;
                    l.softInputMode |= forwardBit;
                    if (r.mPreserveWindow) {
                        a.mWindowAdded = true;
                        r.mPreserveWindow = false;
                        ViewRootImpl impl = decor.getViewRootImpl();
                        if (impl != null) {
                            impl.notifyChildRebuilt();
                        }
                    }

                    if (a.mVisibleFromClient && !a.mWindowAdded) {
                        a.mWindowAdded = true;
                        wm.addView(decor, l);
                    }
                } else if (!willBeVisible) {
                    r.hideForNow = true;
                }

wm.addView(decor, l)经过 WindowManagerImpl再到WindowManagerGlobal 中的 addView方法,在此方法里面对ViewRootImpl 进行了初始化。

 ViewRootImpl root = new ViewRootImpl(view.getContext(), display);
 root.setView(view, wparams, panelParentView);

View的绘制流程是从performTraversals执行遍历开始的,经过measure,layout,draw,三个过程才能最终将一个View绘制出来。

MeasureSpec 测量规格

MeasureSpec

一个32为的int值,高2位代表SpecMode 低30位代表SpecSize。

SpecMode

  • UNSPECIFIED 父容器不对View做任何限制,要多大给多大。
  • EXACTLY 父容器已经检测出了View所需要的精确大小,这时候View的最终大小就是SpecSize所指定的值,对应与LayoutParams中的match_parent和具体数值两种模式。
  • AT_MOST 父容器指定了一个最大值,View不能超过这个最大值,对应wrap_content。

MeasureSpec 和 layoutParams 的对应关系

  • DecorView 其MeasureSpec是由自身的LayoutParams和窗口的尺寸来共同决定

  • 普通的View 其MeasureSpec是由父容器的MeasureSpec和自身的LayoutParams来共同决定

  • 简单来说自定义Veiw到measure这一步的时候,需要测试自身的宽高,需要widthMeasureSpec, heightMeasureSpec,宽的测量规格和高的测量规格,两者的获取方式都是一样,需要根据父容器的MeasureSpec 和自身的 宽高来得到。

  • View的measure过程,到这一步肯定是经过了父控件的measure过程,得到了父控件传下来的measureSpec,然后调用onMeasure,通过setMeasuredDimension 来确定自身的宽高。当然中间需要经过一些计算得到widthMeasureSpec, heightMeasureSpec。

Activity启动时去获取某一View的宽高

  • Activity/View#onWindowFocusChanged。

  • ViewTreeObserver.addOnGlobalLayoutListener()。

总结

从ViewRootImpl的构造方法中调用 loadSystemProperties()

->profileRendering()

->scheduleTraversals()

-> mChoreographer.postCallback(Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);

->doTraversal();

->performTraversals();

->performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);

-> performLayout(lp, mWidth, mHeight);

-> performDraw();

完成View的三大流程。