Android-View的加载

129 阅读4分钟

setContentView的工作机制

  Activity的setContentView方法实现中都直接调用了getWindow().setContentView方法,这里的window实际上是一个PhoneWindow的对象,PhoneWindow在Activity#attach中被new出来,传入了一个window对象,在Activity的启动过程中得知,Activity#attach实际是被ActivityThread调用的(performLaunchActivity)。
  PhoneWindow的几个setContentView的重载方法中,有直接传入View的,也有传入layoutId的,由实现可以很明显看出,传入layoutId的只是多了个inflate的过程,其他过程都一样。

public void setContentView(int layoutResID) {
        // Note: FEATURE_CONTENT_TRANSITIONS may be set in the process of installing the window
        // decor, when theme attributes and the like are crystalized. Do not check the feature
        // before this happens.
        if (mContentParent == null) {
            installDecor();
        } else if (!hasFeature(FEATURE_CONTENT_TRANSITIONS)) {
            mContentParent.removeAllViews();
        }

        if (hasFeature(FEATURE_CONTENT_TRANSITIONS)) {
            final Scene newScene = Scene.getSceneForLayout(mContentParent, layoutResID,
                    getContext());
            transitionTo(newScene);
        } else {
            mLayoutInflater.inflate(layoutResID, mContentParent);
        }
        mContentParent.requestApplyInsets();
        final Callback cb = getCallback();
        if (cb != null && !isDestroyed()) {
            cb.onContentChanged();
        }
        mContentParentExplicitlySet = true;
    }

先是判断mContentParent是否为null,如果为null就调用installDecor。在installDecor中可以看出,这个mContentParent应该是整个Window的View,包括了title之类的,我们setContentView的view只是它的内容区。generateLayout中使用了getWndowStyle,这里就读取了我们给window设置的theme,比如是否有title、是否是悬浮window,是否透明等等。这里还通过getLocalFeatures方法获取了我们在Activity中调用requestWindowFeature,解析出设置的值来定制window的样式。这也是为什么requestWidowFeature需要在setContentView之前调用。
  mContentParent.requestApplyInsets方法看实现其实是设置fitSystemWindows,这个在沉浸式状态栏中经常用到,比如content区是被顶在statusbar下面还是被statusbar覆盖。在setContentView的最后,通过Callback回调了onContentChanged方法,其实这个callback对象就是Activity本身。

View显示过程

  setContentView的过程,如果传入的是一个layoutId则会调用inflate,如果是个View就直接addView了,在这个过程,其实已经构造完成了一个ViewTree。在这些都是在onCreate中,这个时候,所有的View都还没有显示出来,View的显示是在onResume生命周期中。
  在ActivityThread#handleResuleActivity中,调用了WindowManager的addView方法,这个操作是为了把WindowManager与DecorView连接起来,连接的中介就是ViewRootImpl。WindowManager的实现类是WindowManagerImpl,WindowManagerImpl的addView方法中直接调用了WindowManagerGlobal的addView方法。而在WindowManagerGlobal的addView中,就创建了一个ViewRootImpl,并把传入的DecorView包了一层,最后调用ViewRootImpl的setView方法。setView的中间位置就调用了requestLayout方法,ViewRootImpl的requestLayout方法会调用scheduleTraversals,scheduleTraversals方法中通过handler的loop去执行,mTraversalRunnable,这个mTraversalRunnable的run方法中调用了doTraversal方法,进而调用了performTraversals方法,这个performTraversals就是大名鼎鼎的View绘制入口。performTraversals中依次调用了performMeasure、performLayout以及performDraw三个方法,这三个方法的作用分别是计算View的大小、计算View的位置、将View绘制到Canvas上,这三个方法中都是间接调用了ViewRootImpl内部View的measure、layout和draw方法。
  这里又回到View的绘制三步骤来了。View有两种,一种是单一控件,也就是像Button、TextView这种的本身就是一个最小粒度的控件了,还有一种就是容器控件,继承自ViewGroup。
  View的measure方法是个final方法,子类不可重写,但其中调用了onMeasure方法,可以重写,有些ViewGroup的子类就通过重写onMeasure调用measureChildren来递归地调用子View的measure过程。measure传入的两个int参数可以分别继续分解,分别是2位的specMode和30位的specSize。specMode有MeasureSpec.EXACTLY、MeasureSpec.AT_MOST、MeasureSpec.UNSPECIFIED这三种取值,分别表示确定模式(父View指定子View的大小,由specSize确定),最多模式(父View希望子View的大小最多是specSize的大小),未指定模式(父View完全依据子View的设定值来决定),而specSize就是指大小,单位是像素。 View的layout中也调用了onLayout方法,但ViewGroup的onLayout是个抽象方法,表示ViewGroup的继承类需要实现onLayout来保证子View的位置关系。这里需要注意到getWidth、getHeight和getMeasuredWidth、getMeasureHeight之间的区别,getMeasurexxx在measure之后可以获取到,而getxxx必须在layout之后才能获取到,而layout完毕之后即可获取left、top、right以及bottom这些位置参数了。
  View的draw方法中首先调用了drawBackground方法来绘制背景,第二步调用使用onDraw来绘制内容区,第三步调用dispatchDraw绘制子View,第四步绘制滚动条、第五步绘制前景色,最后绘制获取焦点的高亮色。传入的参数是Canvas对象,表示一个画布,通过Surface#lockCanvas获取。

绘制优化核心思想

  通过draw过程,可以更形象地描述,其实View只是一个指导如果在Canvas上进行绘画的框架,measure和layout都只是指导绘制大小位置相关的,而draw方法是指导画的具体过程的,比如说用什么颜色,是否使用渐变色之类的。在绘制优化过程中,可以通过代码逻辑简化View的控制逻辑,执行更少的步骤,提高绘制性能。