赞
踩
/**
* Abstract base class for a top-level window look and behavior policy. An
* instance of this class should be used as the top-level view added to the
* window manager. It provides standard UI policies such as a background, title
* area, default key processing, etc.
*
* <p>The only existing implementation of this abstract class is
* android.view.PhoneWindow, which you should instantiate when needing a
* Window.
*/
public abstract class Window
翻译:顶级窗口外观和行为策略的抽象基类。 此类的实例应用作添加到window manager的顶级视图。 它提供了标准的 UI 策略,例如背景、标题区域、默认按键事件处理等。这个抽象类的唯一现有实现是android.view.PhoneWindow,当需要一个Window时你应该实例化它。
Window类不仅是个抽象类,而且它的定义也挺抽象,但是从这个注释中,我们还是可以得到两点和我们今天所分析的内容相关的信息:
1)、Window在View的层级结构中是作为顶级视图存在的:
2)、Window会被添加到WindowManager,由WindowManager管理。
那么我们了解窗口,也是从这两个角度去出发:
1)、微观角度,探究窗口内部的构造。
2)、宏观角度,探究WMS如何管理屏幕上显示的诸多窗口。
至于窗口,我们先理解它为一块在屏幕上可以显示图像的区域。
从Activity的层面讲,由于Activity是通过各种UI元素与用户进行交互的,那么这些UI元素就需要一个统一的载体来承载它们,这个UI元素的容器也就是Window。
对于每一个Activity,或者每一个窗口来说,它的界面布局都是由多层View嵌套合成得到的,如果把View的层级结构比作一个树状图,那么Window应该是根节点root view的存在,我们自定义的一些布局xml文件中的一些layout就是中间节点,子节点就是这些layout中定义的如Button、TextView等很具体的View。
如果我们想要再深入一点去探究Window的View层级结构的话,会发现上面的说法其实是不够严谨的。View的层级结构是由各种View层层嵌套得来的,这并不是一个抽象的说法,而是实实在在的由一个ViewGroup嵌套多个子ViewGroup,多个子ViewGroup再分别嵌套多个子子ViewGroup,这样一层层嵌套得来的。
ViewGroup是一种特殊的View,它可以包含其他的View,我们通常所用的FrameLayout、RelativeLayout和LinearLayout等,都继承自ViewGroup,因此ViewGroup可以视作为View的容器类,它有一个View数组类型的成员变量mChildren来存放它的子View:
// Child views of this ViewGroup
@UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P)
private View[] mChildren;
并且ViewGroup本身也是继承View的:
public abstract class ViewGroup extends View implements ViewParent, ViewManager
那么ViewGroup就可以作为一个View添加到某一个ViewGroup的mChildren数组中,这就让一个父ViewGroup包含多个子ViewGroup成为可能,就像这样:
我个人习惯用树状图表示:
既然ViewGroup的为View的多级嵌套提供了支持,那么在View的层级结构中,最顶层View应该也是一个ViewGroup。而Window的唯一实现类,PhoneWindow,只继承了Window,因此PhoneWindow是无法作为当前Window的View的层级结构的根节点的,因为根节点应该是一个ViewGroup类型的类。
那么根节点对应的是哪个类?这里我们自问自答,View的层级结构的根节点是DecorView,一个真正的View:
public class DecorView extends FrameLayout implements RootViewSurfaceTaker, WindowCallbacks
一般在网上百度Activity、Window和DecorView之间的关系,都会得到类似下面的图:
之前我以为Activity的区域对应显示设备的整个区域,Activity的区域包含PhoneWindow区域,PhoneWindow的区域又包含DecorView的区域。。。但是实际上,Activity和PhoneWindow都是一个抽象的概念,都没有一个具体的显示区域,而DecorView,作为一个View,它的作用正如它作为PhoneWindow的成员变量注释中所描述的:
// This is the top-level view of the window, containing the window decor.
private DecorView mDecor;
DecorView是一个窗口的最顶层View,因此真正承载UI元素是DecorView,毕竟View才是我们实打实可以看的到的UI元素。
总结一下,上图可以分为抽象和具象两部分:
1)、一方面,Activity启动的时候会创建一个PhoneWindow对象,将UI处理交给PhoneWindow;而PhoneWindow在后续Activity加载布局资源的时候生成一个DecorView,所以在抽象概念上,是Activity > PhoneWindow > DecorView的,如下图所示:
2)、另一方面,由于Activity和PhoneWindow都是抽象的概念,我们直接看到的其实是DecorView。并且DecorView在生成View层级结构的时候,是作为根View的,所以在具象概念上,或者说我们能够看到的层面上,DecorView是最外层的,如下图所示:
那么我们想要了解一个Window的内部构造,就需要知道以DecorView为根View的View层级结构是怎么生成的。
我们经常在Activity#onCreate中调用Activity#setContentView去加载指定的布局文件,就像这样:
setContentView(R.layout.activity_main);
DecorView就是在这个过程中创建的。
/**
* Set the activity content from a layout resource. The resource will be
* inflated, adding all top-level views to the activity.
*
* @param layoutResID Resource ID to be inflated.
*
* @see #setContentView(android.view.View)
* @see #setContentView(android.view.View, android.view.ViewGroup.LayoutParams)
*/
public void setContentView(@LayoutRes int layoutResID) {
getWindow().setContentView(layoutResID);
initWindowDecorActionBar();
}
这里Activity#getWindow返回的便是和当前Activity一一对应的Window对象。
本来我们看到上一步,应该是调用到PhoneWindow#setContentView的,但是我们写的Activity是继承了AppCompatActivity的,AppCompatActivity又重写了Activity#setContentVIew方法,所以流程就有了些许不一样,先走到的是PhoneWindow#installDecor,PhoneWindow#setContentView要在稍晚的时间点才会被调用:
调用堆栈是:
08-20 09:38:31.520 6254 6254 I ukynho_test: MainActivity#onCreate 08-20 09:38:31.543 6254 6254 I ukynho_test: call setContentView 08-20 09:38:31.548 6254 6254 I ukynho_decor: PhoneWindow#installDecor ---- title = null mDecor = null mContentParent = null 08-20 09:38:31.548 6254 6254 I ukynho_decor: java.lang.Throwable 08-20 09:38:31.548 6254 6254 I ukynho_decor: at com.android.internal.policy.PhoneWindow.installDecor(PhoneWindow.java:2716) 08-20 09:38:31.548 6254 6254 I ukynho_decor: at com.android.internal.policy.PhoneWindow.getDecorView(PhoneWindow.java:2128) 08-20 09:38:31.548 6254 6254 I ukynho_decor: at androidx.appcompat.app.AppCompatDelegateImpl.createSubDecor(AppCompatDelegateImpl.java:717) 08-20 09:38:31.548 6254 6254 I ukynho_decor: at androidx.appcompat.app.AppCompatDelegateImpl.ensureSubDecor(AppCompatDelegateImpl.java:659) 08-20 09:38:31.548 6254 6254 I ukynho_decor: at androidx.appcompat.app.AppCompatDelegateImpl.setContentView(AppCompatDelegateImpl.java:552) 08-20 09:38:31.548 6254 6254 I ukynho_decor: at androidx.appcompat.app.AppCompatActivity.setContentView(AppCompatActivity.java:161) 08-20 09:38:31.548 6254 6254 I ukynho_decor: at com.qq.reader.activity.MainActivity.onCreate(MainActivity.java:29) 08-20 09:38:31.548 6254 6254 I ukynho_decor: at android.app.Activity.performCreate(Activity.java:8012) 08-20 09:38:31.548 6254 6254 I ukynho_decor: at android.app.Activity.performCreate(Activity.java:7996) 08-20 09:38:31.548 6254 6254 I ukynho_decor: at android.app.Instrumentation.callActivityOnCreate(Instrumentation.java:1311) 08-20 09:38:31.548 6254 6254 I ukynho_decor: at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:3525) 08-20 09:38:31.548 6254 6254 I ukynho_decor: at android.app.ActivityThread.handleLaunchActivity(ActivityThread.java:3713) 08-20 09:38:31.548 6254 6254 I ukynho_decor: at android.app.servertransaction.LaunchActivityItem.execute(LaunchActivityItem.java:85) 08-20 09:38:31.548 6254 6254 I ukynho_decor: at android.app.servertransaction.TransactionExecutor.executeCallbacks(TransactionExecutor.java:135) 08-20 09:38:31.548 6254 6254 I ukynho_decor: at android.app.servertransaction.TransactionExecutor.execute(TransactionExecutor.java:95) 08-20 09:38:31.548 6254 6254 I ukynho_decor: at android.app.ActivityThread$H.handleMessage(ActivityThread.java:2156) 08-20 09:38:31.548 6254 6254 I ukynho_decor: at android.os.Handler.dispatchMessage(Handler.java:106) 08-20 09:38:31.548 6254 6254 I ukynho_decor: at android.os.Looper.loop(Looper.java:236) 08-20 09:38:31.548 6254 6254 I ukynho_decor: at android.app.ActivityThread.main(ActivityThread.java:7814) 08-20 09:38:31.548 6254 6254 I ukynho_decor: at java.lang.reflect.Method.invoke(Native Method) 08-20 09:38:31.548 6254 6254 I ukynho_decor: at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:621) 08-20 09:38:31.548 6254 6254 I ukynho_decor: at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:997)
PhoneWindow#installDecor方法内容如下:
private void installDecor() { mForceDecorInstall = false; 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); } if (mContentParent == null) { mContentParent = generateLayout(mDecor); // Set up decor part of UI to ignore fitsSystemWindows if appropriate. mDecor.makeFrameworkOptionalFitsSystemWindows(); final DecorContentParent decorContentParent = (DecorContentParent) mDecor.findViewById( R.id.decor_content_parent); // ...... } }
这个方法中和我们现在分析的内容相关的有两点:
1)、通过PhoneWindow#generateDecor生成DecorView。
2)、通过PhoneWindow#generateLayout生成Decor的View层级结构。
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);
}
在PhoneWIndow#installDecor中,由于我们是首次创建,所以DecorView类型的mDecor是空的,需要调用PhoneWIndow#generateDecor去生成一个DecorView。
protected DecorView generateDecor(int featureId) { // System process doesn't have application context and in that case we need to directly use // the context we have. Otherwise we want the application context, so we don't cling to the // activity. Context context; if (mUseDecorContext) { Context applicationContext = getContext().getApplicationContext(); if (applicationContext == null) { context = getContext(); } else { context = new DecorContext(applicationContext, this); if (mTheme != -1) { context.setTheme(mTheme); } } } else { context = getContext(); } return new DecorView(context, featureId, this, getAttributes()); }
mUseDecorContext在PhoneWindow构造方法中被设置为true,那么这里会创建一个DecorView专用的上下文DecorContext,然后DecorView基于这个新建的DecorContext创建。
这里我们拿一个测试App做例子:
一个带了ActionBar和三个按钮的Activity。
我这边查看Activity布局信息一般是通过两种方式,一是adb shell dumpsys activity top,这里可以看下dumpsys出来的信息:
View Hierarchy: DecorView@7b3553[MainActivity] android.widget.LinearLayout{fe72e90 V.E...... ........ 0,0-1200,1904} android.view.ViewStub{8fee389 G.E...... ......I. 0,0-0,0 #10201c3 android:id/action_mode_bar_stub} android.widget.FrameLayout{2d0cf8e V.E...... ........ 0,48-1200,1904} androidx.appcompat.widget.ActionBarOverlayLayout{7f9d3af V.E...... ........ 0,0-1200,1856 #7f07004d app:id/decor_content_parent} androidx.appcompat.widget.ContentFrameLayout{c38a3bc V.E...... ........ 0,128-1200,1856 #1020002 android:id/content} android.widget.RelativeLayout{5692f45 V.E...... ........ 0,0-1200,1728} androidx.appcompat.widget.AppCompatButton{e8e569a VFED..C.. ........ 200,200-376,296 #7f070059 app:id/getSize} androidx.appcompat.widget.AppCompatButton{619fbcb VFED..C.. ........ 0,296-243,392 #7f07009c app:id/startAnother} androidx.appcompat.widget.AppCompatButton{dca3fa8 VFED..C.. ........ 0,392-220,488 #7f070058 app:id/getLocation} androidx.appcompat.widget.ActionBarContainer{2727ac1 V.ED..... ........ 0,0-1200,128 #7f070029 app:id/action_bar_container} androidx.appcompat.widget.Toolbar{e6b4266 V.E...... ........ 0,0-1200,128 #7f070027 app:id/action_bar} androidx.appcompat.widget.AppCompatTextView{2ca89a7 V.ED..... ........ 48,37-316,91} androidx.appcompat.widget.ActionMenuView{457ae54 V.E...... ........ 1184,0-1184,128} androidx.appcompat.widget.ActionBarContextView{96b01fd G.E...... ......I. 0,0-0,0 #7f07002f app:id/action_context_bar} android.view.View{3d91ef2 V.ED..... ........ 0,1904-1200,2000 #1020030 android:id/navigationBarBackground} android.view.View{c0c1943 V.ED..... ........ 0,0-1200,48 #102002f android:id/statusBarBackground}
另一种就是借助SDK自带的工具hierarchyviewer:
这个工具可以看到View的一些具体信息,如寬高,坐标,透明度等,分析问题的时候很有用。
通过这两种方式,看到最顶层是DecorView这不用多说,但是自DecorView到Activity自定义的布局结构,中间还有很多层View,这些便是在PhoneWindow#generateLayout中生成的,也就是PhoneWindow#installDecor的第二部分:
if (mContentParent == null) {
mContentParent = generateLayout(mDecor);
... ...
}
在这一步之前我们只是生成了DecorView这一层,看下这一步是如何生成其他View的。
protected ViewGroup generateLayout(DecorView decor) { // Apply data from current theme. TypedArray a = getWindowStyle(); if (false) { System.out.println("From style:"); String s = "Attrs:"; for (int i = 0; i < R.styleable.Window.length; i++) { s = s + " " + Integer.toHexString(R.styleable.Window[i]) + "=" + a.getString(i); } System.out.println(s); } mIsFloating = a.getBoolean(R.styleable.Window_windowIsFloating, false); int flagsToUpdate = (FLAG_LAYOUT_IN_SCREEN|FLAG_LAYOUT_INSET_DECOR) & (~getForcedWindowFlags()); if (mIsFloating) { setLayout(WRAP_CONTENT, WRAP_CONTENT); setFlags(0, flagsToUpdate); } else { setFlags(FLAG_LAYOUT_IN_SCREEN|FLAG_LAYOUT_INSET_DECOR, flagsToUpdate); getAttributes().setFitInsetsSides(0); getAttributes().setFitInsetsTypes(0); } // ...... if (a.getBoolean(R.styleable.Window_windowTranslucentStatus, false)) { setFlags(FLAG_TRANSLUCENT_STATUS, FLAG_TRANSLUCENT_STATUS & (~getForcedWindowFlags())); } if (a.getBoolean(R.styleable.Window_windowTranslucentNavigation, false)) { setFlags(FLAG_TRANSLUCENT_NAVIGATION, FLAG_TRANSLUCENT_NAVIGATION & (~getForcedWindowFlags())); } // ...... if (a.getBoolean(R.styleable.Window_windowContentTransitions, false)) { requestFeature(FEATURE_CONTENT_TRANSITIONS); } if (a.getBoolean(R.styleable.Window_windowActivityTransitions, false)) { requestFeature(FEATURE_ACTIVITY_TRANSITIONS); } mIsTranslucent = a.getBoolean(R.styleable.Window_windowIsTranslucent, false); final Context context = getContext(); final int targetSdk = context.getApplicationInfo().targetSdkVersion; final boolean targetPreL = targetSdk < android.os.Build.VERSION_CODES.LOLLIPOP; final boolean targetPreQ = targetSdk < Build.VERSION_CODES.Q; if (!mForcedStatusBarColor) { mStatusBarColor = a.getColor(R.styleable.Window_statusBarColor, 0xFF000000); } if (!mForcedNavigationBarColor) { mNavigationBarColor = a.getColor(R.styleable.Window_navigationBarColor, 0xFF000000); mNavigationBarDividerColor = a.getColor(R.styleable.Window_navigationBarDividerColor, 0x00000000); } if (!targetPreQ) { mEnsureStatusBarContrastWhenTransparent = a.getBoolean( R.styleable.Window_enforceStatusBarContrast, false); mEnsureNavigationBarContrastWhenTransparent = a.getBoolean( R.styleable.Window_enforceNavigationBarContrast, true); } WindowManager.LayoutParams params = getAttributes(); // Non-floating windows on high end devices must put up decor beneath the system bars and // therefore must know about visibility changes of those. if (!mIsFloating) { if (!targetPreL && a.getBoolean( R.styleable.Window_windowDrawsSystemBarBackgrounds, false)) { setFlags(FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS, FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS & ~getForcedWindowFlags()); } if (mDecor.mForceWindowDrawsBarBackgrounds) { params.privateFlags |= PRIVATE_FLAG_FORCE_DRAW_BAR_BACKGROUNDS; } } if (a.getBoolean(R.styleable.Window_windowLightStatusBar, false)) { decor.setSystemUiVisibility( decor.getSystemUiVisibility() | View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR); } if (a.getBoolean(R.styleable.Window_windowLightNavigationBar, false)) { decor.setSystemUiVisibility( decor.getSystemUiVisibility() | View.SYSTEM_UI_FLAG_LIGHT_NAVIGATION_BAR); } // ...... // Inflate the window decor. int layoutResource; int features = getLocalFeatures(); // System.out.println("Features: 0x" + Integer.toHexString(features)); if ((features & ((1 << FEATURE_LEFT_ICON) | (1 << FEATURE_RIGHT_ICON))) != 0) { if (mIsFloating) { TypedValue res = new TypedValue(); getContext().getTheme().resolveAttribute( R.attr.dialogTitleIconsDecorLayout, res, true); layoutResource = res.resourceId; } else { layoutResource = R.layout.screen_title_icons; } // XXX Remove this once action bar supports these features. removeFeature(FEATURE_ACTION_BAR); // System.out.println("Title Icons!"); } else if ((features & ((1 << FEATURE_PROGRESS) | (1 << FEATURE_INDETERMINATE_PROGRESS))) != 0 && (features & (1 << FEATURE_ACTION_BAR)) == 0) { // Special case for a window with only a progress bar (and title). // XXX Need to have a no-title version of embedded windows. layoutResource = R.layout.screen_progress; // System.out.println("Progress!"); } else if ((features & (1 << FEATURE_CUSTOM_TITLE)) != 0) { // Special case for a window with a custom title. // If the window is floating, we need a dialog layout if (mIsFloating) { TypedValue res = new TypedValue(); getContext().getTheme().resolveAttribute( R.attr.dialogCustomTitleDecorLayout, res, true); layoutResource = res.resourceId; } else { layoutResource = R.layout.screen_custom_title; } // XXX Remove this once action bar supports these features. removeFeature(FEATURE_ACTION_BAR); } else if ((features & (1 << FEATURE_NO_TITLE)) == 0) { // If no other features and not embedded, only need a title. // If the window is floating, we need a dialog layout if (mIsFloating) { TypedValue res = new TypedValue(); getContext().getTheme().resolveAttribute( R.attr.dialogTitleDecorLayout, res, true); 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; } // System.out.println("Title!"); } else if ((features & (1 << FEATURE_ACTION_MODE_OVERLAY)) != 0) { layoutResource = R.layout.screen_simple_overlay_action_mode; } else { // Embedded, so no decoration is needed. layoutResource = R.layout.screen_simple; // System.out.println("Simple!"); } mDecor.startChanging(); mDecor.onResourcesLoaded(mLayoutInflater, layoutResource); ViewGroup contentParent = (ViewGroup)findViewById(ID_ANDROID_CONTENT); if (contentParent == null) { throw new RuntimeException("Window couldn't find content container view"); } // ...... mDecor.finishChanging(); return contentParent; }
虽然方法很长,但是其实很简单,主要是读取Activity自己设置的窗口风格属性,然后进行一些配置,比较重要的部分有:
1、设置Window flags,如:
if (a.getBoolean(R.styleable.Window_windowFullscreen, false)) {
setFlags(FLAG_FULLSCREEN, FLAG_FULLSCREEN & (~getForcedWindowFlags()));
}
2、启用Screen features,如:
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);
}
3、设置StatusBar和NavigationBar的背景色,如:
if (!mForcedStatusBarColor) {
mStatusBarColor = a.getColor(R.styleable.Window_statusBarColor, 0xFF000000);
}
if (!mForcedNavigationBarColor) {
mNavigationBarColor = a.getColor(R.styleable.Window_navigationBarColor, 0xFF000000);
mNavigationBarDividerColor = a.getColor(R.styleable.Window_navigationBarDividerColor,
0x00000000);
}
4、设置SystemUI flags,如:
if (a.getBoolean(R.styleable.Window_windowLightStatusBar, false)) {
decor.setSystemUiVisibility(
decor.getSystemUiVisibility() | View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR);
}
if (a.getBoolean(R.styleable.Window_windowLightNavigationBar, false)) {
decor.setSystemUiVisibility(
decor.getSystemUiVisibility() | View.SYSTEM_UI_FLAG_LIGHT_NAVIGATION_BAR);
}
5、设置PhoneWindow的一些变量,如:
mIsTranslucent = a.getBoolean(R.styleable.Window_windowIsTranslucent, false);
// ... ...
if (!targetPreQ) {
mEnsureStatusBarContrastWhenTransparent = a.getBoolean(
R.styleable.Window_enforceStatusBarContrast, false);
mEnsureNavigationBarContrastWhenTransparent = a.getBoolean(
R.styleable.Window_enforceNavigationBarContrast, true);
}
6、选取应该加载的布局资源ID:
// Inflate the window decor. int layoutResource; int features = getLocalFeatures(); // System.out.println("Features: 0x" + Integer.toHexString(features)); if ((features & ((1 << FEATURE_LEFT_ICON) | (1 << FEATURE_RIGHT_ICON))) != 0) { if (mIsFloating) { TypedValue res = new TypedValue(); getContext().getTheme().resolveAttribute( R.attr.dialogTitleIconsDecorLayout, res, true); layoutResource = res.resourceId; } else { layoutResource = R.layout.screen_title_icons; } // XXX Remove this once action bar supports these features. removeFeature(FEATURE_ACTION_BAR); // System.out.println("Title Icons!"); } else if ((features & ((1 << FEATURE_PROGRESS) | (1 << FEATURE_INDETERMINATE_PROGRESS))) != 0 && (features & (1 << FEATURE_ACTION_BAR)) == 0) { // Special case for a window with only a progress bar (and title). // XXX Need to have a no-title version of embedded windows. layoutResource = R.layout.screen_progress; // System.out.println("Progress!"); } else if ((features & (1 << FEATURE_CUSTOM_TITLE)) != 0) { // Special case for a window with a custom title. // If the window is floating, we need a dialog layout if (mIsFloating) { TypedValue res = new TypedValue(); getContext().getTheme().resolveAttribute( R.attr.dialogCustomTitleDecorLayout, res, true); layoutResource = res.resourceId; } else { layoutResource = R.layout.screen_custom_title; } // XXX Remove this once action bar supports these features. removeFeature(FEATURE_ACTION_BAR); } else if ((features & (1 << FEATURE_NO_TITLE)) == 0) { // If no other features and not embedded, only need a title. // If the window is floating, we need a dialog layout if (mIsFloating) { TypedValue res = new TypedValue(); getContext().getTheme().resolveAttribute( R.attr.dialogTitleDecorLayout, res, true); 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; } // System.out.println("Title!"); } else if ((features & (1 << FEATURE_ACTION_MODE_OVERLAY)) != 0) { layoutResource = R.layout.screen_simple_overlay_action_mode; } else { // Embedded, so no decoration is needed. layoutResource = R.layout.screen_simple; // System.out.println("Simple!"); } mDecor.startChanging(); mDecor.onResourcesLoaded(mLayoutInflater, layoutResource);
这里根据Activity的主题设置取得最符合的布局文件ID后,调用DecorView#onResourcesLoaded去实例化一个相符合的View层次结构:
void onResourcesLoaded(LayoutInflater inflater, int layoutResource) { if (mBackdropFrameRenderer != null) { loadBackgroundDrawablesIfNeeded(); mBackdropFrameRenderer.onResourcesLoaded( this, mResizingBackgroundDrawable, mCaptionBackgroundDrawable, mUserCaptionBackgroundDrawable, getCurrentColor(mStatusColorViewState), getCurrentColor(mNavigationColorViewState)); } 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 { // Put it below the color views. addView(root, 0, new ViewGroup.LayoutParams(MATCH_PARENT, MATCH_PARENT)); } mContentRoot = (ViewGroup) root; initializeElevation(); }
这里不去考虑DecorCaptionView存在的情况,那么这里的主要工作就是:
final View root = inflater.inflate(layoutResource, null);
addView(root, 0, new ViewGroup.LayoutParams(MATCH_PARENT, MATCH_PARENT));
这里我本地调试,取到的布局资源ID为:R.layout.screen_simple,对应的xml是:frameworks/base/core/res/res/layout/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>
布局文件本身是一个LinearLayout,包含一个ViewStub和FrameLayout,再参考dumpsys出来的信息,是对上了的:
DecorView@7b3553[MainActivity]
android.widget.LinearLayout{fe72e90 V.E...... ........ 0,0-1200,1904}
android.view.ViewStub{8fee389 G.E...... ......I. 0,0-0,0 #10201c3 android:id/action_mode_bar_stub}
android.widget.FrameLayout{2d0cf8e V.E...... ........ 0,48-1200,1904}
7、找到ID_ANDROID_CONTENT对应的View,并且返回。
ViewGroup contentParent = (ViewGroup)findViewById(ID_ANDROID_CONTENT);
if (contentParent == null) {
throw new RuntimeException("Window couldn't find content container view");
}
// ... ...
mDecor.finishChanging();
return contentParent;
因为在第6步,我们已经得到了一个基于R.layout.screen_simple生成的View层次结构,那么就可以通过findViewById找到一个ID为ID_ANDROID_CONTENT的子View,然后返回该子View。
/**
* 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;
那么最终,通过PhoneWindow#generateLayout生成了一个View层次结构,并且找到其ID为R.id.content的子View并且返回该子View,然后赋值给了PhoneWindow.mContentParent,这里看到返回的是一个FrameLayout:
android.widget.FrameLayout{2d0cf8e V.E...... ........ 0,48-1200,1904}
最后将PhoneWindow.mContentParent指向这个返回的ViewGroup对象。
之前分析PhoneWindow#installDecor的时候,说了AppCompatActivity由于重写了Activity#setContentView方法,导致先走了上面分析的PhoneWindow#installDecor方法,PhoneWindow#setContentView会在稍晚的一个时间点被调用。
先看下调用堆栈信息:
08-20 09:38:31.734 6254 6254 I ukynho_decor: PhoneWindow#setContentView ---- title = null mContentParent = android.widget.FrameLayout{2d0cf8e V.E...... ........ 0,48-1200,1904} view = androidx.appcompat.widget.ActionBarOverlayLayout{7f9d3af V.E...... ........ 0,0-1200,1856 #7f07004d app:id/decor_content_parent} 08-20 09:38:31.734 6254 6254 I ukynho_decor: java.lang.Throwable 08-20 09:38:31.734 6254 6254 I ukynho_decor: at com.android.internal.policy.PhoneWindow.setContentView(PhoneWindow.java:476) 08-20 09:38:31.734 6254 6254 I ukynho_decor: at com.android.internal.policy.PhoneWindow.setContentView(PhoneWindow.java:471) 08-20 09:38:31.734 6254 6254 I ukynho_decor: at androidx.appcompat.app.AppCompatDelegateImpl.createSubDecor(AppCompatDelegateImpl.java:855) 08-20 09:38:31.734 6254 6254 I ukynho_decor: at androidx.appcompat.app.AppCompatDelegateImpl.ensureSubDecor(AppCompatDelegateImpl.java:659) 08-20 09:38:31.734 6254 6254 I ukynho_decor: at androidx.appcompat.app.AppCompatDelegateImpl.setContentView(AppCompatDelegateImpl.java:552) 08-20 09:38:31.734 6254 6254 I ukynho_decor: at androidx.appcompat.app.AppCompatActivity.setContentView(AppCompatActivity.java:161) 08-20 09:38:31.734 6254 6254 I ukynho_decor: at com.qq.reader.activity.MainActivity.onCreate(MainActivity.java:29) 08-20 09:38:31.734 6254 6254 I ukynho_decor: at android.app.Activity.performCreate(Activity.java:8012) 08-20 09:38:31.734 6254 6254 I ukynho_decor: at android.app.Activity.performCreate(Activity.java:7996) 08-20 09:38:31.734 6254 6254 I ukynho_decor: at android.app.Instrumentation.callActivityOnCreate(Instrumentation.java:1311) 08-20 09:38:31.734 6254 6254 I ukynho_decor: at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:3525) 08-20 09:38:31.734 6254 6254 I ukynho_decor: at android.app.ActivityThread.handleLaunchActivity(ActivityThread.java:3713) 08-20 09:38:31.734 6254 6254 I ukynho_decor: at android.app.servertransaction.LaunchActivityItem.execute(LaunchActivityItem.java:85) 08-20 09:38:31.734 6254 6254 I ukynho_decor: at android.app.servertransaction.TransactionExecutor.executeCallbacks(TransactionExecutor.java:135) 08-20 09:38:31.734 6254 6254 I ukynho_decor: at android.app.servertransaction.TransactionExecutor.execute(TransactionExecutor.java:95) 08-20 09:38:31.734 6254 6254 I ukynho_decor: at android.app.ActivityThread$H.handleMessage(ActivityThread.java:2156) 08-20 09:38:31.734 6254 6254 I ukynho_decor: at android.os.Handler.dispatchMessage(Handler.java:106) 08-20 09:38:31.734 6254 6254 I ukynho_decor: at android.os.Looper.loop(Looper.java:236) 08-20 09:38:31.734 6254 6254 I ukynho_decor: at android.app.ActivityThread.main(ActivityThread.java:7814) 08-20 09:38:31.734 6254 6254 I ukynho_decor: at java.lang.reflect.Method.invoke(Native Method) 08-20 09:38:31.734 6254 6254 I ukynho_decor: at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:621) 08-20 09:38:31.734 6254 6254 I ukynho_decor: at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:997)
再看下方法的具体内容:
@Override public void setContentView(View view, ViewGroup.LayoutParams params) { // 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(); } if (hasFeature(FEATURE_CONTENT_TRANSITIONS)) { view.setLayoutParams(params); final Scene newScene = new Scene(mContentParent, view); transitionTo(newScene); } else { mContentParent.addView(view, params); } mContentParent.requestApplyInsets(); final Callback cb = getCallback(); if (cb != null && !isDestroyed()) { cb.onContentChanged(); } mContentParentExplicitlySet = true; }
到达这里时,通过打印的log,可以知道,此时:
1)、我们前面的分析PhoneWindow#generateLayout返回了一个ViewGroup对象:
android.widget.FrameLayout{2d0cf8e V.E...... ........ 0,48-1200,1904}
并且PhoneWindow将其成员变量mContentParent指向该对象。
再看到我们加在这边的log,结果是一致的:
mContentParent = android.widget.FrameLayout{2d0cf8e V.E...... ........ 0,48-1200,1904},
2)、根据log,此时传入的View是AppCompatActivity那边已经加载好的View层次结构的root View:
view = androidx.appcompat.widget.ActionBarOverlayLayout{7f9d3af V.E...... ........ 0,0-1200,1856 #7f07004d app:id/decor_content_parent}
然后通过:
mContentParent.addView(view, params);
将传入的View作为子View加入的mContentParent的层级结构中,从而将DecorView与AppCompatActivity部分连接起来:
DecorView@7b3553[MainActivity]
android.widget.LinearLayout{fe72e90 V.E...... ........ 0,0-1200,1904}
android.view.ViewStub{8fee389 G.E...... ......I. 0,0-0,0 #10201c3 android:id/action_mode_bar_stub}
android.widget.FrameLayout{2d0cf8e V.E...... ........ 0,48-1200,1904}
androidx.appcompat.widget.ActionBarOverlayLayout{7f9d3af V.E...... ........ 0,0-1200,1856 #7f07004d app:id/decor_content_parent}
3)、后续AppCompatActivity部分再调用LayoutInflater.inflate,解析我们自定义的layout布局文件R.layout.activity_main,生成我们定义的View层次结构:
android.widget.RelativeLayout{5692f45 V.E...... ........ 0,0-1200,1728}
androidx.appcompat.widget.AppCompatButton{e8e569a VFED..C.. ........ 200,200-376,296 #7f070059 app:id/getSize}
androidx.appcompat.widget.AppCompatButton{619fbcb VFED..C.. ........ 0,296-243,392 #7f07009c app:id/startAnother}
androidx.appcompat.widget.AppCompatButton{dca3fa8 VFED..C.. ........ 0,392-220,488 #7f070058 app:id/getLocation}
并且AppCompatActivity部分在调用LayoutInflater.inflate的时候,指定了parent View,从而将我们自定义的View层次结构添加到AppCompatActivity自己的View层级结构中,指定的parent View为:
androidx.appcompat.widget.ContentFrameLayout{c38a3bc V.E...... ........ 0,128-1200,1856 #1020002 android:id/content}
从而将AppCompatActivity和我们的自定义布局RelativeLayout连接了起来:
androidx.appcompat.widget.ContentFrameLayout{c38a3bc V.E...... ........ 0,128-1200,1856 #1020002 android:id/content}
android.widget.RelativeLayout{5692f45 V.E...... ........ 0,0-1200,1728}
androidx.appcompat.widget.AppCompatButton{e8e569a VFED..C.. ........ 200,200-376,296 #7f070059 app:id/getSize}
androidx.appcompat.widget.AppCompatButton{619fbcb VFED..C.. ........ 0,296-243,392 #7f07009c app:id/startAnother}
androidx.appcompat.widget.AppCompatButton{dca3fa8 VFED..C.. ........ 0,392-220,488 #7f070058 app:id/getLocation}
4)、这里看到AppCompatActivity起到了一个连接的作用,首先它既调用了PhoneWIndow#setContentView,将自己的内部的View层次结构的根View传到PhoneWindow中,在PhoneWindow作为子View加入到了DecorView的层级结构中;另一方面,在自己内部,又负责解析我们的自定义布局,并且将我们的自定义布局的根View作为子View加入到它的View层级结构中。
AppCompatActivity的部分为:
最终形成了最终的View层级结构:
DecorView@b8fdc2b[MainActivity] android.widget.LinearLayout{f289afc V.E...... ........ 0,0-1080,1344} android.view.ViewStub{e6af585 G.E...... ......I. 0,0-0,0 #10201c3 android:id/action_mode_bar_stub} android.widget.FrameLayout{4373fda V.E...... ........ 0,74-1080,1344} androidx.appcompat.widget.ActionBarOverlayLayout{d96cc0b V.E...... ........ 0,0-1080,1270 #7f07004d app:id/decor_content_parent} androidx.appcompat.widget.ContentFrameLayout{6e26ae8 V.E...... ........ 0,112-1080,1270 #1020002 android:id/content} android.widget.RelativeLayout{26a6501 V.E...... ........ 0,0-1080,1158} androidx.appcompat.widget.AppCompatButton{1d97fa6 VFED..C.. ........ 0,0-176,96 #7f070058 app:id/getSize} androidx.appcompat.widget.AppCompatButton{ee41de7 VFED..C.. ........ 0,96-243,192 #7f07009b app:id/startAnother} androidx.appcompat.widget.ActionBarContainer{fe74d94 V.ED..... ........ 0,0-1080,112 #7f070029 app:id/action_bar_container} androidx.appcompat.widget.Toolbar{397503d V.E...... ........ 0,0-1080,112 #7f070027 app:id/action_bar} androidx.appcompat.widget.AppCompatTextView{e0df032 V.ED..... ........ 32,29-300,83} androidx.appcompat.widget.ActionMenuView{3cab183 V.E...... ........ 1080,0-1080,112} androidx.appcompat.widget.ActionBarContextView{db5af00 G.E...... ......I. 0,0-0,0 #7f07002f app:id/action_context_bar} android.view.View{badb339 V.ED..... ........ 0,1344-1080,1440 #1020030 android:id/navigationBarBackground} android.view.View{ca6dd7e V.ED..... ........ 0,0-1080,74 #102002f android:id/statusBarBackground}
DecorView的View层级结构,从上面分析,其实是由三个层级结构组成的:
1)、PhoneWindow根据Activity设置的主题风格,先生成了一个View层级结构,这部分是最顶级的;
2)、我们自己定义的View层级结构,这个是根据我们通过Activity#setContentView中传入的xml类型的layout文件解析出的View层级结构,属于第三级;
3)、AppCompatActivity也有自己的层级结构,这部分属于第二级,在DecorView的View层级结构和我们自定义布局的View层级结构之间起到了连接作用。
另外,我们通过打断点,也可以看到有三次LayoutInflater.inflate调用。
第一次:在PhoneWindow#generateLayout中,这里我们根据Activity的主题风格,选择了一个布局ID,然后调用LayoutInflater.inflate生成了一个View层级结构,根View为:
android.widget.LinearLayout{f289afc V.E...... ........ 0,0-1080,1344}
然后在DecorView#onResourcesLoaded方法中,通过View#addView的方式将该root View作为子View加入到自己的mChildren数组中:
第二次:由AppCompatActivity调用,生成了AppCompatActivity的View层级结构,根View为:
androidx.appcompat.widget.ActionBarOverlayLayout{d96cc0b V.E...... ........ 0,0-1080,1270 #7f07004d app:id/decor_content_parent}
然后AppCompatActivity又调用PhoneWindow#setContentVIew将自己的根View作为子View加入到DecorView的View层级结构中去:
第三次:由AppCompatActivity调用,解析我们在自己定义的Activity中通过Activity#setContentView传入的布局ID,生成了我们定义的View层级结构,根View为:
android.widget.RelativeLayout{26a6501 V.E...... ........ 0,0-1080,1158}
并且这里在调用LayoutInflater.inflate的时候,指定了parent View为
androidx.appcompat.widget.ContentFrameLayout{6e26ae8 V.E...... ........ 0,112-1080,1270 #1020002 android:id/content}
从而将我们自定义的View层级结构作为子View加入到了AppCompatActivity的VIew层级结构中:
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。