面试题

一、View绘制流程

2020-03-04  本文已影响0人  徒步青云

1、Activity布局添加流程

1、创建顶层布局容器DecorView,它被PhoneWindow类所持有
2、在顶层布局DecorView中加载基础布局SubDecor,SubDecor一般包含两部分:title和content
3、将自定义布局添加到基础布局SubDecor中

new了一个DecorView,获取其中id为content的控件mContentParent,将subDecor添加到mContentParent,将自定义布局文件添加到subDecor

image.png

源码分析:

//MainActivity.java  
protected void onCreate(@Nullable Bundle savedInstanceState) {  
    super.onCreate(savedInstanceState);
    setContentView(int layoutResID)
}

//AppCompatActivity.java
public void setContentView(@LayoutRes int layoutResID) {  
    getDelegate().setContentView(layoutResID);  
}

public AppCompatDelegate getDelegate() {  
    if (mDelegate == null) {  
        mDelegate = AppCompatDelegate.create(this, this);  
    }  
    return mDelegate;  
}

//AppCompatDelegate.java
public static AppCompatDelegate create(Activity activity, AppCompatCallback callback) {
    //create静态方法返回AppCompatDelegateImpl对象
    //第二个参数将activity中的mWindow对象,赋值给AppCompatDelegateImpl中mWindow变量
    //而activity中的mWindow对象,是在attach方法中被new成PhoneWindow
    return new AppCompatDelegateImpl(activity, activity.getWindow(), callback);
}

//AppCompatDelegateImpl.java
public void setContentView(int resId) {
    // ***重点***
    //1、创建SubDecor,并将其添加到DecorView中
    ensureSubDecor();
    //2、获得id为content的FrameLayout
    ViewGroup contentParent = mSubDecor.findViewById(android.R.id.content);  
    contentParent.removeAllViews();  
    //3、将自定义布局添加到content布局中
    LayoutInflater.from(mContext).inflate(resId, contentParent);  
    mAppCompatWindowCallback.getWrapped().onContentChanged();  
}
  
private void ensureSubDecor() {  
    if (!mSubDecorInstalled) {  
        mSubDecor = createSubDecor();
        //.......省略设置标题、content.requestLayout等内容  
        mSubDecorInstalled = true;
    }
}

private ViewGroup createSubDecor() {  
    //.......省略ActionBar风格设置
    
    // Now let's make sure that the Window has installed its decor by retrieving it
    //如果DecorView不存在,则创建它,这里需要注意一下
    mWindow.getDecorView();
    
    final LayoutInflater inflater = LayoutInflater.from(mContext);  
    ViewGroup subDecor = null;
    //.......省略部分内容:根据不同风格、属性,为subDecor设置不同的布局文件
    
    if (mDecorContentParent == null) {  
        //获取title控件
        mTitleView = (TextView) subDecor.findViewById(R.id.title);  
    }  

    final ContentFrameLayout contentView = (ContentFrameLayout) subDecor.findViewById(  
            R.id.action_bar_activity_content);  
  
    final ViewGroup windowContentView = (ViewGroup) mWindow.findViewById(android.R.id.content);  
    //如果PhoneWindow中已存在id为content的容器,则将容器中所有子控件转移到DecorView中
    if (windowContentView != null) {
        while (windowContentView.getChildCount() > 0) {
            final View child = windowContentView.getChildAt(0);
            windowContentView.removeViewAt(0);
            contentView.addView(child);
        }

        windowContentView.setId(View.NO_ID);
        contentView.setId(android.R.id.content);
    }

    //将subDecor设置给mWindow
    mWindow.setContentView(subDecor);
    
    return subDecor;  
}

//PhoneWindow.java
public final View getDecorView() {
    if (mDecor == null || mForceDecorInstall) {
        installDecor();
    }
    return mDecor;
}

private void installDecor() {
    mForceDecorInstall = false;
    if (mDecor == null) {
        //new了一个DecorView,它继承至FrameLayout
        mDecor = generateDecor(-1);
        //........
    } else {
        mDecor.setWindow(this);
        //........
    }
    if (mContentParent == null) {
        //mContentParent控件id为content
        mContentParent = generateLayout(mDecor);
        //........
    }
}

protected ViewGroup generateLayout(DecorView decor) {
    //省略layoutResource赋值流程
    mDecor.startChanging();
    //将layoutResource布局文件inflate到mDecor中
    mDecor.onResourcesLoaded(mLayoutInflater, layoutResource);
    ViewGroup contentParent = (ViewGroup)findViewById(ID_ANDROID_CONTENT);
    //省略后续操作
    return contentParent;
}

//mWindow.setContentView(subDecor),其实是调用PhoneWindow的setContentView方法,将subDecor传入

public void setContentView(View view, ViewGroup.LayoutParams params) {
    if (mContentParent == null) {
        installDecor();
    } else if (!hasFeature(FEATURE_CONTENT_TRANSITIONS)) {
        //用于mContentParent已经在getDecorView->installDecor中被赋值,所以会调用到下面这个方法
        mContentParent.removeAllViews();
    }

    if (hasFeature(FEATURE_CONTENT_TRANSITIONS)) {
        view.setLayoutParams(params);
        final Scene newScene = new Scene(mContentParent, view);
        transitionTo(newScene);
    } else {
        //将subDecor添加到DecorView中
        mContentParent.addView(view, params);
    }
    //......
}

image.png image.png

Activity的创建离不开ActivityThread。

ActiveThread是每一个应用程序所在进程的主线程。

//ActivityThread有一个ArrayList存储启动的 Activity列表。

final ArrayList<ActivityClientRecord> mRelaunchingActivities
= new ArrayList<ActivityClientRecord>();
ActivityClientRecord存储启动的Activity相关信息。

https://blog.csdn.net/joedan0104/article/details/82857450

2、View的绘制流程

ActivityThread.handleResumeActivity()
-->WindowManagerImpl.addView()
-->WindowManagerGlobal.addView()
-->ViewRootImpl.addView()
-->ViewRootImpl.requestLayout()
-->ViewRootImpl.scheduleTraversals()
-->ViewRootImpl.doTraversal()
-->ViewRootImpl.performTraversal()
-->进入View绘制流程

1、绘制入口

ActivityThread.handleResumeActivity
    ->WindowManagerImpl.addView(decorView,layoutParams)
    ->WindowManagerGlobal.addView()//会创建ViewRootImpl对象

2、绘制的类及方法

ViewRootImpl.setView(decorView,layoutParams,parentView)
    ->requestLayout()
        ->scheduleTraversals()
        ->doTraversal()
        ->performTraversals()

3、绘制三大步骤

测量:ViewRootImpl.performMeasure
布局:ViewRootImpl.performLayout
绘制:ViewRootImpl.performDraw

3、UI绘制详细步骤

1、测量performMeasure

view.measure->view.onMeasure->view.setMeasuredDimension->setMeasuredDimensionRaw

2、布局performLayout

view.layout->view.onLayout

3、绘制performDraw

ViewRootImpl.draw(fullRedrawNeeded)->ViewRootImpl.drawSoftware->view.draw(Canvas)

View测量:

MeasureSpec是一个32位int值,其中前2位表示模式,后30位表示尺寸大小。

相关方法:

MeasureSpec.getMode()
MeasureSpec.getSize()
MeasureSpec.makeMeasureSpec(size, mode)
模式 含义
AT_MOST 父容器指定一个可用大小,View的大小不能超过这个值,LayoutPamras、wrap_content
EXACTLY 父容器检测出View的大小,View的大小就是SpecSize、LayoutPamras、match_parent、固定大小
UNSPECIFIED 父容器不对View做任何限制,系统内部使用
image.png image.png
上一篇下一篇

猜你喜欢

热点阅读