参考
Android View源码解读:浅谈DecorView与ViewRootImpl
从ViewRootImpl类分析View绘制的流程
Android应用程序窗口(Activity)实现框架简要介绍和学习计划
Android开发艺术探索 Window和WindowManager
一、setContentView
一般地,我们在Activity中,会在onCreate()方法中写下这样一句:
setContentView(R.layout.main);
显然,这是为activity设置一个我们定义好的main.xml布局,我们跟踪一下源码,看看这个方法是怎样做的,Activity#setContentView:
public void setContentView(@LayoutRes int layoutResID) {
getWindow().setContentView(layoutResID); //调用getWindow方法,返回mWindow
initWindowDecorActionBar();
}
...
public Window getWindow() {
return mWindow;
}
从上面看出,里面调用了mWindow的setContentView方法,那么这个“mWindow”是何方神圣呢?尝试追踪一下源码,发现mWindow是Window类型的,但是它是一个抽象类,setContentView也是抽象方法,所以我们要找到Window类的实现类才行。我们在Activity中查找一下mWindow在哪里被赋值了,可以发现它在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) {
...
mWindow = new PhoneWindow(this);
...
}
我们只看关键部分,这里实例化了PhoneWindow类,由此得知,PhoneWindow是Window的实现类,那么我们在PhoneWindow类里面找到它的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) { // 1
installDecor();
} else if (!hasFeature(FEATURE_CONTENT_TRANSITIONS)) {
mContentParent.removeAllViews();
}
if (hasFeature(FEATURE_CONTENT_TRANSITIONS)) {
final Scene newScene = Scene.getSceneForLayout(
mContentParent, layoutResID,getContext());
transitionTo(newScene);
} else {
mLayoutInflater.inflate(layoutResID, mContentParent); // 2
}
final Callback cb = getCallback();
if (cb != null && !isDestroyed()) {
cb.onContentChanged();
}
}
首先判断了mContentParent是否为null,如果为空则执行installDecor()方法,那么这个mContentParent又是什么呢?我们看一下它的注释:
// 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.
private ViewGroup mContentParent;
它是一个ViewGroup类型,结合②号代码处,可以得知,这个mContentParent是我们设置的布局(即main.xml)的父布局。注释还提到了,这个mContentParent是mDecor本身或者是mDecor的一个子元素,这句话什么意思呢?这里先留一个疑问,下面会解释。
这里先梳理一下以上的内容:Activity通过PhoneWindow的setContentView方法来设置布局,而设置布局之前,会先判断是否存在mContentParent,而我们设置的布局文件则是mContentParent的子元素。
二、创建DecorView
接着上面提到的installDecor()方法,我们看看它的源码,PhoneWindow#installDecor:
private void installDecor() {
if (mDecor == null) {
mDecor = generateDecor(); // 1
mDecor.setDescendantFocusability(ViewGroup.FOCUS_AFTER_DESCENDANTS);
mDecor.setIsRootNamespace(true);
if (!mInvalidatePanelMenuPosted && mInvalidatePanelMenuFeatures != 0) {
mDecor.postOnAnimation(mInvalidatePanelMenuRunnable);
}
}
if (mContentParent == null) {
mContentParent = generateLayout(mDecor); // 2
...
}
}
}
protected DecorView generateDecor() {
return new DecorView(getContext(), -1);
}
可以看出,这里实例化了DecorView,而DecorView则是PhoneWindow类的一个内部类,继承于FrameLayout,由此可知它也是一个ViewGroup。那么,DecroView到底充当了什么样的角色呢?其实,DecorView是整个ViewTree的最顶层View,它是一个FrameLayout布局,代表了整个应用的界面。在该布局下面,有标题view和内容view这两个子元素,而内容view则是上面提到的mContentParent。我们接着看②号代码,PhoneWindow#generateLayout方法
protected ViewGroup generateLayout(DecorView decor) {
// Apply data from current theme.
// 从主题文件中获取样式信息
TypedArray a = getWindowStyle();
...
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(...){
...
}
// Inflate the window decor.
// 加载窗口布局
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 if(...){
...
}
//加载layoutResource
View in = mLayoutInflater.inflate(layoutResource, null);
//往DecorView中添加子View,即mContentParent
decor.addView(in, new ViewGroup.LayoutParams(
MATCH_PARENT, MATCH_PARENT));
mContentRoot = (ViewGroup) in;
// 这里获取的就是mContentParent
ViewGroup contentParent = (ViewGroup)findViewById(ID_ANDROID_CONTENT);
if (contentParent == null) {
throw new RuntimeException("Window couldn't find
content container view");
}
if ((features & (1 << FEATURE_INDETERMINATE_PROGRESS)) != 0) {
ProgressBar progress = getCircularProgressBar(false);
if (progress != null) {
progress.setIndeterminate(true);
}
}
if ((features & (1 << FEATURE_SWIPE_TO_DISMISS)) != 0) {
registerSwipeCallbacks();
}
// Remaining setup -- of background and title -- that only applies
// to top-level windows.
...
return contentParent;
}
由以上代码可以看出,该方法还是做了相当多的工作的,首先根据设置的主题样式来设置DecorView的风格,比如说有没有titlebar之类的,接着为DecorView添加子View,而这里的子View则是上面提到的mContentParent,如果上面设置了FEATURE_NO_ACTIONBAR,那么DecorView就只有mContentParent一个子View,这也解释了上面的疑问:mContentParent是DecorView本身或者是DecorView的一个子元素。
小结:DecorView是顶级View,内部有titlebar和contentParent两个子元素,contentParent的id是content,而我们设置的main.xml布局则是contentParent里面的一个子元素。
在DecorView创建完毕后,让我们回到PhoneWindow#setContentView方法,直接看②号代码: mLayoutInflater.inflate(layoutResID, mContentParent);这里加载了我们设置的main.xml布局文件,并且设置mContentParent为main.xml的父布局,至于它怎么加载的,这里就不展开来说了。
到目前为止,通过setContentView方法,创建了DecorView和加载了我们提供的布局,但是这时,我们的View还是不可见的,因为我们仅仅是加载了布局,并没有对View进行任何的测量、布局、绘制工作。在View进行测量流程之前,还要进行一个步骤,那就是把DecorView添加至window中,然后经过一系列过程触发ViewRootImpl#performTraversals方法,在该方法内部会正式开始测量、布局、绘制这三大流程。
以上参考自Android View源码解读:浅谈DecorView与ViewRootImpl
以下参考自从ViewRootImpl类分析View绘制的流程
三、将DecorView添加至Window
DecorView是怎么添加到窗口的呢?这时候我们不得不从Activity是怎么启动的说起,当Activity初始化 Window和将布局添加到
PhoneWindow的内部类DecorView类之后,ActivityThread类会调用handleResumeActivity方法将顶层视图DecorView添加到PhoneWindow窗口,来看看handlerResumeActivity方法的实现:
final void handleResumeActivity(IBinder token,
boolean clearHide, boolean isForward, boolean reallyResume) {
..................
if (r.window == null && !a.mFinished && willBeVisible) {
//获得当前Activity的PhoneWindow对象
r.window = r.activity.getWindow();
//获得当前phoneWindow内部类DecorView对象
View decor = r.window.getDecorView();
//设置窗口顶层视图DecorView可见度
decor.setVisibility(View.INVISIBLE);
//得当当前Activity的WindowManagerImpl对象
ViewManager wm = a.getWindowManager();
WindowManager.LayoutParams l = r.window.getAttributes();
a.mDecor = decor;
l.type = WindowManager.LayoutParams.TYPE_BASE_APPLICATION;
l.softInputMode |= forwardBit;
if (a.mVisibleFromClient) {
//标记根布局DecorView已经添加到窗口
a.mWindowAdded = true;
//将根布局DecorView添加到当前Activity的窗口上面
wm.addView(decor, l);
.....................
WindowManager只是一个接口,它的真正实现是WindowManagerImpl类,然后它又将所有操作委托给WindowManagerGlobal来实现,这是典型的桥接模式。
@Override
public void addView(View view, ViewGroup.LayoutParams params) {
mGlobal.addView(view, params, mDisplay, mParentWindow);
}
源码很简单,直接调用了 mGlobal对象的addView()方法。继续跟踪,mGlobal对象是WindowManagerGlobal类。进入WindowManagerGlobal类看addView()方法。
public void addView(View view, ViewGroup.LayoutParams params,
Display display, Window parentWindow) {
............
ViewRootImpl root;
View panelParentView = null;
............
//获得ViewRootImpl对象root
root = new ViewRootImpl(view.getContext(), display);
...........
// do this last because it fires off messages to start doing things
try {
//将传进来的参数DecorView设置到root中
root.setView(view, wparams, panelParentView);
} catch (RuntimeException e) {
...........
}
}
该方法中创建了一个ViewRootImpl对象root,然后调用ViewRootImpl类中的setView成员方法()。继续跟踪代码进入ViewRootImpl类分析:
public void setView(View view, WindowManager.
LayoutParams attrs, View panelParentView) {
synchronized (this) {
if (mView == null) {
//将顶层视图DecorView赋值给全局的mView
mView = view;
.............
//标记已添加DecorView
mAdded = true;
.............
//请求布局
requestLayout();
.............
}
}
该方法实现有点长,我省略了其他代码,直接看以上几行代码:
将外部参数DecorView赋值给mView成员变量
标记DecorView已添加到ViewRootImpl
调用requestLayout方法请求布局
@Override
public void requestLayout() {
if (!mHandlingLayoutInLayoutRequest) {
checkThread();
mLayoutRequested = true;
scheduleTraversals();
}
}
................
void scheduleTraversals() {
if (!mTraversalScheduled) {
mTraversalScheduled = true;
mTraversalBarrier = mHandler.getLooper().postSyncBarrier();
mChoreographer.postCallback(
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);
}
}
}
............
跟踪代码,最后DecorView的绘制会进入到ViewRootImpl类中的performTraversals()成员方法,这个过程可以参考上面的代码流程图。现在我们主要来分析下 ViewRootImpl类中的performTraversals()方法。
private void performTraversals() {
// cache mView since it is used so much below...
//我们在Step3知道,mView就是DecorView根布局
final View host = mView;
//在Step3 成员变量mAdded赋值为true,因此条件不成立
if (host == null || !mAdded)
return;
//是否正在遍历
mIsInTraversal = true;
//是否马上绘制View
mWillDrawSoon = true;
.............
//顶层视图DecorView所需要窗口的宽度和高度
int desiredWindowWidth;
int desiredWindowHeight;
.....................
//在构造方法中mFirst已经设置为true,表示是否是第一次绘制DecorView
if (mFirst) {
mFullRedrawNeeded = true;
mLayoutRequested = true;
//如果窗口的类型是有状态栏的,那么顶层视图
//DecorView所需要窗口的宽度和高度就是除了状态栏
if (lp.type == WindowManager.LayoutParams.TYPE_STATUS_BAR_PANEL
|| lp.type == WindowManager.LayoutParams.TYPE_INPUT_METHOD) {
// NOTE -- system code, won't try to do compat mode.
Point size = new Point();
mDisplay.getRealSize(size);
desiredWindowWidth = size.x;
desiredWindowHeight = size.y;
} else {//否则顶层视图DecorView所需要窗口的宽度和高度就是整个屏幕的宽高
DisplayMetrics packageMetrics =
mView.getContext().getResources().getDisplayMetrics();
desiredWindowWidth = packageMetrics.widthPixels;
desiredWindowHeight = packageMetrics.heightPixels;
}
}
............
//获得view宽高的测量规格,mWidth和mHeight表示窗口的宽高,
//lp.widthhe和lp.height表示DecorView根布局宽和高
int childWidthMeasureSpec = getRootMeasureSpec(mWidth, lp.width);
int childHeightMeasureSpec = getRootMeasureSpec(mHeight, lp.height);
// Ask host how big it wants to be
//执行测量操作
performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);
........................
//执行布局操作
performLayout(lp, desiredWindowWidth, desiredWindowHeight);
.......................
//执行绘制操作
performDraw();
}
a