Android UI的加载源码分析

view是如何被加载到界面上的?

创建一个普通的activity

//1.MainActivity.java
public class MainActivity extends Activity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        //添加布局入口
        setContentView(R.layout.activity_main);
    }
}

//2.Activity.java
public void setContentView(@LayoutRes int layoutResID) {
        getWindow().setContentView(layoutResID);
        initWindowDecorActionBar();
    }
//getWindow()获取的window对象是mWindow,Window类是个抽象类,拥有唯一的实例android.view.PhoneWindow
/* <p>The only existing implementation of this abstract class is
  android.view.PhoneWindow, which you should instantiate when needing a Window.
 */
 public abstract class Window {
 ...
 }

//3.PhoneWindow.java
//因此到PhoneWindow找到setContentView(int layoutResID)
@Override
    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();
        }
    }

//4.  installDecor();
    
//5.  mLayoutInflater.inflate(layoutResID, mContentParent);
//解析传进来的layoutResID和mContentParent, 实际上就是把传进来的mContentParent加载到mContentParent上,mContentParent是一个frameLayout

//4.  installDecor();
//4.1创建DevorView
 mDecor = generateDecor(); 
//4.2创建mContentParent
 mContentParent = generateLayout(mDecor); 
//4.2创建mContentParent
//解析基础控件layoutResource
View in = mLayoutInflater.inflate(layoutResource, null);
//DecorView添加基础控件in
decor.addView(in, new ViewGroup.LayoutParams(MATCH_PARENT, MATCH_PARENT));
mContentRoot = (ViewGroup) in;
//添加ID_ANDROID_CONTENT 并且直接返回contentParent
ViewGroup contentParent = (ViewGroup)findViewById(ID_ANDROID_CONTENT);

// layoutResource是一个基础控件ViewGroup
//xref: /frameworks/base/core/res/res/layout/screen_title.xml
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
23    android:orientation="vertical"
24    android:fitsSystemWindows="true">
25    <!-- Popout bar for action modes -->
26    <ViewStub android:id="@+id/action_mode_bar_stub"
27              android:inflatedId="@+id/action_mode_bar"
28              android:layout="@layout/action_mode_bar"
29              android:layout_width="match_parent"
30              android:layout_height="wrap_content"
31              android:theme="?attr/actionBarTheme" />
32    <FrameLayout
33        android:layout_width="match_parent" 
34        android:layout_height="?android:attr/windowTitleSize"
35        style="?android:attr/windowTitleBackgroundStyle">
36        <TextView android:id="@android:id/title" 
37            style="?android:attr/windowTitleStyle"
38            android:background="@null"
39            android:fadingEdge="horizontal"
40            android:gravity="center_vertical"
41            android:layout_width="match_parent"
42            android:layout_height="match_parent" />
43    </FrameLayout>
44    <FrameLayout android:id="@android:id/content"
45        android:layout_width="match_parent" 
46        android:layout_height="0dip"
47        android:layout_weight="1"
48        android:foregroundGravity="fill_horizontal|top"
49        android:foreground="?android:attr/windowContentOverlay" />
50</LinearLayout>

View就这样被加载到界面上了

总结:
* 创建顶层布局容器DecorView
* 在顶层布局中添加基础布局ViewGroup
* 将contentView添加到基础布局容器的FrameLayout中
view是如何绘制的?
    1. ActivityThread.class
public void handleMessage(Message msg) {
            if (DEBUG_MESSAGES) Slog.v(TAG, ">>> handling: " + codeToString(msg.what));
            switch (msg.what) {
                case LAUNCH_ACTIVITY: {
                    Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "activityStart");
                    final ActivityClientRecord r = (ActivityClientRecord) msg.obj;

                    r.packageInfo = getPackageInfoNoCheck(
                            r.activityInfo.applicationInfo, r.compatInfo);
                    handleLaunchActivity(r, null);
                    Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
                } break;
                ...
}
  • 2.handleLaunchActivity
  • 3.handleResumeActivity
  • 4.wm.addView(decor, l);
// 4.wm.addView(decor, l);
 ViewManager wm = a.getWindowManager();
 -> mWindowManager = mWindow.getWindowManager();
 //mWindow唯一的实例是 PhoneWindow,因此
 -> mWindowManager = ((WindowManagerImpl)wm).createLocalWindowManager(this);
 //因此wm是WindowManagerImpl的实例,在addView中调用了mGlobal.addView()
 -> mGlobal.addView(view, params, mDisplay, mParentWindow);
 //ViewRootImpl关联view, params, panelParentView,root是ViewRootImpl实例化对象
 -> root.setView(view, wparams, panelParentView);
 -> requestLayout()
 -> checkThread();
 -> scheduleTraversals();
 -> TraversalRunnable{}
 -> doTraversal();
 -> performTraversals();
 -> performMeasure(childWidthMeasureSpec,childHeightMeasureSpec); //1890 测量
 -> performLayout(lp, desiredWindowWidth, desiredWindowHeight);  //1931 布局
 -> performDraw(); // 2067 绘制

view的绘制流程就是以上这些步骤,现在具体来看下performMeasure(),performLayout(),performDraw()这三步关键的步骤。

  • 测量:performMeasure()
    • 先引入一个概念MeasureSpec,view的测量规格,是一个32位的二进制整数类型。view的测量按照一定的规则,以下是MeasureSpec的一些规则:
//MeasureSpec.java

  public static int makeMeasureSpec(int size, int mode) {
            if (sUseBrokenMakeMeasureSpec) {
                return size + mode;
            } else {
                return (size & ~MODE_MASK) | (mode & MODE_MASK);
            }
        }

UNSPECIFIED: 00 0000 0000 0000 0000 0000 0000 0000 00
-> 父容器不对view做任何限制,系统内部使用
  EXACTLY: 01 0000 0000 0000 0000 0000 0000 0000 00
-> 父容器检测view的大小,view的大小就是SpecSize ,对应子控件的 LayoutParams.match_parent / 固定大小
  AT_MOST: 10 0000 0000 0000 0000 0000 0000 0000 00
-> 父容器指定一个可用大小,view不能超过这个值, LayoutParams.wrap_content

MODE_MASK = 11 0000 0000 0000 0000 0000 0000 0000 00
 ~MODE_MASK = 00 1111 1111 1111 1111 1111 1111 1111 11
 size &  ~MODE_MASK = 00 后三十位
 model & MODE_MASK = 前两位 0000 0000 0000 0000 0000 0000 0000 00

(size & ~MODE_MASK) | (mode & MODE_MASK);  == model(前两位) size(后三十位)

  • 继续回到 ViewRootImpl 的1890行 performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);

-> performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);
-> mView.measure(childWidthMeasureSpec, childHeightMeasureSpec);
-> onMeasure(widthMeasureSpec, heightMeasureSpec);
-> setMeasuredDimension(getDefaultSize(getSuggestedMinimumWidth(), widthMeasureSpec),
                getDefaultSize(getSuggestedMinimumHeight(), heightMeasureSpec));
                //保存自身的宽和高
-> setMeasuredDimensionRaw(measuredWidth, measuredHeight);
//此处即测量结束,赋值给宽高,并设置标记flags
private void setMeasuredDimensionRaw(int measuredWidth, int measuredHeight) {
        mMeasuredWidth = measuredWidth;
        mMeasuredHeight = measuredHeight;

        mPrivateFlags |= PFLAG_MEASURED_DIMENSION_SET;
    }

//以上来看view的测量就结束了。但是view是如何测量的,就需要了解传进来的参数childWidthMeasureSpec,childHeightMeasureSpec是怎么得来的
  • int childWidthMeasureSpec = getRootMeasureSpec(mWidth, lp.width); //1854 ViewRootImpl.java
//mWidth : 窗口容器的宽度
//lp.width: 顶层容器的宽度
getRootMeasureSpec(mWidth, lp.width)
// 因此 DecorView的MeasureSpec由窗口大小和自身LayoutParams决定,遵守以下规则:

//  LayoutParams.MATCH_PARENT: 精确模式,窗口大小
//  LayoutParams.WRAP_CONTENT: 最大模式,最大为窗口大小
//  固定大小:精确模式,大小为LayoutParams的大小

//实际上是调用DecorView的measure()方法
 mView.measure(childWidthMeasureSpec, childHeightMeasureSpec);

 //在view的measure()方法内部还调用了 onMeasure(widthMeasureSpec, heightMeasureSpec);
 //DecorView的父类是FrameLayout,在FrameLayout的onMeasure中调用了 child.measure(childWidthMeasureSpec, childHeightMeasureSpec);

//因为是容器viewGroup,所以测量了自身需要在测量child控件

总结: viewGroup : measure -> onMeasure(子控件的宽高) -> setMeasuredDimension() -> setMeasuredDimensionRaw(保存自己的宽高)
view : measure -> onMeasure() -> setMeasuredDimension() -> setMeasuredDimensionRaw(保存自己的宽高)
自定义view不重写onMeasure方法,使用match_parent 和 wrap_content的效果是一样的

  • 布局performLayout() ,回到performLayout(lp, desiredWindowWidth, desiredWindowHeight); //1931 ViewRootImpl.java
->   host.layout(0, 0, host.getMeasuredWidth(), host.getMeasuredHeight());

-> setFrame(l, t, r, b);

//调用view.layout确定自身的位置,即确定mLeft,mTop,mRight,mBottom的值

总结:
viewGroup: layout(确定自己的位置,4个点的位置)->onLayout(进行子view的布局)
view: layout(确定自己的位置,4个点的位置)

  • performDraw绘制 //2067 ViewRootImpl.java
-> performDraw()
-> draw()
-> drawSoftware()
-> mView.draw(canvas);
->   /*
         * Draw traversal performs several drawing steps which must be executed
         * in the appropriate order:
         *
         *      1. Draw the background
         *      2. If necessary, save the canvas' layers to prepare for fading
         *      3. Draw view's content
         *      4. Draw children
         *      5. If necessary, draw the fading edges and restore layers
         *      6. Draw decorations (scrollbars for instance)
         */

总结:
viewGroup:

  1. 绘制背景drawBackground()
  2. 绘制自己onDraw()
  3. 绘制子view dispatchDraw()
  4. 绘制前景,滚动条等装饰onDrawForeground()

view:

  1. 绘制背景drawBackground()
  2. 绘制自己onDraw()
  3. 绘制前景,滚动条等装饰onDrawForeground()
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值