阅读 144

Android进阶知识:绘制流程(下)

1、前言

在前一篇Android进阶知识:绘制流程(中)中主要是关于ViewViewGroup的绘制流程的源码,梳理了绘制流程。这一篇主要来看看绘制流程的起点到底在哪?Activity的界面到底是怎样显示出来的呢?最后的这篇就来看看这些问题。

2、Activity的setContentView方法

想要知道Activity的界面到底是怎样显示出来,第一反应就是看Activity的源码,从哪里开始看呢?自然是setContentView方法,毕竟只要使用Activity必然会在onCreate的生命周期中调用这个方法将画好的布局传入进去。所以就来看看这个方法到底做了些什么。

   public void setContentView(@LayoutRes int layoutResID) {
        //获取到PhoneWindow调用其setContentView方法
        getWindow().setContentView(layoutResID);
        initWindowDecorActionBar();
    }
复制代码

setContentView方法中调用了getWindowsetContentView方法,来看这个getWindow获得的是个什么东西。

    public Window getWindow() {
        return mWindow;
    }
复制代码

getWindow方法中返回了成员变量中的mWindow,接着寻找发现它是Window类型的,在Activityattach方法找到了它的初始化,是new了一个PhoneWindow对象实例。

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) {
        attachBaseContext(context);
        ......
        mWindow = new PhoneWindow(this, window, activityConfigCallback);
        ......
        }
复制代码

所以接下来来看这个PhoneWindow里的setContentView方法。

    @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.
        //判断mContentParent为空调用 installDecor 方法
        if (mContentParent == null) {
            //装载DecorView
            installDecor();
        } else if (!hasFeature(FEATURE_CONTENT_TRANSITIONS)) {
            //清除mContentParent上所有子View
            mContentParent.removeAllViews();
        }

        if (hasFeature(FEATURE_CONTENT_TRANSITIONS)) {
            final Scene newScene = Scene.getSceneForLayout(mContentParent, layoutResID,
                    getContext());
            transitionTo(newScene);
        } else {
            //将传入的布局添加到mContentParent上
            mLayoutInflater.inflate(layoutResID, mContentParent);
        }
        mContentParent.requestApplyInsets();
        final Callback cb = getCallback();
        if (cb != null && !isDestroyed()) {
            cb.onContentChanged();
        }
        mContentParentExplicitlySet = true;
    }
复制代码

方法中看到会先判断mContentParent是否为空,为空则调用installDecor方法,不为空就再判断是否有转场动画,没有就清除mContentParent下所有子View。继续向下还是判断转场动画,一般没有就会走到mLayoutInflater.inflate(layoutResID, mContentParent);这行,将我们在setContentView中传进来的布局添加到mContentParent之中。也就就是说我们传入的布局最终是加载到mContentParent上,所以紧接着看mContentParent是个啥?

    private ViewGroup mContentParent;
复制代码

很容易能找到mContentParent是一个ViewGroup,那么接下来进入installDecor方法,看看mContentParent到底是个啥和它怎么初始化的。

    private void installDecor() {
        mForceDecorInstall = false;
        //判断mDecor为空就创建一个mDecor
        if (mDecor == null) {
            mDecor = generateDecor(-1);
            mDecor.setDescendantFocusability(ViewGroup.FOCUS_AFTER_DESCENDANTS);
            mDecor.setIsRootNamespace(true);
            if (!mInvalidatePanelMenuPosted && mInvalidatePanelMenuFeatures != 0) {
                mDecor.postOnAnimation(mInvalidatePanelMenuRunnable);
            }
        } else {
            //不为空为mDecor与Window建立关联
            mDecor.setWindow(this);
        }
        //如果mContentParent为空就生成mContentParent
        if (mContentParent == null) {
            mContentParent = generateLayout(mDecor);
            .......
        }
    }
复制代码

这里省略了一些设置Window窗体属性、转场过渡和标题设置相关的代码只看主要的部分。installDecor方法中先判断mDecor是否为空,为空就调用generateDecor(-1)方法创建mDecor,不为空调用setWindow方法将mDecorWindow建立关联。那么再去源码里找找mDecor到底是个啥?

    // This is the top-level view of the window, containing the window decor.
    private DecorView mDecor;
复制代码

可以看到mDecor就是DecorView,根据注释它是Window中的顶级View。并且进入是DecorView查看,是DecorView实际上是继承了一个FramLayout

    public class DecorView extends FrameLayout implements RootViewSurfaceTaker, WindowCallbacks {
    }
复制代码

接下来看generateDecor方法来看下是怎么创建这个DecorView的。

protected DecorView generateDecor(int featureId) {
        Context context;
        if (mUseDecorContext) {
            Context applicationContext = getContext().getApplicationContext();
            if (applicationContext == null) {
                context = getContext();
            } else {
                context = new DecorContext(applicationContext, getContext());
                if (mTheme != -1) {
                    context.setTheme(mTheme);
                }
            }
        } else {
            context = getContext();
        }
        return new DecorView(context, featureId, this, getAttributes());
    }
复制代码

generateDecor方法里return直接new了个DecorView返回。并且将根据条件获取到的上下文context、方法传入的featureId、当前的窗口Window和窗口的属性通过构造传入建立了关联。再回到installDecor方法,mDecor获得之后,再判断mContentParent为空就调用generateLayout(mDecor)方法将刚获得mDecor传入,创建mContentParent

protected ViewGroup generateLayout(DecorView decor) {
        //获取窗体样式
        TypedArray a = getWindowStyle();
		......
        //设置窗体样式属性
        if (a.getBoolean(R.styleable.Window_windowNoTitle, false)) {
            // 无标题栏
            requestFeature(FEATURE_NO_TITLE);
        } else if (a.getBoolean(R.styleable.Window_windowActionBar, false)) {
            // 无标题栏就没有ActionBar
            requestFeature(FEATURE_ACTION_BAR);
        }

	......

        // Inflate the window decor.
        int layoutResource;
        int features = getLocalFeatures();
        // 根据不同的情况加载不同的布局文件
        if ((features & (1 << FEATURE_SWIPE_TO_DISMISS)) != 0) {
            layoutResource = R.layout.screen_swipe_dismiss;
            ......
        } else if ((features & ((1 << FEATURE_LEFT_ICON) | (1 << FEATURE_RIGHT_ICON))) != 0) {
				if (mIsFloating) {
					......
					layoutResource = res.resourceId;
				} else {
					layoutResource = R.layout.screen_title_icons;
				}
				......
        } else if ((features & ((1 << FEATURE_PROGRESS) | (1 << FEATURE_INDETERMINATE_PROGRESS))) != 0
                && (features & (1 << FEATURE_ACTION_BAR)) == 0) {
            layoutResource = R.layout.screen_progress;
        } else if ((features & (1 << FEATURE_CUSTOM_TITLE)) != 0) {
            if (mIsFloating) {
			    .......
                layoutResource = res.resourceId;
            } else {
                layoutResource = R.layout.screen_custom_title;
            }
			.......
        } else if ((features & (1 << FEATURE_NO_TITLE)) == 0) {
       
            if (mIsFloating) {
                .......
                layoutResource = res.resourceId;
            } else if ((features & (1 << FEATURE_ACTION_BAR)) != 0) {
                layoutResource = a.getResourceId(
                        R.styleable.Window_windowActionBarFullscreenDecorLayout,
                        R.layout.screen_action_bar);
            } else {
                layoutResource = R.layout.screen_title;
            }
        } else if ((features & (1 << FEATURE_ACTION_MODE_OVERLAY)) != 0) {
            layoutResource = R.layout.screen_simple_overlay_action_mode;
        } else {
            layoutResource = R.layout.screen_simple;
        }
        mDecor.startChanging();
        // 加载layoutResource布局文件到mDecor即DecorView中
        mDecor.onResourcesLoaded(mLayoutInflater, layoutResource);
        // 通过findViewById获得contentParent
        ViewGroup contentParent = (ViewGroup)findViewById(ID_ANDROID_CONTENT);
        if (contentParent == null) {
            throw new RuntimeException("Window couldn't find content container view");
        }

		......
        //其他的设置
        //DecorView的背景、标题等设置
        if (getContainer() == null) {
            final Drawable background;
            if (mBackgroundResource != 0) {
                background = getContext().getDrawable(mBackgroundResource);
            } else {
                background = mBackgroundDrawable;
            }
            mDecor.setWindowBackground(background);
            final Drawable frame;
            if (mFrameResource != 0) {
                frame = getContext().getDrawable(mFrameResource);
            } else {
                frame = null;
            }
            mDecor.setWindowFrame(frame);
            mDecor.setElevation(mElevation);
            mDecor.setClipToOutline(mClipToOutline);
            if (mTitle != null) {
                setTitle(mTitle);
            }
            if (mTitleColor == 0) {
                mTitleColor = mTextColor;
            }
            setTitleColor(mTitleColor);
        }
        mDecor.finishChanging();
        return contentParent;
    }
复制代码

generateLayout方法的代码很长,还是只看关键的,方法里首先是获取窗体的样式属性和设置这些样式属性,这里省略了很多代码,留了一个看的眼熟的FEATURE_NO_TITLE,一般我们想要设置Activity去除标题栏,会在Activity中调用requestWindowFeature方法设置这个属性,而且要在setContentView方法之前调用,这里就能看出原因了,因为在setContentView方法的代码执行流程中就会在这里设置这个属性。

接着generateLayout方法根据不同的情况给DecorView选择加载不同的布局文件,调用mDecor.onResourcesLoaded方法将layoutResource传入加载,接着通过findViewById方法获取到最终返回的contentParent,最后进行背景、标题等其他的设置。

获得contentParent通过findViewById传入的idID_ANDROID_CONTENT,成员变量里可以找到具体的值。

    public static final int ID_ANDROID_CONTENT = com.android.internal.R.id.content;
复制代码

然后再进入mDecor.onResourcesLoaded方法,看一下具体是怎么加载的。

   void onResourcesLoaded(LayoutInflater inflater, int layoutResource) {
        ......
        mDecorCaptionView = createDecorCaptionView(inflater);
        final View root = inflater.inflate(layoutResource, null);
        if (mDecorCaptionView != null) {
            if (mDecorCaptionView.getParent() == null) {
                addView(mDecorCaptionView,
                        new ViewGroup.LayoutParams(MATCH_PARENT, MATCH_PARENT));
            }
            mDecorCaptionView.addView(root,
                    new ViewGroup.MarginLayoutParams(MATCH_PARENT, MATCH_PARENT));
        } else {
            addView(root, 0, new ViewGroup.LayoutParams(MATCH_PARENT, MATCH_PARENT));
        }
        mContentRoot = (ViewGroup) root;
        initializeElevation();
    }
复制代码

在这个方法中将传入的layout布局文件通过inflater.inflate获得对应名为rootView,再调用addView方法将得到的root添加到DecorView上,完成了加载。完成了加载流程再去看看加载的布局到底是什么样的,这里选择一个最简单R.layout.screen_simple的来看,进入screen_simple.xml

<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>
复制代码

screen_simple.xml中根布局是一个纵向的LinearLayout,其中分为两个区域,一个是ActionBar的区域,另一个是FrameLayout类型的内容的区域,这个内容的FrameLayoutid就是content就是之前ID_ANDROID_CONTENT的值,所以之前通过findViewById获得的contentParent就对应的是这个FrameLayout

setContentView的代码流程就是这些,这里再总结下:setContentView方法中主要做了DecorView的初始化,并将传入的布局添加到DecorView 上,调用DecorViewsetWindow方法将DecorView与窗体建立联系。DecorView本身继承FrameLayout,generateLayout方法将适合的布局文件添加到DecorView上,而这个布局是个LinearLayout且一般分为两个部分,一个放置ActionBar,另一个FrameLayout放置content,这个放置内容的布局contentParent就是放置从setContentView传入的布局。最终形成的布局层级如下图。

3、绘制流程的起点

看完了ActivitysetContentView方法明白了布局的层级,但是还是没找到有关开始绘制的内容。界面绘制的起点到底在哪里还是没有找到。所以接下来就只能从Activity的启动开始找了,在Activity的启动过程中一定会有方法开启界面绘制的流程。

Activity的启动流程又分为两种,一种是根Activity(即一个应用的第一个Activity)的启动流程另一种是普通Activity的启动流程。这其中会涉及到LauncherInstrumentationAMSApplicationThreadActivityThread等之间的通信。由于其中大部分与界面绘制关系不大,不是重点,所以这里先不管,直接从ActivityThreadhandleLaunchActivity方法的源码开始看起。简单的来说知道Activity启动会走到ActivityThreadhandleLaunchActivity方法就行了。

private void handleLaunchActivity(ActivityClientRecord r, Intent customIntent, String reason) {
		......
	// 初始化WindowManagerGlobal
        WindowManagerGlobal.initialize();
        Activity a = performLaunchActivity(r, customIntent);
        if (a != null) {
            r.createdConfig = new Configuration(mConfiguration);
            reportSizeConfigurations(r);
            Bundle oldState = r.state;
            handleResumeActivity(r.token, false, r.isForward,
                    !r.activity.mFinished && !r.startsNotResumed, r.lastProcessedSeq, reason);
			......
        } else {
            ......
        }
    }
复制代码

handleLaunchActivity方法中省略其他的只看performLaunchActivityhandleResumeActivity这两个方法。先进入performLaunchActivity方法。

private Activity performLaunchActivity(ActivityClientRecord r, Intent customIntent) {
    // 获取ActivityInfo类
        ActivityInfo aInfo = r.activityInfo;
        if (r.packageInfo == null) {
            r.packageInfo = getPackageInfo(aInfo.applicationInfo, r.compatInfo,
                    Context.CONTEXT_INCLUDE_CODE);
        }
        ComponentName component = r.intent.getComponent();
        if (component == null) {
            component = r.intent.resolveActivity(
                mInitialApplication.getPackageManager());
            r.intent.setComponent(component);
        }
        if (r.activityInfo.targetActivity != null) {
            component = new ComponentName(r.activityInfo.packageName,
                    r.activityInfo.targetActivity);
        }
	// 创建Context
        ContextImpl appContext = createBaseContextForActivity(r);
        Activity activity = null;
        try {
	// 获取ClassLoader,通过ClassLoader创建Activity
            java.lang.ClassLoader cl = appContext.getClassLoader();
            activity = mInstrumentation.newActivity(
                    cl, component.getClassName(), r.intent);
			
			......
        }
        try {
	  // 创建Application
            Application app = r.packageInfo.makeApplication(false, mInstrumentation);
            ......

            if (activity != null) {
                ......
				
		//调用Activity的attach方法
                activity.attach(appContext, this, getInstrumentation(), r.token,
                        r.ident, app, r.intent, r.activityInfo, title, r.parent,
                        r.embeddedID, r.lastNonConfigurationInstances, config,
                        r.referrer, r.voiceInteractor, window, r.configCallback);
				......
				
		//调用Activity的onCreate方法
                if (r.isPersistable()) {
                    mInstrumentation.callActivityOnCreate(activity, r.state, r.persistentState);
                } else {
                    mInstrumentation.callActivityOnCreate(activity, r.state);
                }
                ......
				
                if (!r.activity.mFinished) {
		//调用Activity的onStart方法
                    activity.performStart();
                    r.stopped = false;
                }
               ......

        return activity;
    }
复制代码

performLaunchActivity方法中主要进行了这几件事,先是创建Context上下文,然后通过ClassLoader创建了所需的Activity,接着创建Application,之后判断Activity不为空的话,依次调用Activity中的方法attachonCreateonStart方法。

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) {
        attachBaseContext(context);
        mFragments.attachHost(null /*parent*/);
        mWindow = new PhoneWindow(this, window, activityConfigCallback);
        mWindow.setWindowControllerCallback(this);
        mWindow.setCallback(this);
		......
        
		mWindow.setWindowManager(
                (WindowManager)context.getSystemService(Context.WINDOW_SERVICE),
                mToken, mComponent.flattenToString(),
                (info.flags & ActivityInfo.FLAG_HARDWARE_ACCELERATED) != 0);
        if (mParent != null) {
            mWindow.setContainer(mParent.getWindow());
        }
        mWindowManager = mWindow.getWindowManager();
        ......
    }
复制代码

这里之前看过,在Activityattach方法中创建了Window,设置了Window各项属性,获得绑定了WindowManager

  public void callActivityOnCreate(Activity activity, Bundle icicle,PersistableBundle persistentState) {
        prePerformCreate(activity);
        activity.performCreate(icicle, persistentState);
        postPerformCreate(activity);
    }
复制代码

mInstrumentation.callActivityOnCreate方法中调用activity.performCreate方法,进而调用了ActivityonCreate方法,因此setContentView方法也会被调用,DecorView以上布局被创建关联。

final void performCreate(Bundle icicle, PersistableBundle persistentState) {
        restoreHasCurrentPermissionRequest(icicle);
        onCreate(icicle, persistentState);
        mActivityTransitionState.readState(icicle);
        performCreateCommon();
    }
复制代码

performStart方法最终会调用到onStart方法。

    final void performStart(String reason) {
        ......
        mInstrumentation.callActivityOnStart(this);
        ......
    }
复制代码
public void callActivityOnStart(Activity activity) {
        activity.onStart();
}
复制代码

从这开始我们熟悉的Activity的生命周期方法就开始调用执行。接下来回到handleLaunchActivity方法中继续进入下一个handleResumeActivity方法。

 final void handleResumeActivity(IBinder token,
            boolean clearHide, boolean isForward, boolean reallyResume, int seq, String reason) {
        ActivityClientRecord r = mActivities.get(token);
		......
        
	// 这个方法里会调用Activity的onResume方法
        r = performResumeActivity(token, clearHide, reason);
        if (r != null) {
			......
			
            if (r.window == null && !a.mFinished && willBeVisible) {
		//通过activity获取到window
                r.window = r.activity.getWindow();
		//通过window获取到DecorView
                View decor = r.window.getDecorView();
                decor.setVisibility(View.INVISIBLE);
		//获取到WindowManager
                ViewManager wm = a.getWindowManager();
                WindowManager.LayoutParams l = r.window.getAttributes();
                a.mDecor = decor;
                l.type = WindowManager.LayoutParams.TYPE_BASE_APPLICATION;
                l.softInputMode |= forwardBit;
                if (r.mPreserveWindow) {
                    a.mWindowAdded = true;
                    r.mPreserveWindow = false;
		// 通过DecorView获取到ViewRootImpl
                    ViewRootImpl impl = decor.getViewRootImpl();
                    if (impl != null) {
                        impl.notifyChildRebuilt();
                    }
                }
                if (a.mVisibleFromClient) {
                    if (!a.mWindowAdded) {
                        a.mWindowAdded = true;
		    // 调用WindowManager.addView方法将DecorView添加到Window
                        wm.addView(decor, l);
                    } else {
                        a.onWindowAttributesChanged(l);
                    }
                }
		......
    }
复制代码

这个方法中调用performResumeActivity方法,此方法里会调用ActivityonResume生命周期方法,此时WindowDecorViewWindowManager对象都已经创建好了,并且WindowDecorView已经关联,这时调用WindowManageraddView方法将DecorView传入添加到窗体。这里WindowManager的实现类是WindowManagerImpl类进入去看addView方法。

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

WindowManagerImpladdView方法又调用了mGlobal.addView方法,这个mGlobal是一个WindowManagerGlobal类型,这里使用了桥接模式,在WindowManagerImpl中并没有实现对Window的进行操作,操作Window的方法都在WindowManagerGlobal类里实现。继续看WindowManagerGlobal中的addView方法。

public void addView(View view, ViewGroup.LayoutParams params,
            Display display, Window parentWindow) {
		......
        ViewRootImpl root;
        View panelParentView = null;
        synchronized (mLock) {
	......
            root = new ViewRootImpl(view.getContext(), display);
            ......
            try {
                root.setView(view, wparams, panelParentView);
            } catch (RuntimeException e) {
                // BadTokenException or InvalidDisplayException, clean up.
                if (index >= 0) {
                    removeViewLocked(index, true);
                }
                throw e;
            }
        }
    }
复制代码

方法中创建了ViewRootImpl并且调用了其setView方法将DecorView传入,进入setView方法查看。

	 public void setView(View view, WindowManager.LayoutParams attrs, View panelParentView) {
        synchronized (this) {
            if (mView == null) {
                mView = view;
		......
		requestLayout();
                ......
                try {
                    .......
                    res = mWindowSession.addToDisplay(mWindow, mSeq, mWindowAttributes,
                            getHostVisibility(), mDisplay.getDisplayId(), mWinFrame,
                            mAttachInfo.mContentInsets, mAttachInfo.mStableInsets,
                            mAttachInfo.mOutsets, mAttachInfo.mDisplayCutout, mInputChannel);
                } 
                  
        }
    }
复制代码

setView方法里主要来看requestLayoutmWindowSession.addToDisplay这两个方法。这里的mWindowSessionIWindowSession类型,是一个Binder对象,通过IWindowSessionWMS进行交互最终完成窗体Window的添加显示。而我们一直寻找的绘制流程的起点就在requestLayout方法中。

 @Override
    public void requestLayout() {
        if (!mHandlingLayoutInLayoutRequest) {
            checkThread();
            mLayoutRequested = true;
            scheduleTraversals();
        }
    }
复制代码

requestLayout方法里通过checkThread校验了线程,若UI主线程更新界面会抛出异常。

void checkThread() {
        if (mThread != Thread.currentThread()) {
            throw new CalledFromWrongThreadException(
                    "Only the original thread that created a view hierarchy can touch its views.");
        }
    }
复制代码

接着进入scheduleTraversals方法查看。

void scheduleTraversals() {
       ......
       mChoreographer.postCallback(
                    Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);
       ......
    }
复制代码

方法中post传入了一个Runnable继续来看这个mTraversalRunnable

final TraversalRunnable mTraversalRunnable = new TraversalRunnable();

final class TraversalRunnable implements Runnable {
        @Override
        public void run() {
            doTraversal();
        }
    }
复制代码

里面只调用了doTraversal方法。

void doTraversal() {
          ......
          performTraversals();
          ......
    }
复制代码

doTraversal方法里调用了performTraversals方法,其中会依次调用performMeasureperformLayoutperformDrawDecorView进行测量、布局、绘制,这里就是整个绘制流程的起点。于是对performTraversals方法进行查看。

 private void performTraversals() {
  ......
     int childWidthMeasureSpec = getRootMeasureSpec(mWidth, lp.width);
     int childHeightMeasureSpec = getRootMeasureSpec(mHeight, lp.height);
 ......
     performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);
 ......
     performLayout(lp, mWidth, mHeight);  
 ......
     performDraw();
 ......
     
 }
复制代码

performTraversals方法非常长,所以这里就只看performMeasureperformLayoutperformDraw这几个主要的绘制流程起始方法。先是performMeasure方法。

  private void performMeasure(int childWidthMeasureSpec, int childHeightMeasureSpec) {
        if (mView == null) {
            return;
        }
        Trace.traceBegin(Trace.TRACE_TAG_VIEW, "measure");
        try {
            mView.measure(childWidthMeasureSpec, childHeightMeasureSpec);
        } finally {
            Trace.traceEnd(Trace.TRACE_TAG_VIEW);
        }
    }
复制代码

performMeasure方法中很简单,就是调用了mViewmeasure方法开始测量。那么这个mView是哪个View呢?

//ViewRootImpl类中的成员变量
View mView;
复制代码
 public void setView(View view, WindowManager.LayoutParams attrs, View panelParentView) {
        synchronized (this) {
            if (mView == null) {
                mView = view;
                ......
         
     }
复制代码

很容易在代码里找到,这个成员变量mView是在setView方法中赋值,所以这个mView就是在WindowManagerGlobaladdView方法中传入的DecorVIew。由此可知performMeasure方法中通过调用顶级DecorViewmeasure方法开启了测量流程。

关于顶级DecorView测量还有一个问题,根据前一篇对ViewViewGroup的绘制流程分析我们知道了子Viewmeasure方法中传入的测量规格是由父ViewGroup调用getChildMeasureSpec方法,由父View的测量规格MeasureSpec和子View的布局参数LayoutParams共同决定的。那么顶级Viewmeasure方法传入的这两个childWidthMeasureSpecchildHeightMeasureSpec测量规格是哪儿来的呢?于是再看代码发现这两个值是通过getRootMeasureSpec方法获得的,于是来看这个方法。

private static int getRootMeasureSpec(int windowSize, int rootDimension) {
        int measureSpec;
        switch (rootDimension) {
        case ViewGroup.LayoutParams.MATCH_PARENT:
            // Window can't resize. Force root view to be windowSize.
            measureSpec = MeasureSpec.makeMeasureSpec(windowSize, MeasureSpec.EXACTLY);
            break;
        case ViewGroup.LayoutParams.WRAP_CONTENT:
            // Window can resize. Set max size for root view.
            measureSpec = MeasureSpec.makeMeasureSpec(windowSize, MeasureSpec.AT_MOST);
            break;
        default:
            // Window wants to be an exact size. Force root view to be that size.
            measureSpec = MeasureSpec.makeMeasureSpec(rootDimension, MeasureSpec.EXACTLY);
            break;
        }
        return measureSpec;
    }
复制代码

getRootMeasureSpec方法的两个参数分别表示窗体有效的大小windowSizeWindowManager.LayoutParams中的宽高尺寸rootDimension,方法中根据rootDimension来确定最终返回的测量规格。rootDimensionMATCH_PARENT则测量大小就为窗体大小,测量模式为EXACTLYrootDimensionWRAP_CONTENT则测量大小最大为窗体大小,测量模式为AT_MOST。除此之外的情况即具体数值,测量大小就为rootDimension,测量模式为EXACTLY。从这里可以看出顶级View的测量规格因为其没有父View,所以与Window大小是相关联的。

接下来看performLayout方法。

private void performLayout(WindowManager.LayoutParams lp, int desiredWindowWidth,
        int desiredWindowHeight) {
        mLayoutRequested = false;
        mScrollMayChange = true;
        //layout标记位
        mInLayout = true;
        //这个mView就是DecorView
        final View host = mView;
        if (host == null) {
            return;
        }
        if (DEBUG_ORIENTATION || DEBUG_LAYOUT) {
            Log.v(mTag, "Laying out " + host + " to (" +
                    host.getMeasuredWidth() + ", " + host.getMeasuredHeight() + ")");
        }

        Trace.traceBegin(Trace.TRACE_TAG_VIEW, "layout");
        try {
            //调用layout方法
            host.layout(0, 0, host.getMeasuredWidth(), host.getMeasuredHeight());
            //layout结束修改标志位
            mInLayout = false;
            //layoutRequest数量,即layout时调用requestLayout方法的View数量
            int numViewsRequestingLayout = mLayoutRequesters.size();
            if (numViewsRequestingLayout > 0) {
                // requestLayout() was called during layout.
                // If no layout-request flags are set on the requesting views, there is no problem.
                // If some requests are still pending, then we need to clear those flags and do
                // a full request/measure/layout pass to handle this situation.
                //调用requestLayout方法的View集合
                ArrayList<View> validLayoutRequesters = getValidLayoutRequesters(mLayoutRequesters,
                        false);
                if (validLayoutRequesters != null) {
                    // Set this flag to indicate that any further requests are happening during
                    // the second pass, which may result in posting those requests to the next
                    // frame instead
                    mHandlingLayoutInLayoutRequest = true;

                    //处理新的requestLayout
                    int numValidRequests = validLayoutRequesters.size();
                    for (int i = 0; i < numValidRequests; ++i) {
                        final View view = validLayoutRequesters.get(i);
                        Log.w("View", "requestLayout() improperly called by " + view +
                                " during layout: running second layout pass");
                        view.requestLayout();
                    }
                    //再次measure
                    measureHierarchy(host, lp, mView.getContext().getResources(),
                            desiredWindowWidth, desiredWindowHeight);
                    mInLayout = true;
                    //再次layout
                    host.layout(0, 0, host.getMeasuredWidth(), host.getMeasuredHeight());

                    mHandlingLayoutInLayoutRequest = false;

                    //再次获得调用requestLayout方法的View集合
                    validLayoutRequesters = getValidLayoutRequesters(mLayoutRequesters, true);
                    if (validLayoutRequesters != null) {
                        final ArrayList<View> finalRequesters = validLayoutRequesters;
                        //第二次之后还有requestLayout请求,则发送给下一帧
                        getRunQueue().post(new Runnable() {
                            @Override
                            public void run() {
                                int numValidRequests = finalRequesters.size();
                                for (int i = 0; i < numValidRequests; ++i) {
                                    final View view = finalRequesters.get(i);
                                    Log.w("View", "requestLayout() improperly called by " + view +
                                            " during second layout pass: posting in next frame");
                                    view.requestLayout();
                                }
                            }
                        });
                    }
                }

            }
        } finally {
            Trace.traceEnd(Trace.TRACE_TAG_VIEW);
        }
        mInLayout = false;
    }
复制代码

performMeasure方法一样performLayout方法中同样调用了DecorViewlayout方法,开启了DecorView的布局流程。最后是performDraw方法。

    private void performDraw() {
         ......
        //是否需要重绘
        final boolean fullRedrawNeeded = mFullRedrawNeeded || mReportNextDraw;
        mFullRedrawNeeded = false;

        mIsDrawing = true;
        Trace.traceBegin(Trace.TRACE_TAG_VIEW, "draw");

        boolean usingAsyncReport = false;
        if (mReportNextDraw && mAttachInfo.mThreadedRenderer != null
                && mAttachInfo.mThreadedRenderer.isEnabled()) {
            usingAsyncReport = true;
            mAttachInfo.mThreadedRenderer.setFrameCompleteCallback((long frameNr) -> {
                // TODO: Use the frame number
                pendingDrawFinished();
            });
        }

        try {
            //调用draw方法
            boolean canUseAsync = draw(fullRedrawNeeded);
            if (usingAsyncReport && !canUseAsync) {
                mAttachInfo.mThreadedRenderer.setFrameCompleteCallback(null);
                usingAsyncReport = false;
            }
        } finally {
            mIsDrawing = false;
            Trace.traceEnd(Trace.TRACE_TAG_VIEW);
        }

        .......
    }

复制代码

performDraw方法里开始会有许多窗体信息和硬件加速渲染的相关逻辑,这里省略只关心核心的软件绘制流程。主要的核心就是调用了draw方法,注意这里的draw方法不是Viewdraw方法,是ViewRootImpl里的draw方法。

      private boolean draw(boolean fullRedrawNeeded) {
        //获得Surface
        Surface surface = mSurface;
        if (!surface.isValid()) {
            return false;
        }
       ......
        //获得绘制区域
        final Rect dirty = mDirty;
        if (mSurfaceHolder != null) {
            dirty.setEmpty();
            if (animating && mScroller != null) {
                mScroller.abortAnimation();
            }
            return false;
        }
        //根据方法传入的fullRedrawNeeded设置重绘区域
        if (fullRedrawNeeded) {
            mAttachInfo.mIgnoreDirtyState = true;
            dirty.set(0, 0, (int) (mWidth * appScale + 0.5f), (int) (mHeight * appScale + 0.5f));
        }
		......
		
         if (mAttachInfo.mThreadedRenderer != null && mAttachInfo.mThreadedRenderer.isEnabled()) {
               ......
	    //硬件加速绘制
            } else {
			......
		//这里调用了drawSoftware方法软件绘制
                if (!drawSoftware(surface, mAttachInfo, xOffset, yOffset,
                        scalingRequired, dirty, surfaceInsets)) {
                    return false;
                }
            }
		......
        return useAsyncReport;
    }
复制代码

ViewRootImpl里的draw方法里获得了一个Surfacedirty,这里的dirty表示需要重绘的区域,然后根据传入的fullRedrawNeeded如果为true则全部区域重新绘制。最后软件绘制调用drawSoftware方法。

 private boolean drawSoftware(Surface surface, AttachInfo attachInfo, int xoff, int yoff,
            boolean scalingRequired, Rect dirty, Rect surfaceInsets) {
        // Draw with software renderer.
        final Canvas canvas;
		......
            //锁定canvas区域为dirty
            canvas = mSurface.lockCanvas(dirty);
            // The dirty rectangle can be modified by Surface.lockCanvas()
            //noinspection ConstantConditions
            if (left != dirty.left || top != dirty.top || right != dirty.right
                    || bottom != dirty.bottom) {
                attachInfo.mIgnoreDirtyState = true;
            }
		......
        try {
            if (DEBUG_ORIENTATION || DEBUG_DRAW) {
                Log.v(mTag, "Surface " + surface + " drawing to bitmap w="
                        + canvas.getWidth() + ", h=" + canvas.getHeight());
                //canvas.drawARGB(255, 255, 0, 0);
            }
            // If this bitmap's format includes an alpha channel, we
            // need to clear it before drawing so that the child will
            // properly re-composite its drawing on a transparent
            // background. This automatically respects the clip/dirty region
            // or
            // If we are applying an offset, we need to clear the area
            // where the offset doesn't appear to avoid having garbage
            // left in the blank areas.
            if (!canvas.isOpaque() || yoff != 0 || xoff != 0) {
                canvas.drawColor(0, PorterDuff.Mode.CLEAR);
            }
            dirty.setEmpty();
            mIsAnimating = false;
            mView.mPrivateFlags |= View.PFLAG_DRAWN;
            ......
            //调用View的draw开始绘制
             mView.draw(canvas);
		    ......
        return true;
    }
复制代码

drawSoftware方法里主要是锁定获取画布区域,最终调用mView.drawDecorView.draw方法,开启绘制流程。

4、总结

至此一个Activity界面显示的绘制流程就梳理完了,也与之前讲的ViewViewGroup的绘制流程接上了,最后画了两张图总结了一下,方便理解这个流程。一张是Activity的布局层级图,另一张是绘制流程的方法调用流程图。

界面布局层次

绘制流程主要方法

关注下面的标签,发现更多相似文章
评论