前言:
- 前面的文章,我们从源码角度,从InputChannel出发,将事件分发一路串行分析,走到了ViewRootImpl类中。
- 同时分发到了ViewRootImple的成员mView,也对应了mView其实就是DecorView。
- 其实还有几个疑问:
- DecorView我们很少接触,它和ViewRootImpl是怎么和Activity关联起来的?
- 在客户端,事件分发一般不都是从Activity开始讨论的吗?
Activity和Window
- 在Android系统中,Activity是一个维度,Window也是一个维度。我们可以姑且这么理解:
- 一个Activity会对应一个Window。
- 但是一个Window不一定就是Activity,因为也有可能是Dialog等形式。
- 在系统源码中,Activity专门针对一个ActivityManagerService(下称AMS)进行管理。而window,专门对应一个WindowManagerService(下称WMS)进行管理。
- AMS和WMS之间也有通信,也有相互关联,这不是我们今天要讨论的议题。这里只需要知道,从Activity角度,每一个Activity,都有一个Window。或者说每一个Activity都是一个Window。
- 在Activity中,我们能看到有一个Window成员变量:
private Window mWindow;
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);//PhoneWindow是Window的唯一实现。
mWindow.setWindowControllerCallback(this);
mWindow.setCallback(this);
mWindow.setOnWindowDismissedCallback(this);
mWindow.getLayoutInflater().setPrivateFactory(this);
...//省略代码
}
Window:
- Window在App开发中其实也不常使用到,一般在Dialog需要全屏的时候,才需要调用getWindow()来做一些特殊操作。
- 而源码中,Window.java是一个abstract类,这个类,全局只有一个实现:PhoneWindow。
- 基本得出结论,一个Activity对应的其实是PhoneWindow。
PhoneWindow:
- 我们来撸一下PhoneWindow的代码。
- PhoneWindow中,有一个成员变量:
private DecorView mDecor;
- DecorView,View的壳。这其实是一个抽象概念,DecorView可以算是一个Activity级别最父级的View。我们能看到,DecorView其实是FrameLayout的子类。
- 这就难怪我们每次看View的Hierarchy的时候,根布局都是FrameLayout。
Activity和PhoneWindow
- Activity其实并不是View级别的概念,其实Activity应该是一个Controller,它内部管理着Window,Window的实现是PhoneWindow。
- PhoneWindow内部管理着DecorView,DecorView是所有View的父级。
- 这样逻辑就很清晰了。
setContentView()
- 平时我们创建Activity,设置布局都是走setContentView()。那么出发setContentView()之后,Activity、Widnow、DecorView都做了什么操作呢?
DecorView生成:
- setContentView()内部会创建用于盛放开发者传入的布局的ViewGroup。
@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) {
installDecor();//如果mContentParent还没创建,表示DecorView还没创建,需要创建DecorView
} else if (!hasFeature(FEATURE_CONTENT_TRANSITIONS)) {
mContentParent.removeAllViews();
}
...//省略代码
}
installDecor()
- installDecor基本的意思就是创建DecorView的实例对象。
- 我们知道,其实DecorView已经是View层级的最根部了。开发者传入的ViewGroup到底放到哪里呢?
- 没错,mContentParent成员,其实是一个ViewGroup,它其实就是开发者的ViewGroup盛放的位置。
- 它的创建过程如下:
private void installDecor() {
mForceDecorInstall = false;
if (mDecor == null) {
mDecor = generateDecor(-1);//这里是创建DecorView实例的地方
mDecor.setDescendantFocusability(ViewGroup.FOCUS_AFTER_DESCENDANTS);
mDecor.setIsRootNamespace(true);
if (!mInvalidatePanelMenuPosted && mInvalidatePanelMenuFeatures != 0) {
mDecor.postOnAnimation(mInvalidatePanelMenuRunnable);
}
} else {
mDecor.setWindow(this);
}
if (mContentParent == null) {//判断mContentParent是否已经创建
mContentParent = generateLayout(mDecor);//创建mContentParent
}
...//省略代码
}
generateLayout()
- 创建mContentParent的函数,又臭又长(主要是主题、feature相关的设置),我提炼了一下比较值得说的代码,大家可以看下:
/**
* The ID that the main layout in the XML layout file should have.
*/
public static final int ID_ANDROID_CONTENT = com.android.internal.R.id.content;
protected ViewGroup generateLayout(DecorView decor) {
// Apply data from current theme.
...//省略代码
ViewGroup contentParent = (ViewGroup) findViewById(ID_ANDROID_CONTENT);//通过findViewById,找到一个id值是content的ViewGroup
if (contentParent == null) {
throw new RuntimeException("Window couldn't find content container view");
}
...//省略代码
}
- 从上面可以看到,其实mContentParent就是DecorView中的一个子ViewGroup,id恒定设置为ID_ANDROID_CONTENT常量。
- 所有的Activity中开发者设置的子View,都会被设置到这个里面去。
inflate:
- DecorView和mContentParent有了,就等于有了根布局,那简单了,要加什么View,尽管来。
@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) {
installDecor();
} else if (!hasFeature(FEATURE_CONTENT_TRANSITIONS)) {
mContentParent.removeAllViews();
}
//到这里,DecorView和mContentParent都创建好了
if (hasFeature(FEATURE_CONTENT_TRANSITIONS)) {
final Scene newScene = Scene.getSceneForLayout(mContentParent, layoutResID,
getContext());
transitionTo(newScene);
} else {
mLayoutInflater.inflate(layoutResID, mContentParent);//接下来就是inflate我们要放的View了。
}
mContentParent.requestApplyInsets();
final Callback cb = getCallback();
if (cb != null && !isDestroyed()) {
cb.onContentChanged();
}
mContentParentExplicitlySet = true;
}
- 关键逻辑在inflate()上。其实也很简单,传入的LayoutResId,直接用LayoutInflater,直接渲染添加到mContentParent内部。
- 大功告成。