Android View的绘制
1. 概述
我们在Android的开发工作中都在不停地跟View打交道,Android中的任何一个布局、控件其实都是直接或间接继承自View的,如TextView、Button、ImageView、ListView等。这些控件虽然是Android系统本身就提供好的,我们只需要拿过来使用就可以了,但多知道一些总是没有坏处的,接下来就将介绍View是如何被绘制到屏幕上的。
任何一个视图都不可能凭空突然出现在屏幕上,它们都是要经过非常科学的绘制流程后才能显示出来的。每一个视图的绘制过程都必须经历三个最主要的阶段,即onMeasure()、onLayout()和onDraw(),这也是我们最主要的介绍部分,但在开始之前我们应当先了解一下Android的窗口结构。
2. Android的窗口结构
简介
先来看一张图:
Android的窗口结构Activity
- Activity并不负责视图控制(例如添加或者删除view),它只是控制生命周期和处理事件。
- 每一个Activity都包含一个根Window对象,Window才是真正代表一个窗口,Window对象通常由PhoneWindow实现
- Activity更像一个控制器,统筹视图的添加与显示,以及通过其他回调方法,来与Window、以及View进行交互。
Window(PhoneWindow)
Window的唯一实现类为PhoneWindow,而DecorView为PhoneWindow的内部类,PhoneWindow持有该内部类对象mDecor,所以真正持有和控制视图的是PhoneWindow
DecorView
- DecorView是FrameLayout的子类
- 是当前activity中所有view的祖先,我们在activity中调用setVisible(boolean visiable)时,实际上就是设置DecorView的可见性
- DecorView是FrameLayout的子类,它可以被认为是Android视图树的根节点视图。DecorView作为顶级View,一般情况下它内部包含一个竖直方向的LinearLayout,在这个LinearLayout里面有上下三个部分,上面是个ViewStub,延迟加载的视图(应该是设置ActionBar,根据Theme设置),中间的是标题栏(根据Theme设置,有的布局没有),下面的是内容栏。
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:fitsSystemWindows="true">
<!-- Popout bar for action modes -->
<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:layout_width="match_parent"
android:layout_height="?android:attr/windowTitleSize"
style="?android:attr/windowTitleBackgroundStyle">
<TextView android:id="@android:id/title"
style="?android:attr/windowTitleStyle"
android:background="@null"
android:fadingEdge="horizontal"
android:gravity="center_vertical"
android:layout_width="match_parent"
android:layout_height="match_parent" />
</FrameLayout>
<FrameLayout android:id="@android:id/content"
android:layout_width="match_parent"
android:layout_height="0dip"
android:layout_weight="1"
android:foregroundGravity="fill_horizontal|top"
android:foreground="?android:attr/windowContentOverlay" />
</LinearLayout>
我们在Activity中通过setContentView所设置的布局文件其实就是被加到内容栏之中的,成为其唯一子View,就是上面的id为content的FrameLayout中,在代码中可以通过content来得到对应加载的布局:
ViewGroup content = (ViewGroup)findViewById(android.R.id.content);
ViewGroup rootView = (ViewGroup)content.getChildAt(0);
-
还有一些其他功能:
-
作为PhoneWindow与ViewRoot之间的桥梁,ViewRoot通过DecorView设置窗口属性。
View view = getWindow().getDecorView();
-
分发ViewRoot分发来的key、touch、trackball等外部事件;
-
ViewRootImpl
ViewRootImpl可能听起来比较陌生,但他十分重要。所有View的绘制以及事件分发等交互都是通过它来执行或传递的。
但一定要注意,它不是View的子类或父类。对于结构而言,在大部分正常情况下,一颗ViewTree的根节点往往是DecorView,而DecorView的根则是PhoneWindow,跟ViewRoot(ViewRootImpl)真没什么关系。
感兴趣的话,可以看一段代码(位于WIndowManagerGolobal.java中):
public void addView(View view, ViewGroup.LayoutParams params, Display display, Window parentWindow) { ...... ViewRootImpl root; View panelParentView = null; synchronized (mLock) { // Start watching for system property changes. ...... root = new ViewRootImpl(view.getContext(), display); view.setLayoutParams(wparams); mViews.add(view); mRoots.add(root); mParams.add(wparams); } // do this last because it fires off messages to start doing things try { root.setView(view, wparams, panelParentView); } catch (RuntimeException e) { // BadTokenException or InvalidDisplayException, clean up. synchronized (mLock) { final int index = findViewLocked(view, false); if (index >= 0) { removeViewLocked(index, true); } } throw e; } }
可以发现,ViewRootImpl是在Framework层的WindowManagerGlobal中初始化的,其中包含了三个ArrayList(在旧版本中实现是数组),其中mViews代表DecorView,mRoots代表ViewRoot。
从这里可以看出,对于应用层来说,ViewRootImpl实际上和DecorView显然没有像ViewTree里父子节点那种包含关系。而后面的root.setView(view, wparams, panelParentView),更是证明了,DecorView的parentView也不是ViewRoot,而实际上是PhoneWindow。
实际上,ViewRoot的真正的作用其实是作为一个DecorView的“管理者”,它本身并不是一个视图节点,或许被叫作ViewTreeManager才更为合适,本质上是一个管理类。ViewRootImpl还要负责处理应用层与底层WindowManagerService交互事件
3. 从setContentView讲起
接下我们就从一个常见的方法中去认知之前提到这些类之间的关系,那就是activity里面的setContentView,是我们平常把布局内容显示到界面上的一个方法:activity.setContentView
public void setContentView(@LayoutRes int layoutResID) {
getWindow().setContentView(layoutResID);
initWindowDecorActionBar();
}
里面方法调用了getWindow().setContentView,而这个getWindow方法获取的就是Activity上的Window(phoneWindow)
/**
* Retrieve the current {@link android.view.Window} for the activity.
* This can be used to directly access parts of the Window API that
* are not available through Activity/Screen.
*
* @return Window The current window, or null if the activity is not
* visual.
*/
public Window getWindow() {
return mWindow;
}
注释中说的很明确,可以看到如果当前mWindow为null的话,则表示当前Activity不在窗口上。
之前的mWindow.setContentView,实际上调用到的是它的实现类方法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.
if (mContentParent == null) {
//创建DecorView,并添加到mContentParent上
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 {
//将要加载的资源添加到mContentParent上
mLayoutInflater.inflate(layoutResID, mContentParent);
}
mContentParent.requestApplyInsets();
final Callback cb = getCallback();
if (cb != null && !isDestroyed()) {
//回调通知表示完成界面加载
cb.onContentChanged();
}
}
如果当前内容还未放置到窗口,则此时mContentParent==null,也就是第一次调用的时候会调用installDecor方法。FEATURE_CONTENT_TRANSITIONS,则是标记当前内容加载有没有使用过渡(转场)动画。如果内容已经加载过,并且不需要动画,则会调用removeAllViews。
添加完Content后如有设置了FEATURE_CONTENT_TRANSITIONS则添加Scene来过度启动。否则mLayoutInflater.inflate(layoutResID, mContentParent);将我们的资源文件通过LayoutInflater对象转换为View树,并且添加至mContentParent视图中。
既然第一次启动会调用到installDecor,从字面上看可以知道该方法用来添加DecorView:
private void installDecor() {
if (mDecor == null) {
//调用该方法创建new一个DecorView
mDecor = generateDecor();
mDecor.setDescendantFocusability(ViewGroup.FOCUS_AFTER_DESCENDANTS);
mDecor.setIsRootNamespace(true);
if (!mInvalidatePanelMenuPosted && mInvalidatePanelMenuFeatures != 0) {
mDecor.postOnAnimation(mInvalidatePanelMenuRunnable);
}
}
//一开始DecorView未加载到mContentParent,所以此时mContentParent=null
if (mContentParent == null) {
//该方法将mDecorView添加到Window上绑定布局
mContentParent = generateLayout(mDecor);
// Set up decor part of UI to ignore fitsSystemWindows if appropriate.
mDecor.makeOptionalFitsSystemWindows();
final DecorContentParent decorContentParent = (DecorContentParent) mDecor.findViewById(
R.id.decor_content_parent);
...//添加其他资源
...//设置转场动画
}
}
所以过程大致是,先通过generateDecor创建DecorView:
protected DecorView generateDecor() {
return new DecorView(getContext(), -1);
}
创建完后再通过调用generateLayout将setContentView的内容赋值到mContentParent,这个方法有点长:
protected ViewGroup generateLayout(DecorView decor) {
// Apply data from current theme.
//根据当前设置的主题来加载默认布局
TypedArray a = getWindowStyle();
//如果你在theme中设置了window_windowNoTitle,则这里会调用到,其他方法同理,
//这里是根据你在theme中的设置去设置的
if (a.getBoolean(R.styleable.Window_windowNoTitle, false)) {
requestFeature(FEATURE_NO_TITLE);
} else if (a.getBoolean(R.styleable.Window_windowActionBar, false)) {
// Don't allow an action bar if there is no title.
requestFeature(FEATURE_ACTION_BAR);
}
//是否有设置全屏
if (a.getBoolean(R.styleable.Window_windowFullscreen, false)) {
setFlags(FLAG_FULLSCREEN, FLAG_FULLSCREEN & (~getForcedWindowFlags()));
}
...//省略其他加载资源
// 添加布局到DecorView,前面说到,DecorView是继承与FrameLayout,它本身也是一个ViewGroup,而我们前面创建它的时候,只是调用了new DecorView,此时里面并无什么东西。而下面的步骤则是根据用户设置的Feature来创建相应的默认布局主题。举个例子,如果我在setContentView之前调用了requestWindowFeature(Window.FEATURE_NO_TITLE),这里则会通过getLocalFeatures来获取你设置的feature,进而选择加载对应的布局,此时则是加载没有标题栏的主题,对应的就是R.layout.screen_simple
int layoutResource;
int features = getLocalFeatures();
// System.out.println("Features: 0x" + Integer.toHexString(features));
if ((features & (1 << FEATURE_SWIPE_TO_DISMISS)) != 0) {
layoutResource = R.layout.screen_swipe_dismiss;
} ... //省略其他判断方法
} else {
// Embedded, so no decoration is needed.
layoutResource = R.layout.screen_simple;
// System.out.println("Simple!");
}
mDecor.startChanging();
//选择对应布局创建添加到DecorView中
View in = mLayoutInflater.inflate(layoutResource, null);
decor.addView(in, new ViewGroup.LayoutParams(MATCH_PARENT, MATCH_PARENT));
mContentRoot = (ViewGroup) in;
//设置contentParent
ViewGroup contentParent = (ViewGroup)findViewById(ID_ANDROID_CONTENT);
...
return contentParent;
}
总结起来就是,首先generateLayout会根据当前用户设置的主题去设置对应的Feature,接着根据对应的Feature来选择加载对应的布局文件,接下来通过getLocalFeatures来获取你设置的feature,进而选择加载对应的布局,这也就是为什么我们要在setContentView之前调用requesetFeature的原因。
正如我们之前给出的那个例子:
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:fitsSystemWindows="true">
<!-- Popout bar for action modes -->
<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:layout_width="match_parent"
android:layout_height="?android:attr/windowTitleSize"
style="?android:attr/windowTitleBackgroundStyle">
<TextView android:id="@android:id/title"
style="?android:attr/windowTitleStyle"
android:background="@null"
android:fadingEdge="horizontal"
android:gravity="center_vertical"
android:layout_width="match_parent"
android:layout_height="match_parent" />
</FrameLayout>
<FrameLayout android:id="@android:id/content"
android:layout_width="match_parent"
android:layout_height="0dip"
android:layout_weight="1"
android:foregroundGravity="fill_horizontal|top"
android:foreground="?android:attr/windowContentOverlay" />
</LinearLayout>
“DecorView只有一个子元素为LinearLayout,代表整个Window界面,包含通知栏、标题栏、内容显示栏三块区域。”要注意FrameLayout里面的id,@android:id/content ,我们setContentView的内容就是添加到这个FrameLayout中。
generateLayout的返回是contentParent,而它的获取则是ViewGroup contentParent = (ViewGroup)findViewById(ID_ANDROID_CONTENT);
public static final int ID_ANDROID_CONTENT = com.android.internal.R.id.content;
正好对应id为content的FrameLayout,之后我们setContentView则是添加在mContentParent上面了。
我们再回到前面的方法:
@Override
public void setContentView(int layoutResID) {
......
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();
}
}
此时已经创建完DecorView并且获取到mContentParent,接着就是将你setContentView的内容添加到mContentParent中,也就是:
mLayoutInflater.inflate(layoutResID, mContentParent);
// 或者
mContentParent.addView(view, params);
// 他们本质上是一样的
最后调用Callback来通知界面发生改变。Callback是Window里面的一个接口,里面声明了当界面更改触摸时调用的各种方法。
这里的话,我们看下onContentChanged,虽然在PhoneWindow里面并没有看到onContentChanged的实现类,但我们知道Activity本身又是加载在Window上的,所以来看下Activity:
public class Activity extends ContextThemeWrapper
implements LayoutInflater.Factory2,
Window.Callback, KeyEvent.Callback,
OnCreateContextMenuListener, ComponentCallbacks2,
Window.OnWindowDismissedCallback
{
...
}
可以看到Activity里面实现了Window.Callback接口,并且
public void onContentChanged() {
}
onContentChanged方法为空,所以我们可以通过重写该方法来监听布局内容的改变
总结起来就是,在调用setContentView()方法后,DecorView被初始化,用户视图也被挂载到DecorView上
4. DecorView的显示和ViewRootImpl
经过上一部分的步骤,DecorView就已经被建立起来了。但大家应该都知道,界面虽然经过了setContentView()的设置,但要等到onResume()之后才对用户可见。
Activity的生命周期大家都已经学过,所以不做过多的介绍了。
偷一张巨佬的图(关于Activity启动和Window的绑定我放在了补充里,有兴趣的筒子们可以自行去查看,不然感觉实在太多了...)
在我们想要开启一个Activity的时候,ActivityThread的handleLaunchActivity()会在Handler中被调用,那我们就来看一看这个方法:
private void handleLaunchActivity(ActivityClientRecord r, Intent customIntent) {
//就是在这里调用了Activity.attach(),接着调用了Activity.onCreate()和Activity.onStart()生命周期,但是由于只是初始化了mDecor,添加了布局文件,还没有把mDecor添加到负责UI显示的PhoneWindow中,所以这时候对用户来说,是不可见的
Activity a = performLaunchActivity(r, customIntent);
......
if (a != null) {
//这里面执行了Activity.onResume()
handleResumeActivity(r.token, false, r.isForward,
!r.activity.mFinished && !r.startsNotResumed);
if (!r.activity.mFinished && r.startsNotResumed) {
try {
r.activity.mCalled = false;
//执行Activity.onPause()
mInstrumentation.callActivityOnPause(r.activity);
}
}
}
}
我们重点来看一下handleResumeActivity()做了什么:
final void handleResumeActivity(IBinder token,
boolean clearHide, boolean isForward, boolean reallyResume) {
//此时Activity.onResume()已经被调用,但界面还是不可见的
ActivityClientRecord r = performResumeActivity(token, clearHide);
if (r != null) {
final Activity a = r.activity;
if (r.window == null && !a.mFinished && willBeVisible) {
r.window = r.activity.getWindow();
View decor = r.window.getDecorView();
//decor对用户不可见
decor.setVisibility(View.INVISIBLE);
ViewManager wm = a.getWindowManager();
WindowManager.LayoutParams l = r.window.getAttributes();
a.mDecor = decor;
//WindowManager.LayoutParams的type为TYPE_BASE_APPLICATION
l.type = WindowManager.LayoutParams.TYPE_BASE_APPLICATION;
if (a.mVisibleFromClient) {
a.mWindowAdded = true;
//decor被添加进WindowManager了,但是这个时候还是不可见的
wm.addView(decor, l);
}
if (!r.activity.mFinished && willBeVisible
&& r.activity.mDecor != null && !r.hideForNow) {
//在这里,划重点!
if (r.activity.mVisibleFromClient) {
r.activity.makeVisible();
}
}
}
也就是说,其实在onResume()执行之后,界面还是不可见的,当我们执行了Activity.makeVisible()方法之后,界面才对我们是可见的。
void makeVisible() {
if (!mWindowAdded) {
ViewManager wm = getWindowManager();
// 将DecorView添加到WindowManager,此处十分重要
wm.addView(mDecor, getWindow().getAttributes());
mWindowAdded = true;
}
mDecor.setVisibility(View.VISIBLE);//DecorView可见
}
到此DecorView便可见,显示在屏幕中。但是在这其中,wm.addView(mDecor, getWindow().getAttributes())起到了重要的作用,因为其内部创建了一个ViewRootImpl对象,负责绘制显示各个子View。
下面我们具体来看addView()方法,因为WindowManager是个接口,具体是交给WindowManagerImpl来实现的:
public final class WindowManagerImpl implements WindowManager {
private final WindowManagerGlobal mGlobal = WindowManagerGlobal.getInstance();
...
@Override
public void addView(View view, ViewGroup.LayoutParams params) {
mGlobal.addView(view, params, mDisplay, mParentWindow);
}
}
可以发现,这里还是交给WindowManagerGlobal的addView()方法去实现
public void addView(View view, ViewGroup.LayoutParams params,
Display display, Window parentWindow) {
final WindowManager.LayoutParams wparams = (WindowManager.LayoutParams)params;
......
synchronized (mLock) {
ViewRootImpl root;
//实例化一个ViewRootImpl对象
root = new ViewRootImpl(view.getContext(), display);
view.setLayoutParams(wparams);
mViews.add(view);
mRoots.add(root);
mParams.add(wparams);
}
......
try {
//将DecorView交给ViewRootImpl
root.setView(view, wparams, panelParentView);
}catch (RuntimeException e) {
}
}
}
记不记得我们之前说过,WindowManagerGlobal中包含了三个ArrayList,其中mViews代表DecorView,mRoots代表ViewRoot。正是这个root.setView(view, wparams, panelParentView)方法,经过一系列调用,最终调用了performTraversals()方法。
我们来看ViewRootImpl类中的addView方法:
public void setView(View view, WindowManager.LayoutParams attrs, View panelParentView) {
synchronized (this) {
if (mView == null) {
//将顶层视图DecorView赋值给全局的mView
mView = view;
.............
//标记已添加DecorView
mAdded = true;
.............
//请求布局
requestLayout();
.............
}
}
继续跟踪requestLayout()方法
@Override
public void requestLayout() {
if (!mHandlingLayoutInLayoutRequest) {
checkThread();
mLayoutRequested = true;
scheduleTraversals(); // 再看这个
}
}
.......
final TraversalRunnable mTraversalRunnable = new TraversalRunnable(); // 跟踪
void scheduleTraversals() {
if (!mTraversalScheduled) {
mTraversalScheduled = true;
mTraversalBarrier = mHandler.getLooper().postSyncBarrier();
mChoreographer.postCallback(
// 这里传入将要执行遍历绘制的 runnable
Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);
if (!mUnbufferedInputDispatch) {
scheduleConsumeBatchedInput();
}
notifyRendererOfFramePending();
}
}
......
final class TraversalRunnable implements Runnable {
@Override
public void run() {
doTraversal(); // 继续跟踪
}
}
final TraversalRunnable mTraversalRunnable = new TraversalRunnable();
...............
void doTraversal() {
if (mTraversalScheduled) {
mTraversalScheduled = false;
mHandler.getLooper().removeSyncBarrier(mTraversalBarrier);
try {
performTraversals(); // 终于找到你,还好我没放弃!!
} finally {
Trace.traceEnd(Trace.TRACE_TAG_VIEW);
}
}
}
............
经历千辛万苦,我们终于接近了View树的绘制流程。这个绘制流程就是从ViewRootImpl类的performTraversals()方法开始的,这个方法主要是根据之前设置的状态,判断是否重新计算视图大小(measure)、是否重新放置视图的位置(layout)、以及是否重绘 (draw):
private void performTraversals() {
...
//最外层的根视图的widthMeasureSpec和heightMeasureSpec由来
//lp.width和lp.height在创建ViewGroup实例时等于MATCH_PARENT
int childWidthMeasureSpec = getRootMeasureSpec(mWidth, lp.width);
int childHeightMeasureSpec = getRootMeasureSpec(mHeight, lp.height);
// 执行测量操作
performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);
...
//执行布局操作
performLayout(lp, desiredWindowWidth, desiredWindowHeight);
...
//执行绘制操作
performDraw();
}
performTraversals()方法有700多行,实在是太长了,这里就先举一下最重要的三个。他会经过一系列复杂的调用,最终绘制出View,大体如下:
performTraversals大致流程那么接下来,我们就来分三个阶段介绍
5. 第一阶段:Measure
主体
话不多说,我们先来看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);
}
}
之前我们介绍过,在handleResumeActivity中,已经将Window.mDecor(也就是DecorView)传入了进来。
所以,这里的mView就是该ViewRootImpl指依赖的Activity中的DecorView,也已经介绍过DecorView是整个View树的最顶层,可以认为是View树的根,因此可以通过调用mView来绘制一个Acitivity组件的UI。
可以发现,在performMeasure方法中我们传入了两个参数childWidthMeasureSpec和childHeightMeasureSpec:
int childWidthMeasureSpec = getRootMeasureSpec(mWidth, lp.width);
int childHeightMeasureSpec = getRootMeasureSpec(mHeight, lp.height);
这里调用了getRootMeasureSpec()方法去获取widthMeasureSpec和heightMeasureSpec的值,注意方法中传入的参数,其中lp.width和lp.height在创建ViewGroup实例的时候就被赋值了,它们都等于MATCH_PARENT。然后看下getRootMeasureSpec()方法中的代码:
private int getRootMeasureSpec(int windowSize, int rootDimension) {
int measureSpec;
switch (rootDimension) {
case ViewGroup.LayoutParams.MATCH_PARENT:
measureSpec = MeasureSpec.makeMeasureSpec(windowSize, MeasureSpec.EXACTLY);
break;
case ViewGroup.LayoutParams.WRAP_CONTENT:
measureSpec = MeasureSpec.makeMeasureSpec(windowSize, MeasureSpec.AT_MOST);
break;
default:
measureSpec = MeasureSpec.makeMeasureSpec(rootDimension, MeasureSpec.EXACTLY);
break;
}
return measureSpec;
}
可以看到,这里使用了MeasureSpec.makeMeasureSpec()方法来组装一个MeasureSpec,当rootDimension参数等于MATCH_PARENT的时候,MeasureSpec的specMode就等于EXACTLY,当rootDimension等于WRAP_CONTENT的时候,MeasureSpec的specMode就等于AT_MOST。并且MATCH_PARENT和WRAP_CONTENT时的specSize都是等于windowSize的,这也是为什么根视图总是会充满全屏的。
- UNSPECIFIED:父容器不对子View有限制,子View要多大给多大,这种一般我们不会接触到
- EXACTLY: 表示精确模式,View的大小已经确认,为SpecSize所指定的值。
- AT_MOST:表示子View的大小不确认,指定了该子View最大可以为多少。子View可以在该范围内设定自己的大小。
介绍完参数,我们接下来来看view.measure()方法:
public final void measure(int widthMeasureSpec, int heightMeasureSpec) {
if ((mPrivateFlags & FORCE_LAYOUT) == FORCE_LAYOUT ||
widthMeasureSpec != mOldWidthMeasureSpec ||
heightMeasureSpec != mOldHeightMeasureSpec) {
mPrivateFlags &= ~MEASURED_DIMENSION_SET;
if (ViewDebug.TRACE_HIERARCHY) {
ViewDebug.trace(this, ViewDebug.HierarchyTraceType.ON_MEASURE);
}
// 调用onMeasure方法
onMeasure(widthMeasureSpec, heightMeasureSpec);
if ((mPrivateFlags & MEASURED_DIMENSION_SET) != MEASURED_DIMENSION_SET) {
throw new IllegalStateException("onMeasure() did not set the"
+ " measured dimension by calling"
+ " setMeasuredDimension()");
}
mPrivateFlags |= LAYOUT_REQUIRED;
}
mOldWidthMeasureSpec = widthMeasureSpec;
mOldHeightMeasureSpec = heightMeasureSpec;
}
注意观察,measure()这个方法是final的,因此我们无法在子类中去重写这个方法,说明Android是不允许我们改变View的measure框架的。然后在第9行调用了onMeasure()方法,这里才是真正去测量并设置View大小的地方,默认会调用getDefaultSize()方法来获取视图的大小:
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
setMeasuredDimension(getDefaultSize(getSuggestedMinimumWidth(), widthMeasureSpec),
getDefaultSize(getSuggestedMinimumHeight(), heightMeasureSpec));
}
public static int getDefaultSize(int size, int measureSpec) {
int result = size;
int specMode = MeasureSpec.getMode(measureSpec);
int specSize = MeasureSpec.getSize(measureSpec);
switch (specMode) {
case MeasureSpec.UNSPECIFIED:
result = size;
break;
case MeasureSpec.AT_MOST:
case MeasureSpec.EXACTLY:
result = specSize;
break;
}
return result;
}
// 建议的最小宽度和高度都是由View的Background尺寸与通过设置View的miniXXX属性共同决定的
protected int getSuggestedMinimumWidth() {
return (mBackground == null) ? mMinWidth : max(mMinWidth, mBackground.getMinimumWidth());
}
protected int getSuggestedMinimumHeight() {
return (mBackground == null) ? mMinHeight : max(mMinHeight, mBackground.getMinimumHeight());
}
这里传入的measureSpec是一直从measure()方法中传递过来的。然后调用MeasureSpec.getMode()方法可以解析出specMode,调用MeasureSpec.getSize()方法可以解析出specSize。接下来进行判断,如果specMode等于AT_MOST或EXACTLY就返回specSize。
之后会在onMeasure()方法中调用setMeasuredDimension()方法来设定测量出的大小,这样一次measure过程就结束了。
当然,一个界面的展示可能会涉及到很多次的measure,因为一个布局中一般都会包含多个子视图,每个视图都需要经历一次measure过程。ViewGroup中定义了一个measureChildren()方法来去测量子视图的大小,如下所示:
protected void measureChildren(int widthMeasureSpec, int heightMeasureSpec) {
final int size = mChildrenCount;
final View[] children = mChildren;
for (int i = 0; i < size; ++i) {
final View child = children[I];
if ((child.mViewFlags & VISIBILITY_MASK) != GONE) {
// 这里首先会去遍历当前布局下的所有子视图,然后逐个调用measureChild()方法来测量相应子视图的大小
measureChild(child, widthMeasureSpec, heightMeasureSpec);
}
}
}
protected void measureChild(View child, int parentWidthMeasureSpec,
int parentHeightMeasureSpec) {
// 获取布局文件中定义的多种属性
final LayoutParams lp = child.getLayoutParams();
// 计算子视图MeasureSpec
final int childWidthMeasureSpec = getChildMeasureSpec(parentWidthMeasureSpec,
mPaddingLeft + mPaddingRight, lp.width);
// 计算子视图MeasureSpec
final int childHeightMeasureSpec = getChildMeasureSpec(parentHeightMeasureSpec,
mPaddingTop + mPaddingBottom, lp.height);
child.measure(childWidthMeasureSpec, childHeightMeasureSpec);
}
可以看到,在measureChild()又调用了getChildMeasureSpec()方法来去计算子视图的MeasureSpec,计算的依据就是布局文件中定义的MATCH_PARENT、WRAP_CONTENT等值
public static int getChildMeasureSpec(int spec, int padding, int childDimension) {
//获取当前Parent View的Mode和Size
int specMode = MeasureSpec.getMode(spec);
int specSize = MeasureSpec.getSize(spec);
//获取Parent size与padding差值(也就是Parent剩余大小),若差值小于0直接返回0
int size = Math.max(0, specSize - padding);
//定义返回值存储变量
int resultSize = 0;
int resultMode = 0;
//依据当前Parent的Mode进行switch分支逻辑
switch (specMode) {
//默认Root View的Mode就是EXACTLY
case MeasureSpec.EXACTLY:
if (childDimension >= 0) {
//如果child的layout_wOrh属性在xml或者java中给予具体大于等于0的数值
//设置child的size为真实layout_wOrh属性值,mode为EXACTLY
resultSize = childDimension;
resultMode = MeasureSpec.EXACTLY;
} else if (childDimension == LayoutParams.MATCH_PARENT) {
//如果child的layout_wOrh属性在xml或者java中给予MATCH_PARENT
//设置child的size为size,mode为EXACTLY
resultSize = size;
resultMode = MeasureSpec.EXACTLY;
} else if (childDimension == LayoutParams.WRAP_CONTENT) {
//如果child的layout_wOrh属性在xml或者java中给予WRAP_CONTENT
//设置child的size为size,mode为AT_MOST
resultSize = size;
resultMode = MeasureSpec.AT_MOST;
}
break;
......
//其他Mode大体分支类似
}
//将mode与size通过MeasureSpec方法整合为32位整数返回
return MeasureSpec.makeMeasureSpec(resultSize, resultMode);
}
然后在最后调用子视图的measure()方法,并把计算出的MeasureSpec传递进去,之后的流程就和前面所介绍的一样了。
由此可见,视图大小的控制是由父视图、布局文件、以及视图本身共同完成的,父视图会提供给子视图参考的大小,而开发人员可以在XML文件中指定视图的大小,然后视图本身会对最终的大小进行拍板。
到此为止,我们就把视图绘制流程的第一阶段分析完了。
核心
- MeasureSpec(View的内部类)测量规格为int型,值由高2位规格模式specMode和低30位具体尺寸specSize组成。其中specMode只有三种值:
- MeasureSpec.EXACTLY:确定模式,父View希望子View的大小是确定的,由specSize决定;
- MeasureSpec.AT_MOST:最多模式,父View希望子View的大小最多是specSize指定的值;
- MeasureSpec.UNSPECIFIED:未指定模式,父View完全依据子View的设计值来决定;
- View的measure方法是final的,不允许重载,View子类只能重载onMeasure来完成自己的测量逻辑。
- 最顶层DecorView测量时的MeasureSpec是由ViewRootImpl中getRootMeasureSpec方法确定的(LayoutParams宽高参数均为MATCH_PARENT,specMode是EXACTLY,specSize为物理屏幕大小)。
- ViewGroup类提供了measureChild,measureChild和measureChildWithMargins方法,简化了父子View的尺寸计算。
- 只要是ViewGroup的子类就必须要求LayoutParams继承子MarginLayoutParams,否则无法使用layout_margin参数。
- View的布局大小由父View和子View共同决定。
- 使用View的getMeasuredWidth()和getMeasuredHeight()方法来获取View测量的宽高,必须保证这两个方法在onMeasure流程之后被调用才能返回有效值。
小补充1:重写onMeasure
之前我们提到了,measure方法是final的,所以我们无法通过重写他去修改测量方式,但onMeasure方法却是我们可以在子类中重写的,也就是说,如果你不想使用系统默认的测量方式,可以按照自己的意愿进行定制,比如:
public class MyView extends View {
......
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
setMeasuredDimension(70, 70);
}
}
这样的话就把View默认的测量流程覆盖掉了,不管在布局文件中定义MyView这个视图的大小是多少,最终在界面上显示的大小都将会是70*70。
小补充2:测量带有margin的子视图
protected void measureChildWithMargins(View child,
int parentWidthMeasureSpec, int widthUsed,
int parentHeightMeasureSpec, int heightUsed) {
//获取子视图的LayoutParams
final MarginLayoutParams lp = (MarginLayoutParams) child.getLayoutParams();
//调整MeasureSpec
//通过这两个参数以及子视图本身的LayoutParams来共同决定子视图的测量规格
final int childWidthMeasureSpec = getChildMeasureSpec(parentWidthMeasureSpec,
mPaddingLeft + mPaddingRight + lp.leftMargin + lp.rightMargin
+ widthUsed, lp.width);
final int childHeightMeasureSpec = getChildMeasureSpec(parentHeightMeasureSpec,
mPaddingTop + mPaddingBottom + lp.topMargin + lp.bottomMargin
+ heightUsed, lp.height);
//调运子View的measure方法,子View的measure中会回调子View的onMeasure方法
child.measure(childWidthMeasureSpec, childHeightMeasureSpec);
}
其实也很简单
6. 第二阶段:Layout
主体
measure过程结束后,视图的大小就已经测量好了,接下来就是layout的过程了。正如其名字所描述的一样,这个方法是用于给视图进行布局的,也就是确定视图的位置。performLayout()中会调用View.layout()方法来执行这个过程:
private void performLayout(WindowManager.LayoutParams lp, int desiredWindowWidth,
int desiredWindowHeight) {
......
host.layout(0, 0, host.mMeasuredWidth, host.mMeasuredHeight);
......
}
public void layout(int l, int t, int r, int b) {
int oldL = mLeft;
int oldT = mTop;
int oldB = mBottom;
int oldR = mRight;
// 判断视图大小是否变化
boolean changed = setFrame(l, t, r, b);
// 需要重新layout
if (changed || (mPrivateFlags & LAYOUT_REQUIRED) == LAYOUT_REQUIRED) {
if (ViewDebug.TRACE_HIERARCHY) {
ViewDebug.trace(this, ViewDebug.HierarchyTraceType.ON_LAYOUT);
}
// 跟踪
onLayout(changed, l, t, r, b);
mPrivateFlags &= ~LAYOUT_REQUIRED;
if (mOnLayoutChangeListeners != null) {
ArrayList<OnLayoutChangeListener> listenersCopy =
(ArrayList<OnLayoutChangeListener>) mOnLayoutChangeListeners.clone();
int numListeners = listenersCopy.size();
for (int i = 0; i < numListeners; ++i) {
listenersCopy.get(i).onLayoutChange(this, l, t, r, b, oldL, oldT, oldR, oldB);
}
}
}
mPrivateFlags &= ~FORCE_LAYOUT;
}
layout()方法接收四个参数,分别代表着左、上、右、下的坐标,当然这个坐标是相对于当前视图的父视图而言的。可以看到,这里还把刚才测量出的宽度和高度传到了layout()方法中:
在layout()方法中,首先会调用setFrame()方法来判断视图的大小是否发生过变化,以确定有没有必要对当前的视图进行重绘,同时还会在这里把传递过来的四个参数分别赋值给mLeft、mTop、mRight和mBottom这几个变量。接下来会在调用onLayout()方法,正如onMeasure()方法中的默认行为一样,也许你已经迫不及待地想知道onLayout()方法中的默认行为是什么样的了:
/**
* Called from layout when this view should
* assign a size and position to each of its children.
*
* Derived classes with children should override
* this method and call layout on each of
* their children.
* @param changed This is a new size or position for this view
* @param left Left position, relative to parent
* @param top Top position, relative to parent
* @param right Right position, relative to parent
* @param bottom Bottom position, relative to parent
*/
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
}
居然是空的...
这是因为onLayout()过程是为了确定视图在布局中所在的位置,而这个操作应该是由布局来完成的,即父视图决定子视图的显示位置。所以我们应该看看ViewGroup中的onLayout()方法:
@Override
protected abstract void onLayout(boolean changed, int l, int t, int r, int b);
可以看到,ViewGroup中的onLayout()方法竟然是一个抽象方法,这就意味着所有ViewGroup的子类都必须重写这个方法。像LinearLayout、RelativeLayout等布局,都是重写了这个方法,然后在内部按照各自的规则对子视图进行布局的。
这里我们用LinearLayout举个例子:
public class LinearLayout extends ViewGroup {
......
@Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
if (mOrientation == VERTICAL) {
layoutVertical(l, t, r, b);
} else {
layoutHorizontal(l, t, r, b);
}
}
}
原来还是分Vertical和Horizontal的,我们来简单看看Vertical了解一下吧:
void layoutVertical(int left, int top, int right, int bottom) {
final int paddingLeft = mPaddingLeft;
int childTop;
int childLeft;
//计算父窗口推荐的子View宽度
final int width = right - left;
//计算父窗口推荐的子View右侧位置
int childRight = width - mPaddingRight;
//child可使用空间大小
int childSpace = width - paddingLeft - mPaddingRight;
//通过ViewGroup的getChildCount方法获取ViewGroup的子View个数
final int count = getVirtualChildCount();
//获取Gravity属性设置
final int majorGravity = mGravity & Gravity.VERTICAL_GRAVITY_MASK;
final int minorGravity = mGravity & Gravity.RELATIVE_HORIZONTAL_GRAVITY_MASK;
//依据majorGravity计算childTop的位置值
switch (majorGravity) {
case Gravity.BOTTOM:
// mTotalLength contains the padding already
childTop = mPaddingTop + bottom - top - mTotalLength;
break;
// mTotalLength contains the padding already
case Gravity.CENTER_VERTICAL:
childTop = mPaddingTop + (bottom - top - mTotalLength) / 2;
break;
case Gravity.TOP:
default:
childTop = mPaddingTop;
break;
}
//开始遍历!
for (int i = 0; i < count; i++) {
final View child = getVirtualChildAt(i);
if (child == null) {
childTop += measureNullChild(i);
} else if (child.getVisibility() != GONE) {
//LinearLayout中其子视图显示的宽和高由measure过程来决定的,因此measure过程的意义就是为layout过程提供视图显示范围的参考值
final int childWidth = child.getMeasuredWidth();
final int childHeight = child.getMeasuredHeight();
//获取子View的LayoutParams
final LinearLayout.LayoutParams lp =
(LinearLayout.LayoutParams) child.getLayoutParams();
int gravity = lp.gravity;
if (gravity < 0) {
gravity = minorGravity;
}
final int layoutDirection = getLayoutDirection();
final int absoluteGravity = Gravity.getAbsoluteGravity(gravity, layoutDirection);
//依据不同的absoluteGravity计算childLeft位置
switch (absoluteGravity & Gravity.HORIZONTAL_GRAVITY_MASK) {
case Gravity.CENTER_HORIZONTAL:
childLeft = paddingLeft + ((childSpace - childWidth) / 2)
+ lp.leftMargin - lp.rightMargin;
break;
case Gravity.RIGHT:
childLeft = childRight - childWidth - lp.rightMargin;
break;
case Gravity.LEFT:
default:
childLeft = paddingLeft + lp.leftMargin;
break;
}
if (hasDividerBeforeChildAt(i)) {
childTop += mDividerHeight;
}
childTop += lp.topMargin;
//通过垂直排列计算调运child的layout设置child的位置
setChildFrame(child, childLeft, childTop + getLocationOffset(child),
childWidth, childHeight);
childTop += childHeight + lp.bottomMargin + getNextLocationOffset(child);
i += getChildrenSkipCount(child, i);
}
}
}
不难看出,一般情况下layout过程会参考measure过程中计算得到的mMeasuredWidth和mMeasuredHeight来安排子View在父View中显示的位置,但这不是必须的,measure过程得到的结果可能完全没有实际用处,特别是对于一些自定义的ViewGroup,其子View的个数、位置和大小都是固定的,这时候我们可以忽略整个measure过程,只在layout函数中传入的4个参数来安排每个子View的具体位置。
到此为止,我们把视图绘制流程的第二阶段也分析完了。
核心
- View.layout方法可被重载,ViewGroup.layout为final的不可重载,ViewGroup.onLayout为abstract的,子类必须重载实现自己的位置逻辑。
- measure操作完成后得到的是对每个View经测量过的measuredWidth和measuredHeight,layout操作完成之后得到的是对每个View进行位置分配后的mLeft、mTop、mRight、mBottom,这些值都是相对于父View来说的。
- 凡是layout_XXX的布局属性基本都针对的是包含子View的ViewGroup的,当对一个没有父容器的View设置相关layout_XXX属性是没有任何意义的。
- 使用View的getWidth()和getHeight()方法来获取View测量的宽高,必须保证这两个方法在onLayout流程之后被调用才能返回有效值。
7. 第三阶段:Draw
概述
measure和layout的过程都结束后,接下来就进入到draw的过程了。同样,根据名字你就能够判断出,在这里才真正地开始对视图进行绘制。ViewRoot中的代码会继续执行并创建出一个Canvas对象,然后调用performDraw,通过调用View的draw()方法来执行具体的绘制工作:
private void performTraversals() {
......
final Rect dirty = mDirty;
......
canvas = mSurface.lockCanvas(dirty);
......
performDraw();
......
}
private void performDraw(){
......
mView.draw(canvas);
......
}
我们来看一看重中之重的View.draw()(ViewGroup并没有重写View的draw方法):
public void draw(Canvas 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)
*/
// Step 1, draw the background, if needed
......
if (!dirtyOpaque) {
drawBackground(canvas);
}
// skip step 2 & 5 if possible (common case)
......
// Step 2, save the canvas' layers
......
if (drawTop) {
canvas.saveLayer(left, top, right, top + length, null, flags);
}
......
// Step 3, draw the content
if (!dirtyOpaque) onDraw(canvas);
// Step 4, draw the children
dispatchDraw(canvas);
// Step 5, draw the fade effect and restore layers
......
if (drawTop) {
matrix.setScale(1, fadeHeight * topFadeStrength);
matrix.postTranslate(left, top);
fade.setLocalMatrix(matrix);
p.setShader(fade);
canvas.drawRect(left, top, right, top + length, p);
}
......
// Step 6, draw decorations (scrollbars)
onDrawScrollBars(canvas);
......
}
源码注释说(”skip step 2 & 5 if possible (common case)”)第2和5步可以跳过,所以我们接下来重点剩余四步:
第一步:绘制View的背景
我们来看drawBackground(canvas):
private void drawBackground(Canvas canvas) {
//获取xml中通过android:background属性或者代码中setBackgroundColor()、setBackgroundResource()等方法进行赋值的背景Drawable
final Drawable background = mBackground;
......
//根据layout过程确定的View位置来设置背景的绘制区域
if (mBackgroundSizeChanged) {
background.setBounds(0, 0, mRight - mLeft, mBottom - mTop);
mBackgroundSizeChanged = false;
rebuildOutline();
}
......
//调用Drawable的draw()方法来完成背景的绘制工作
background.draw(canvas);
......
}
第三步:对View内容容进行绘制
这里去调用了一下View的onDraw()方法(ViewGroup也没有重写该方法):
/**
* Implement this to do your drawing.
*
* @param canvas the canvas on which the background will be drawn
*/
protected void onDraw(Canvas canvas) {
}
可以发现,这里又是一个空方法。因为每个View的内容部分是不同的,所以需要由子类去实现具体的逻辑
第四步:对当前View的所有子View进行绘制
先来看dispatchDraw(canvas)方法:
/**
* Called by draw to draw the child views. This may be overridden
* by derived classes to gain control just before its children are drawn
* (but after its own view has been drawn).
* @param canvas the canvas on which to draw the view
*/
protected void dispatchDraw(Canvas canvas) {
}
对于空方法已经看习惯了....其实他们的逻辑都差不多,一般都是因为具体实现要交给子类去完成。注释中还说到,如果View包含子View需要重写他,所以我们来看一看ViewGroup.dispatchDraw():
@Override
protected void dispatchDraw(Canvas canvas) {
......
final int childrenCount = mChildrenCount;
final View[] children = mChildren;
......
for (int i = 0; i < childrenCount; i++) {
......
// 这里在for循环中遍历了每一个子View,并通过drawChild方法绘制子View的内容
if ((child.mViewFlags & VISIBILITY_MASK) == VISIBLE || child.getAnimation() != null) {
more |= drawChild(canvas, child, drawingTime);
}
}
......
// Draw any disappearing views that have animations
if (mDisappearingChildren != null) {
......
for (int i = disappearingCount; i >= 0; i--) {
......
more |= drawChild(canvas, child, drawingTime);
}
}
......
}
drawChild:
/* ViewGroup.drawChild */
protected boolean drawChild(Canvas canvas, View child, long drawingTime) {
return child.draw(canvas, this, drawingTime);
}
可以看见drawChild()方法调运了子View的draw()方法。所以说ViewGroup类已经为我们重写了dispatchDraw()的功能实现,我们一般不需要重写该方法,但可以重载父类函数实现具体的功能。
到此,draw过程就从父View传递到了子View,并重复此过程直到到达View树的叶子节点。
第六步:绘制装饰(如滑动条)
可以看到,这里去调用了一下View的onDrawScrollBars()方法:
/**
* <p>Request the drawing of the horizontal and the vertical scrollbar. The
* scrollbars are painted only if they have been awakened first.</p>
*
* @param canvas the canvas on which to draw the scrollbars
*
* @see #awakenScrollBars(int)
*/
protected final void onDrawScrollBars(Canvas canvas) {
//绘制ScrollBars分析不是我们这篇的重点,所以暂时不做分析
......
}
其实不管是Button也好,TextView也好,任何一个视图都是有滚动条的,只是一般情况下我们都没有让它显示出来而已。绘制滚动条的代码逻辑也比较复杂,这里就不再贴出来了,因为我们的重点是第三步过程。
通过以上流程分析,相信大家已经发现,View是不会帮我们绘制内容部分的,因此需要每个视图根据想要展示的内容来自行绘制。如果你去观察TextView、ImageView等类的源码,你会发现它们都有重写onDraw()这个方法,并且在里面执行了相当不少的绘制逻辑。绘制的方式主要是借助Canvas这个类,它会作为参数传入到onDraw()方法中,供给每个视图使用。
到此为止,三个阶段全部完成。
核心
- 如果该View是一个ViewGroup,则需要递归绘制其所包含的所有子View。
- View默认不会绘制任何内容,真正的绘制都需要自己在子类中实现。
- View的绘制是借助onDraw方法传入的Canvas类来进行的。
- 区分View动画和ViewGroup布局动画,前者指的是View自身的动画,可以通过setAnimation添加,后者是专门针对ViewGroup显示内部子视图时设置的动画,可以在xml布局文件中对ViewGroup设置layoutAnimation属性(譬如对LinearLayout设置子View在显示时出现逐行、随机、下等显示等不同动画效果)。
- 在获取画布剪切区(每个View的draw中传入的Canvas)时会自动处理掉padding,子View获取Canvas不用关注这些逻辑,只用关心如何绘制即可。
- 默认情况下子View的ViewGroup.drawChild绘制顺序和子View被添加的顺序一致,但是你也可以重载ViewGroup.getChildDrawingOrder()方法提供不同顺序。
小补充:View的requestLayout
其实在上面分析View绘制流程时或多或少都调运到了这个方法,而且这个方法对于View来说也比较重要,所以我们接下来分析一下他:
public void requestLayout() {
......
if (mParent != null && !mParent.isLayoutRequested()) {
//由此向ViewParent请求布局
//从这个View开始向上一直requestLayout,最终到达ViewRootImpl的requestLayout
mParent.requestLayout();
}
......
}
当我们触发View的requestLayout时其实质就是层层向上传递,直到ViewRootImpl为止,然后触发我们之前提到的ViewRootImpl的requestLayout方法:
@Override
public void requestLayout() {
if (!mHandlingLayoutInLayoutRequest) {
checkThread();
mLayoutRequested = true;
//View调运requestLayout最终层层上传到ViewRootImpl后最终触发了该方法
scheduleTraversals();
}
}
8. 补充:Activity启动与WindowManager
对于Activity的启动过程,是有两种,一种是点击程序进入启动的Activity,另一种而是在已有的Activity中调用startActivity,启动期间通过Binder驱动ActivityWindowService,ActivityThread,ApplicationThread,ActivityStack ,Activity之间进行通信,为当前Activity创建进程分配任务栈后启动Activity。
这里就跳过前面很多步骤,直接到了ActivityThread.handleLaunchActivity去查看Activity的创建:
private void handleLaunchActivity(ActivityClientRecord r, Intent customIntent) {
...
// Initialize before creating the activity
WindowManagerGlobal.initialize();
Activity a = performLaunchActivity(r, customIntent);
if (a != null) {
r.createdConfig = new Configuration(mConfiguration);
Bundle oldState = r.state;
handleResumeActivity(r.token, false, r.isForward,
...
}
可以看到 WindowManagerGlobal.initialize()通过WindowManagerGlobal创建了WindowManagerServer,接下来调用了performLaunchActivity:
private Activity performLaunchActivity(ActivityClientRecord r, Intent customIntent) {
...
Activity activity = null;
try { //Activity通过ClassLoader创建出来
java.lang.ClassLoader cl = r.packageInfo.getClassLoader();
activity = mInstrumentation.newActivity(
cl, component.getClassName(), r.intent);
} ...
try {
//创建Application
Application app = r.packageInfo.makeApplication(false, mInstrumentation);
...
if (activity != null) {
//创建Activity所需的Context
Context appContext = createBaseContextForActivity(r, activity);
...
//将Context与Activity进行绑定
activity.attach(appContext, this, getInstrumentation(), r.token,
r.ident, app, r.intent, r.activityInfo, title, r.pareånt,
r.embeddedID, r.lastNonConfigurationInstances, config,
r.referrer, r.voiceInteractor);
...
//调用activity.oncreate
mInstrumentation.callActivityOnCreate(activity, r.state, r.persistentState);
...
//调用Activity的onstart方法
activity.performStart();
//调用activitu的OnRestoreInstanceState方法进行Window数据恢复
mInstrumentation.callActivityOnRestoreInstanceState(activity, r.state,
r.persistentState);
...
return activity;
}
先通过调用 activity = mInstrumentation.newActivity创建Activity,可以看到里面是通过ClassLoader来加载的
public Activity newActivity(ClassLoader cl, String className,
Intent intent)
throws InstantiationException, IllegalAccessException,
ClassNotFoundException {
return (Activity)cl.loadClass(className).newInstance();
}
接着创建Activity所需的Application和Context,再调用到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) {
//ContextImpl的绑定
attachBaseContext(context);
//在当前Activity创建Window
mWindow = new PhoneWindow(this);
mWindow.setCallback(this);
mWindow.setOnWindowDismissedCallback(this);
mWindow.getLayoutInflater().setPrivateFactory(this);
...
//为Window设置WindowManager
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());
}
//创建完后通过getWindowManager就可以得到WindowManager实例
mWindowManager = mWindow.getWindowManager();
mCurrentConfig = config;
}
可以看到对应的Window窗口也被创建起来,而且Window也与WindowManager绑定。而mWindow,和mWindowManager则是Activity的成员变量。可以看到这里WindiwManager的创建是context.getSystemService(Context.WINDOW_SERVICE)
接着创建WindowManager的实现类,我们平时在Activity中使用getWindow()和getWindowManager,就是返回对应这两个成员变量:
public void setWindowManager(WindowManager wm, IBinder appToken, String appName,
boolean hardwareAccelerated) {
...
if (wm == null) {
wm = (WindowManager)mContext.getSystemService(Context.WINDOW_SERVICE);
}
mWindowManager = ((WindowManagerImpl)wm).createLocalWindowManager(this);
}
在调用了activity.attach后创建了Window和WindowManager,之后调用了:
public void callActivityOnCreate(Activity activity, Bundle icicle) {
prePerformCreate(activity);
activity.performCreate(icicle);
postPerformCreate(activity);
}
// 可以看到里面调用了performCreate
final void performCreate(Bundle icicle) {
onCreate(icicle);
mActivityTransitionState.readState(icicle);
performCreateCommon();
}
然后就算是进入到Activiy的生命周期了....
参考资料
以下都是我在学习和写文档时候参考的文章,对于一些知识点的讲解可能更加详细:
简析Window、Activity、DecorView以及ViewRoot之间的错综关系
网友评论