概述
本篇文章从源码(基于Android 8.1)角度,由Activity中的setContentView开始切入分析Android中Activity的View的绘制流程,侧重于layoutResID加载流程的分析,对
在进行实际的分析之前,我们先来看下面这张图:
一、基本关系
在这里先把Activity作为Android手机与用户交互视图中顶级的组件。
Window作为Activity中的一个具体对窗口的管理类,但是Window是一个抽象类,它提供了绘制窗口的一组通用API,真正执行各项操作的是它的子类PhoneWindow实体类。PhoneWindow中再次将用户可视的View的绘制等工作分离出,并且把绘制工作交给它的一个私有成员类DecorView;所以就可视View而言DecoView是Activity的根View;
DecorView继承自FrameLayout,在内部根据用户设置的Activity主题对FrameLayout进行修饰,为用户提供给定Theme下的布局样式。如上图所示,一般情况下,DecorView中包含一个用于显示Activity标题的TitleView和一个用于显示内容的ContentView。
一、开工
1.Activity.setContentView(int layoutResID)
同样是从Activity的setContentView()出发;
在Activity中setContentView()源码:
public void setContentView(@LayoutRes int layoutResID) {
getWindow().setContentView(layoutResID);//获取Window对象,并且将layoutResID交给Window进一步处理;
initWindowDecorActionBar();//初始化DecorView 和ActionBar
}
public Window getWindow() {
return mWindow;
}
final void attach(//省略若干参数) {
//这里省略若干行代码……
mWindow = new PhoneWindow(this, window, activityConfigCallback);//mWindow 实例
mWindow.setWindowControllerCallback(this);
mWindow.setCallback(this);
mWindow.setOnWindowDismissedCallback(this);
mWindow.getLayoutInflater().setPrivateFactory(this);
//这里省略若干行代码……
}
根据上述源码中的得到:在Activity中主要干了两件事: 将layoutResID传递给交给Window,并且交给Window中的setContentView(layoutResID)继续处理,然后初始化DecorView 和ActionBar;
跟踪代码我们发现Window一个abstract类,在Activity中的attach()中new了一个PhoneWindow对象;所以PhoneWindow才只是getWindow().setContentView(layoutResID)的具体是执行者;
2.PhoneWindow.setContentView()
继续跟踪代码;
PhoneWindow.setContentView源码;
public void setContentView(int layoutResID) {
if (this.mContentParent == null) {
//初识化参数: 1 创建DecorView(FrameLayout)对象。; 2 初识化mContentParent;下面贴出源码
this.installDecor();
} else if (!this.hasFeature(12)) {
//如果内容已经加载过(不是第一次加载),并且不需要动画,removeAllViews()
this.mContentParent.removeAllViews();
}
if (this.hasFeature(12)) {
// 5.0专场动画
Scene newScene = Scene.getSceneForLayout(this.mContentParent, layoutResID, this.getContext());
this.transitionTo(newScene);
} else {
//加载布局的核心方法;将Activity的ContenView的xml文件inflate入父布局mContentParent;
this.mLayoutInflater.inflate(layoutResID, this.mContentParent);
}
this.mContentParent.requestApplyInsets();
android.view.Window.Callback cb = this.getCallback();
if (cb != null && !this.isDestroyed()) {
cb.onContentChanged();
}
this.mContentParentExplicitlySet = true;
}
在PhoneWindow.setContentView中首先是通过installDecor()方法初识化了DecorView,然后将activity中xml的布局文件作为mContentParent的子VIew添加都DecorView中;其中mContentParent就是在installDecor()中一同初始化的DecorView根布局;
进一步跟踪installDecor()代码;
private void installDecor() {
mForceDecorInstall = false;
if (mDecor == null) {
//实例化DecorView
mDecor = generateDecor(-1);
//设置mDecor中的子View的聚焦性,该方法决定了mDecor与其中包含的子View之间关于焦点获取的关系
//FOCUS_AFTER_DESCENDANTS表示只有当mDecor的子View都不愿意获取焦点时,才让mDecor获取焦点mDecor.setDescendantFocusability(ViewGroup.FOCUS_AFTER_DESCENDANTS);
// 设置mDecor为整个Activity窗口的根节点,从此处可以看出窗口根节点为一个DecorView
mDecor.setIsRootNamespace(true);
if (!mInvalidatePanelMenuPosted && mInvalidatePanelMenuFeatures != 0) {
mDecor.postOnAnimation(mInvalidatePanelMenuRunnable);
}
} else {
mDecor.setWindow(this);
}
if (mContentParent == null) {
//DecorView的父布局xml文件
mContentParent = generateLayout(mDecor);
…………
}
…………
…………
}
在installDecor()中显然是在generateDecor()函数中先实例化DecorView对象,然后调用generateDecor()根据开发者设置的Theme选择对应的xml,然后通知DecorView加载xml,生成得出DecorView根布局即实例化了PhoneWindow.setContenView中的mContentParent对象,然后回到setContenView中将Activity中传入的xml布局作为字View添加到DecorView布局的ContentView布局中;
3.PhoneWindow.generateDecor(DecorView decor);
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, getContext().getResources());
if (mTheme != -1) {
context.setTheme(mTheme);
}
}
} else {
context = getContext();
}
return new DecorView(context, featureId, this, getAttributes());
}
4.PhoneWindow.generateLayout(DecorView decor)
protected ViewGroup generateLayout(DecorView decor) {
// Apply data from current theme.
// 应用当前theme的数据,下面一连串的if就是根据你设置的theme来判断当前加载的形式的
TypedArray a = getWindowStyle();
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);
}
......
......
//省略多行读style属性对Window设置的
//包括windowNoTitle、windowFullscreen、windowTranslucentStatus
......
//根据targetSdkVersion来判断是否需要加载菜单栏
final Context context = getContext();
final int targetSdk = context.getApplicationInfo().targetSdkVersion;
final boolean targetPreHoneycomb = targetSdk < android.os.Build.VERSION_CODES.HONEYCOMB;
final boolean targetPreIcs = targetSdk < android.os.Build.VERSION_CODES.ICE_CREAM_SANDWICH;
final boolean targetPreL = targetSdk < android.os.Build.VERSION_CODES.LOLLIPOP;
final boolean targetHcNeedsOptions = context.getResources().getBoolean(
R.bool.target_honeycomb_needs_options_menu);
......
//软键盘的参数
......
//加载动画
......
/**
*
* 根据开者在styles中设置的Theme和在代码中requesetFeature()设置的 Features 选择对应的xml布局文件layoutResource
* 将layoutResource布局文件inflate到DecorView中
*/
// 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;
setCloseOnSwipeEnabled(true);
} else 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();
//将layoutResource文件加载到DecorView中
mDecor.onResourcesLoaded(mLayoutInflater, layoutResource);
//这里的ID_ANDROID_CONTENT就是上图中的ContentView的根布局;
//Activity 中setContentView(layoutResID)中的layoutResID最终作为子View添加到ContentView中的;
ViewGroup contentParent = (ViewGroup)findViewById(ID_ANDROID_CONTENT);
......
//给DecView设置背景、Drawable、color等一系列的参数
......
mDecor.finishChanging();
return contentParent;
}
最后记录一下PhoneWindow源码的路径:AS的SDK路径下:\sources\android-27\com\android\internal\policy;
DecorView中xml文件路径,AS的SDK路径下:\platforms\android-27\data\res\layout