目录
一、Activity、Window、DecorView的层级关系如下图所示:
1、Activity
2、Window
3、DecorView
二、DecorView初始化相关源码
三、DecorView显示时机
前言: 不同的Android版本有差异,以下基于Android 11进行讲解。
一、Activity、Window、DecorView的层级关系如下图所示:
从上图可以直观的看到Activity、Window以及DecorView之间的关系,Activity持有window(Phone Window)、而在window里管理着DecorView,我们一般平时开发对Activity样式的修改,实际上是对DecorView的修改、系统中提供着几种默认DecorView样式。
1、Activity
对于应用的开发,我们通常操做Activity来创建我们想要的视图界面,但实际上对视图的控制并不是Activity,而是Activity持有的Window。每一个Activity包含一个PhoneWindow,PhoneWindow是window的子类。
如上图所示,在Activity类中的attach方法中创建了PhoneWindow实例。
2、Window
Window是视图界面真正的管理器。Window是一个抽象类,具体的实现在PhoneWindow类,PhoneWindow类继承于Window抽象类。PhoneWindow类该持有DecorView的实例。PhoneWindow类通过DecorView来加载布局xml文件。
如上图所示,在PhoneWindow中给全局变量mDecor负值。mDecor为DecorView实例。
3、DecorView
根据以上源码截图我们发现DecorView是FrameLayout类的子类。
Android原生提供了几种样式给DecorView。如下这几种xml布局都是原生提供的:
其中我们看看screen_title.xml布局代码:
<?xml version="1.0" encoding="utf-8"?>
<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,在这个LinearLayout里面有上下三个部分,上面是个ViewStub,根据Theme样式设置ActionBar等。中间的是TitleView,有的xml布局没有这一部分,例如screen_simple.xml中就没有这一部分。最下面的是ContentViews,这是最重要的一部分,我们开发应用时在oncreate()函数中调用的setContentView()加载方法,其实就是将其加载到这个ContentViews里。
如上图所示,phonewindow中通过DecorView的实例mDecor.onResourcesLoaded()方法将该布局加载到DecorView.
二、DecorView初始化相关源码
接下来我们看看从Activity 到 PhoneWindow再到DecorView的初始化以及布局加载源码:
Activity的完整启动流程在这里不再细说,直接从ActivityThread.java的handleLaunchActivity()方法开始讲解。代码如下:
\frameworks\base\core\java\android\app\ActivityThread.java
@Override
public Activity handleLaunchActivity(ActivityClientRecord r,
PendingTransactionActions pendingActions, Intent customIntent) {
.................
// Hint the GraphicsEnvironment that an activity is launching on the process.
GraphicsEnvironment.hintActivityLaunch();
final Activity a = performLaunchActivity(r, customIntent);//(1)
.................
return a;
}
private Activity performLaunchActivity(ActivityClientRecord r, Intent customIntent) {
..................
appContext.getResources().addLoaders(
app.getResources().getLoaders().toArray(new ResourcesLoader[0]));
appContext.setOuterContext(activity);
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,
r.assistToken); //(2)
..................
}
如上代码,在Activity的启动过程中,其中在ActivityThread.java中的与PhoneWindow及DecorView初始化相关部分得调用流程:
handleLaunchActivity()=> performLaunchActivity( ) => activity.attach( )
接下来我们看看一下activity中attach()方法:
frameworks\base\core\java\android\app\Activity.java
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, IBinder assistToken){
...................
mWindow = new PhoneWindow(this, window, activityConfigCallback); //(3)
mWindow.setWindowControllerCallback(mWindowControllerCallback);
mWindow.setCallback(this); //(4)
...................
}
在注释(3)可以看到Activity持有PhoneWindow并初始化了PhoneWindow实例,在注释(4)中调用PhoneWindow的setCallback方法将activity实例设置给PhoneWindow,这一点很重要,我们在看DecorView的代码时候,看到的mWindow.getCallback()方法,实际就是获取DecorView相对应的Activity实例。
我们在第一节第3点讲DecorView的布局的时候讲到在oncreate()方法中调用setContentView()方法给activity设置布局是加载到DecorView的ContentViews里。接下来我们看一下这个代码流程:
frameworks\base\core\java\android\app\Activity.java
public void setContentView(View view) {
getWindow().setContentView(view);
initWindowDecorActionBar();
}
我们在前面注释(3)中讲了Activity持有PhoneWindow并初始化了PhoneWindow实例mWindow。
public Window getWindow() {
return mWindow;
}
getWindow()是拿到我们初始化好的PhoneWindow。
我们看一下PhoneWindow中的setContentView()方法:
frameworks\base\core\java\com\android\internal\policy\PhoneWindow.java
@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) { //(5)
installDecor(); //(6)
} 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);//(7)
}
mContentParent.requestApplyInsets();
final Callback cb = getCallback();
if (cb != null && !isDestroyed()) {
cb.onContentChanged();
}
mContentParentExplicitlySet = true;
}
以上,注释(3)中的mContentParent 就是DecorView的ContentViews,在(6)中,如果mContentParent为空即通过installDecor()方法初始化一个DecorView实例、并将此PhoneWindow实例传给DecorView,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);
}
} else {
mDecor.setWindow(this);
}
............
}
在注释(7)中将xml布局文件加载给DecorView的ContentViews。
总结:
Activity创建PhoneWindow对象,并把自己实例传递给PhoneWindow;PhoneWindow会创建一个DecorView对象,并把自己实例传递给DecorView。DecorView创建过程中根据开发者设置的不同的主题,加载不同的布局到DecorView的ContentViews里。
三、DecorView显示时机
根据我们平时的开发经验,我们给activity设置的布局,在activity调用了onResume()方法之后才会显示出来,那么接下来我们看一下这个流程是否是我们想的这样。
在以上我们详述onCreate()方法中调用了setContentView()方法给DecorView设置了布局,此时并不可见。Activity的onResume()方法的调用以及DecorView的显示都会在ActivityThread的handleResumeActivity()方法实现,我们看一下该方法:
\frameworks\base\core\java\android\app\ActivityThread.java
@Override
public void handleResumeActivity(IBinder token, boolean finalStateRequest, boolean isForward,
String reason) {
.....................
final ActivityClientRecord r = performResumeActivity(token,
finalStateRequest, reason); //(8)
if (r == null) {
// We didn't actually resume the activity, so skipping any follow-up actions.
return;
}
.....................
if (r.window == null && !a.mFinished && willBeVisible) {
r.window = r.activity.getWindow();
View decor = r.window.getDecorView();
decor.setVisibility(View.INVISIBLE); //(9)
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) {
if (!a.mWindowAdded) {
a.mWindowAdded = true;
wm.addView(decor, l); //(10)
} else {
a.onWindowAttributesChanged(l);
}
}
} else if (!willBeVisible) {
if (localLOGV) Slog.v(TAG, "Launch " + r + " mStartedActivity set");
r.hideForNow = true;
}
if (!r.activity.mFinished && willBeVisible && r.activity.mDecor != null && !r.hideForNow) {
.....................
if (r.activity.mVisibleFromClient) {
r.activity.makeVisible(); //(11)
}
}
.....................
}
我们先看注释(8)performResumeActivity()方法的调用。
\frameworks\base\core\java\android\app\ActivityThread.java
public ActivityClientRecord performResumeActivity(IBinder token, boolean finalStateRequest,
String reason){
final ActivityClientRecord r = mActivities.get(token);
.................
r.activity.performResume(r.startsNotResumed, reason);//(12)
.................
}
注释(12)中,调用了activity的performResume()的方法:
\frameworks\base\core\java\android\app\Activity.java
final void performResume(boolean followedByPause, String reason) {
dispatchActivityPreResumed();
..................
mInstrumentation.callActivityOnResume(this);//(13)
..................
}
注释(13)中,调用了Instrumentation的callActivityOnResume()的方法:
public void callActivityOnResume(Activity activity) {
activity.mResumed = true;
activity.onResume();//(14)
.......................
}
至此,我们知道了在注释(14)中,我们熟悉的activity中的onResume()方法是被Instrumentation的callActivityOnResume()方法所调起的。
回到前面,也就是说在ActivityThread.java中的注释(8)中的performResumeActivity()方法里就调起了activity中的onResume()方法。
再看注释(9)decor.setVisibility(View.INVISIBLE),将DecorView设置为不可见,注释(10)中wm.addView(decor, l)通过addView的方式将decor视图添加;最后在注释(11)r.activity.makeVisible()方法里将decor设置为VISIBLE。makeVisible()方法如下:
\frameworks\base\core\java\android\app\Activity.java
void makeVisible() {
if (!mWindowAdded) {
ViewManager wm = getWindowManager();
wm.addView(mDecor, getWindow().getAttributes());
mWindowAdded = true;
}
mDecor.setVisibility(View.VISIBLE);
}
总结:由以上代码流程我们知道,activity里的decorView的布局的显示是在activity中的OnResume()执行了之后才展示出来的,但是我们需要注意的是,即使我们发现了在activity中的OnResume()被调用了,但是handleResumeActivity()方法中的r.activity.makeVisible()方法没有被执行,该布局视图依然是不可见的。