View绘制流程的开启

9 篇文章 0 订阅
7 篇文章 0 订阅

上一篇文章《Activity的创建》中我们说到了Activity创建后会调用其onCreate生命周期,而我们的onCreate方法一般这么写

@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);
}

我一般会使用setContentView方法设置Activity的界面布局,今天我们就看下它做了什么

1.Activity#setContentView()

public void setContentView(@LayoutRes int layoutResID) {
    getWindow().setContentView(layoutResID);
    initWindowDecorActionBar();
}

显然Activity自己并没有对其做处理,而是交给了getWindow方法处理,getWindow方法返回的是Activity中的全局变量mWindow,它是Window窗口类型。Window是一个抽象类,在Android中它唯一的子类是PhoneWindow,也就是说Activity的mWindow全局变量必定是PhoneWindow类型的。我们看下mWindow是如何被创建的。

我们在上一篇文章《Activity的创建》讲到了Activity是在ActivityThreadperformLaunchActivity创建的,而在创建Activity后,会调用activity.attach方法将ContextApplicationActivity绑定,而就在该方法里我们创建了PhoneWindow对象,我们来看下Activity#attach方法

final void attach(Context context, ActivityThread aThread,
            Instrumentation instr, IBinder token, int ident,
            Application application, Intent intent, ActivityInfo info,
            CharSequence title, Activity parent, String id,
            NonConfigurationInstances lastNonConfigurationInstances,
            Configuration config, String referrer, IVoiceInteractor voiceInteractor,
            Window window, ActivityConfigCallback activityConfigCallback) {
    ...
    mWindow = new PhoneWindow(this, window, activityConfigCallback);
    mWindow.setWindowControllerCallback(this);
    mWindow.setCallback(this);
    mWindow.setOnWindowDismissedCallback(this);
    mWindow.getLayoutInflater().setPrivateFactory(this);
    ...
    mWindow.setWindowManager(
        (WindowManager)context.getSystemService(Context.WINDOW_SERVICE),
        mToken, mComponent.flattenToString(),
    ...
}    

我们可以看到mWindow确实为PhoneWindow的实例

既然如此,我们接下来就要看一下PhoneWindowsetContentView方法

@Override
public void setContentView(int layoutResID) {
    if (mContentParent == null) {
        //1.初始化decor
        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 {
        //2.加载Activity设置的界面
        mLayoutInflater.inflate(layoutResID, mContentParent);
    }
    ...
}

在上面2处我们可以看到我们再Activity设置的布局被解析关联到了mContentParent,而mContentParent是一个View,他的注释为This is the view in which the window contents are placed. It is either mDecor itself, or a child of mDecor where the contents go.简单翻译为 这是放置窗口内容的视图。它是mDecor本身,或者内容所在的mDecor的child 。

其实mContentParent是在上面的注释1出installDecor()方法获得的,下面我们看下installDecor()方法源码

2.PhoneWindow#installDecor()

private void installDecor() {
	...
    if (mDecor == null) {
        //1.初始化decor
        mDecor = generateDecor(-1);
        ...
    } 
    ...
    if (mContentParent == null) {
        //2.初始化ContentParent
        mContentParent = generateLayout(mDecor);
        ...
    }
    ...                      
}                               

我们可以看到在2处我们通过generateLayout得到了ContentParent

我们看下generateLayout方法

protected ViewGroup generateLayout(DecorView decor) {
	int layoutResource;
	...
	layoutResource = R.layout.screen_simple;
	...
	mDecor.onResourcesLoaded(mLayoutInflater, layoutResource);
	ViewGroup contentParent = (ViewGroup)findViewById(ID_ANDROID_CONTENT);
	...
	return contentParent;
}

可以看到我们调用了PhoneWindowfindViewById方法,我们继续看它的源码

@Nullable
public <T extends View> T findViewById(@IdRes int id) {
    return getDecorView().findViewById(id);
}

PhoneWindow类自己并没有处理而是交给了getDecorView()获取的view对象处理

我们来看下PhoneWindowgetDecorView()方法

@Override
public final View getDecorView() {
    if (mDecor == null || mForceDecorInstall) {
    	//看这里,是不是很眼熟
        installDecor();
    }
    return mDecor;
}

这里返回了一个mDecor对象,大家可以看上面installDecor方法里使用generateDecor去获取了mDecor的对象,我们再继续看一下generateDecor方法;

protected DecorView generateDecor(int featureId) {
	...
	return new DecorView(context, featureId, this, getAttributes());
}

直接简单粗暴的创建了一个DecorView对象,而DecorView实际上是一个继承了FrameLayoutViewGroup

也就是说我们再generateLayout方法里调用ViewGroup contentParent = (ViewGroup)findViewById(ID_ANDROID_CONTENT)获取到contentParent 实际上就是在DecorView里找到一个id为ID_ANDROID_CONTENT(其值为com.android.internal.R.id.content)的ViewGroup但是我们翻遍DecorView也找不到他含有一个id为ID_ANDROID_CONTENT的child,那么它只能是后期添加到DecorView里面的。

我们继续回到generateLayout方法有一样代码mDecor.onResourcesLoaded(mLayoutInflater, layoutResource),看名字我们猜测它就是为了加载布局

layoutResource的赋值为布局R.layout.screen_simple(这只是一种情况下的布局)

而我们看R.layout.screen_simple的内容

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:fitsSystemWindows="true"
    android:orientation="vertical">
    <ViewStub android:id="@+id/action_mode_bar_stub"
              android:inflatedId="@+id/action_mode_bar"
              android:layout="@layout/action_mode_bar"
              android:layout_width="match_parent"
              android:layout_height="wrap_content"
              android:theme="?attr/actionBarTheme" />
    <FrameLayout
         android:id="@android:id/content"
         android:layout_width="match_parent"
         android:layout_height="match_parent"
         android:foregroundInsidePadding="false"
         android:foregroundGravity="fill_horizontal|top"
         android:foreground="?android:attr/windowContentOverlay" />
</LinearLayout>

在这里我们看到R.id.content,我们猜测mDecor.onResourcesLoaded方法将这个布局添加为自己的子布局,后面又用findViewById获取到了id为R.id.contentFrameLayout

我们看下DecorViewonResourcesLoaded方法

void onResourcesLoaded(LayoutInflater inflater, int layoutResource) {
    ...
    final View root = inflater.inflate(layoutResource, null);
    ...
    addView(root, 0, new ViewGroup.LayoutParams(MATCH_PARENT, MATCH_PARENT));
    mContentRoot = (ViewGroup) root;
    ...
}

果然像我们猜测的一样,将传入的布局解析后添加为了自己的子View,那么之前我们猜测的contentParent 为id为R.id.contentFrameLayout也就属实了。

结合上面PhoneWindowsetContentView中我们将Activity的布局文件加载到了contentParent 中我们脑海中可以建立一个View树结构

DecorVeiw->布局R.layout.screen_simple(LinearLayout)->R.id.contentFrameLayout->Activity定义的布局

到了这里我们好像也还是不清楚Activity怎么渲染的,其实也对,上面的这些操作都是在onCreate生命周期里处理的,我们知道onCreate生命周期时Activity根本不可见,只有在onResume生命周期里Activity才可见。

ActivityThread handleResumeActivity 中会执行Activity的onResume生命周期

3. ActivityThread #handleResumeActivity()

final void handleResumeActivity(IBinder token,
            boolean clearHide, boolean isForward, boolean reallyResume, int seq, String reason) {
    ...
    //调用Activity的onResume生命周期
    r = performResumeActivity(token, clearHide, reason);
    if (r != null) {
        final Activity a = r.activity;
        ...
        View decor = r.window.getDecorView();
        decor.setVisibility(View.INVISIBLE);
        ViewManager wm = a.getWindowManager();
        WindowManager.LayoutParams l = r.window.getAttributes();
        ...
        //添加View到WM
        wm.addView(decor, l);
    }
    ...
}    

这里将调用WindowManageraddView方法将DecorView添加,WindowManger addViewDecorView 被渲染绘制到屏幕上显示.

PhoneWindow 只是负责处理一些应用窗口通用的逻辑(设置标题栏,导航栏等)。但是真正完成把一个 View 作为窗口添加到 WMS 的过程是由 WindowManager 来完成的。

WindowManager 是接口类型,它是在Activityattach方法中调用PhoneWindow setWindowManager方法设置的

mWindow.setWindowManager(
        (WindowManager)context.getSystemService(Context.WINDOW_SERVICE),
        mToken, mComponent.flattenToString(),

我们来看下它的源码

public void setWindowManager(WindowManager wm, IBinder appToken, String appName,
            boolean hardwareAccelerated) {
    ...
    mWindowManager = ((WindowManagerImpl)wm).createLocalWindowManager(this);     
}            
public WindowManagerImpl createLocalWindowManager(Window parentWindow) {
    return new WindowManagerImpl(mContext, parentWindow);
}

可以看到mWindowManagerWindowManagerImpl类的实例

那么我们需要看一下WindowManagerImpladdView方法的源码

WindowManagerImpl#addView

@Override
public void addView(@NonNull View view, @NonNull ViewGroup.LayoutParams params) {
    applyDefaultToken(params);
    mGlobal.addView(view, params, mContext.getDisplay(), mParentWindow);
}

WindowManagerImpl什么都没有做,将其交给了mGlobal处理

mGlobalWindowManagerGlobal的实例

public final class WindowManagerImpl implements WindowManager {
    private final WindowManagerGlobal mGlobal = WindowManagerGlobal.getInstance();
    ...
}        

我们继续看WindowManagerGlobaladdView方法

public void addView(View view, ViewGroup.LayoutParams params,Display display, Window parentWindow) {
    ...
    ViewRootImpl root;
    View panelParentView = null;
    synchronized (mLock) {
        ...
        //创建一个ViewRootImpl
        root = new ViewRootImpl(view.getContext(), display);
        ...
         try {
              root.setView(view, wparams, panelParentView);
         }
        ...
    }
        
}

可以看到,先是创建了一个ViewRootImpl,然后调用了其setView方法设置View

4.ViewRootImpl#setView

public void setView(View view, WindowManager.LayoutParams attrs, View panelParentView) {
    ...
    mView = view;
    ...
    //开启绘制流程
    requestLayout();
    ...
    try{
        ...
        //将 View 添加到 WMS 中
        res = mWindowSession.addToDisplay(mWindow, mSeq, mWindowAttributes,
                            getHostVisibility(), mDisplay.getDisplayId(),
                            mAttachInfo.mContentInsets, mAttachInfo.mStableInsets,
                            mAttachInfo.mOutsets, mInputChannel);
    }
	...
}

先开启View的绘制流程,确保View在被添加到Window上显示到屏幕之前,已经完成测量和绘制操作,然后调用 mWindowSession addToDisplay 方法将 View 添加到 WMS 中。

mWindowSession WindowManagerGlobal 中的单例对象,实际上是 IWindowSession 类型,真正的实现类是

System 进程中的 SessionSession会与WMS进行通信。将View相关的操作转交给WMS。

我们重点看下requestLayout方法

5. ViewRootImpl#requestLayout

@Override
public void requestLayout() {
    ...
    scheduleTraversals();
}
void scheduleTraversals() {
    ...
    mChoreographer.postCallback(
                Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);
    ...    
}

mTraversalRunnable是一个TraversalRunnable类的实例,它继承自Runnable

它的run方法

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

调用performTraversals

private void performTraversals() {
    ...
    //执行测量
	performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);
    ...
    //执行布局
    performLayout(lp, mWidth, mHeight);
    ...
    //执行绘制
    performDraw();
    ...
}    

performTraversals方法里开始执行测量、布局、绘制流程

private void performMeasure(int childWidthMeasureSpec, int childHeightMeasureSpec) {
    ...
    try {
            mView.measure(childWidthMeasureSpec, childHeightMeasureSpec);
        }
    ...
}

这里调用mView.measure方法,而mView是之前的设置进来的DecorView,也就是说在这里开启了View树的测量流程

private void performLayout(WindowManager.LayoutParams lp, int desiredWindowWidth,
            int desiredWindowHeight) {
    ...
    final View host = mView;
    ...
    try {
        //开启布局
        host.layout(0, 0, host.getMeasuredWidth(), host.getMeasuredHeight());
    }
    ...
    
}            

这里跟measure一样调用DecorViewlayout方法,开启了View树的布局流程。

private void performDraw() {
    ...
    try {
        draw(fullRedrawNeeded);
    }
    ...
}
private void draw(boolean fullRedrawNeeded) {
    ...
    if (!drawSoftware(surface, mAttachInfo, xOffset, yOffset, scalingRequired, dirty){
        return;
	}
    ...
}
private boolean drawSoftware(Surface surface, AttachInfo attachInfo, int xoff, int yoff,boolean scalingRequired, Rect dirty) {
    ...
    //开始绘制
    mView.draw(canvas);
    ...
}            

performDraw经过一系列方法调用后最终调用DecorViewdraw方法开始绘制流程,而DecorView会对其子View进行非法draw事件

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值