1.前言
在前面的几篇文章中详细分析了activity的启动之后,接下来这篇文章便来探索一下,activity的界面是如何生成的。主要解决activity界面的生成过程中涉及到哪些类,及他们是如何协作的。在分析这些过程之前,我们先来看一段话:来自老罗的文章
每一个Activity组件都有一个关联的Window对象,用来描述一个应用程序窗口。每一个应用程序窗口内部又包含有一个View对象,用来描述应用程序窗口的视图。应用程序窗口视图是真正用来实现UI内容和布局的,也就是说,每一个Activity组件的UI内容和布局都是通过与其所关联的一个Window对象的内部的一个View对象来实现的。
这段话帮我理解了activity与activity的界面之间的关系,从而也有了这篇文章的大纲。在此感谢老罗!
- 与activity关联的应用程序窗口是如何创建的?
- 应用程序窗口的视图对象是如何生成的?
- 生成的应用程序窗口的视图对象是如何添加到手机屏幕上的?
2.创建应用程序的窗口对象
在Activity启动流程(下)这篇文章中曾经说到过,ActivityThread的performLaunchActivity方法中会通过反射创建一个activity实例,然后调用activity的attach方法完成activity的初始化过程,应用程序的窗口对象的创建就是在这个过程中完成。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)
{
......
//1.创建一个窗口对象
mWindow = new PhoneWindow(this);
//2.为当前的activity设置回调,一遍activity能处理屏幕事件
mWindow.setCallback(this);
......
mToken = token;
......
//3.将创建的WindowManager注入窗口对象以便管理窗口的视图对象
mWindow.setWindowManager(
(WindowManager)context.getSystemService(Context.WINDOW_SERVICE),
mToken, mComponent.flattenToString(),
(info.flags & ActivityInfo.FLAG_HARDWARE_ACCELERATED) != 0);
if (mParent != null) {
mWindow.setContainer(mParent.getWindow());
}
//4.获取注入窗口的视图对象用来管理activity界面的显示
mWindowManager = mWindow.getWindowManager();
......
}
- 注释1创建了一个PhoneWindow对象,其实Window对象是一个抽象类,在手机app开发中具体的应用程序的窗口功能由PhoneWindow承担,所以我们经常讨论的应用程序的窗口都是指PhoneWindow。
- 注释2说明activity本身不具备处理用户的事件的能力,用户事件都是又应用程序窗口转给activity,开发中才能在activity响应用户的事件。
- 注释3的代码会创建一个WindowManager的实现类,并将这个实现类保存到Window的类型为WindowManager的mWindowManager中,以便可以通过getWindowManager来得到WindowManager的实现类,从而管理Window的视图对象。
- 注释4获取Window的WindowManager的实现类WindowManagerImpl保存在activity的mWindowManager中。
下面我们再跟进注释3的代码,看看WindowManager的实现类WindowManagerImpl是如何创建的。Window的setWindowManager方法如下:
public void setWindowManager(WindowManager wm, IBinder appToken, String appName,
boolean hardwareAccelerated) {
mAppToken = appToken;
mAppName = appName;
mHardwareAccelerated = hardwareAccelerated
|| SystemProperties.getBoolean(PROPERTY_HARDWARE_UI, false);
if (wm == null) {
wm = (WindowManager)mContext.getSystemService(Context.WINDOW_SERVICE);
}
//创建WindowManager的实现类WindowManagerImpl并赋值给mWindowManager
mWindowManager = ((WindowManagerImpl)wm).createLocalWindowManager(this);
}
可以看见,Window的setWindowManager方法中通过WindowManagerImpl实例的createLocalWindowManager方法获取了WindowManager实例,如下:
public final class WindowManagerImpl implements WindowManager {
......
private WindowManagerImpl(Display display, Window parentWindow) {
mDisplay = display;
mParentWindow = parentWindow;
}
......
public WindowManagerImpl createLocalWindowManager(Window parentWindow) {
return new WindowManagerImpl(mDisplay, parentWindow);
}
......
}
这样WindowManager的实现类WindowManagerImpl就创建好了,到这里activity的窗口对象以及管理窗口对象的视图的WindowManage都创建好了,下一步就开始创建activity的窗口对象的视图。
3.应用程序的窗口对象视图的创建
还记得当初刚接触Android的时候,有一个疑惑,在activity的onCreate方法中为什么可以通过setContentView将我们写的布局绘制到手机屏幕上。现在这个疑惑终于解开了,下面跟踪源码一步一步的来看setContentView是如何将我们绘制的布局加载到手机界面上的。首先来看activity的setContentView方法:
public void setContentView(@LayoutRes int layoutResID) {
getWindow().setContentView(layoutResID);
initWindowDecorActionBar();
}
getWindow()将会得到在上一个小节创建的一个PhoneWindow对象,然后调用PhoneWindow的setContentView方法:
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.
//1.如果第一次调用,则mContentParent为空
if (mContentParent == null) {
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 {
//2.将开发中的xml文件转化成视图填充到mContentParent
mLayoutInflater.inflate(layoutResID, mContentParent);
}
final Callback cb = getCallback();
if (cb != null && !isDestroyed()) {
cb.onContentChanged();
}
}
- 注释1由于第一次调用这个方法,因此mContentParent 为空,因此会调用PhoneWindow的installDecor方法:
private void installDecor() {
if (mDecor == null) {
mDecor = generateDecor();
mDecor.setDescendantFocusability(ViewGroup.FOCUS_AFTER_DESCENDANTS);
mDecor.setIsRootNamespace(true);
if (!mInvalidatePanelMenuPosted && mInvalidatePanelMenuFeatures != 0) {
mDecor.postOnAnimation(mInvalidatePanelMenuRunnable);
}
}
if (mContentParent == null) {
//根据窗口的风格修饰,选择对应的修饰布局文件,并将id为content的FrameLayout赋值给mContentParent
mContentParent = generateLayout(mDecor);
......
}
}
这段代码主要做了两件事:
- 通过generateDecor()创建了一个窗口视图对象FrameLayout的变量mDecor
- generateLayout(mDecor)根据窗口的风格修饰,选择对应的修饰布局文件,并且将id为content的FrameLayout赋值给mContentParent:
protected ViewGroup generateLayout(DecorView decor) {
// Apply data from current theme.
TypedArray a = getWindowStyle();
//根据当前的主题设置窗口属性
......
// Inflate the window decor.
int layoutResource;
int features = getLocalFeatures();
//根据当前的窗口属性选择相对应的布局
......
//将相应的布局文件转成view添加到窗口视图对象decor中
View in = mLayoutInflater.inflate(layoutResource, null);
decor.addView(in, new ViewGroup.LayoutParams(MATCH_PARENT, MATCH_PARENT));
mContentRoot = (ViewGroup) in;
ViewGroup contentParent = (ViewGroup)findViewById(ID_ANDROID_CONTENT);
if (contentParent == null) {
throw new RuntimeException("Window couldn't find content container view");
}
......
return contentParent;
}
这段代码主要做了4件事:
- 根据当前的主题设置窗口属性
- 根据当前的窗口属性选择相对应的布局
- 将相应的布局文件转成view添加到窗口视图对象decor中
View in = mLayoutInflater.inflate(layoutResource, null); decor.addView(in, new ViewGroup.LayoutParams(MATCH_PARENT, MATCH_PARENT)); - 从根布局中找出id为content的控件赋值给contentParent
iewGroup contentParent = (ViewGroup)findViewById(ID_ANDROID_CONTENT);
到这里再转过头来看PhoneWindow的setContentView方法的注释2:
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.
//1.如果第一次调用,则mContentParent为空
if (mContentParent == null) {
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 {
//2.将开发中的xml文件转化成视图填充到mContentParent
mLayoutInflater.inflate(layoutResID, mContentParent);
}
final Callback cb = getCallback();
if (cb != null && !isDestroyed()) {
cb.onContentChanged();
}
}
- 注释2将我们从activity的setContentView中传过来的布局文件解析成view填充到mContentParent中。
到此我们开发中的布局文件就封装在了应用程序的视图DecorView中。下一步中,我们就通过第一步中创建好的WindowManagerImpl将视图发送给WindowManagerService中以便于WindowManagerService将视图绘制到手机屏幕上。
4.将生成的应用程序窗口的视图对象是添加到手机屏幕
我们知道在开发中我们的视图需要在activity的生命周期onResume执行之后才会显。这是因为
final void handleResumeActivity(IBinder token,
boolean clearHide, boolean isForward, boolean reallyResume) {
// If we are getting ready to gc after going to the background, well
// we are back active so skip it.
......
// TODO Push resumeArgs into the activity for consideration
//1.调用了activity的onResume方法
ActivityClientRecord r = performResumeActivity(token, clearHide);
if (r != null) {
......
// If the window hasn't yet been added to the window manager,
// and this guy didn't finish itself or start another activity,
// then go ahead and add the window.
......
// If the window has already been added, but during resume
// we started another activity, then don't yet make the
// window visible.
......
// The window is now visible if it has been added, we are not
// simply finishing, and we are not starting another activity.
if (!r.activity.mFinished && willBeVisible
&& r.activity.mDecor != null && !r.hideForNow) {
......
if (r.activity.mVisibleFromClient) {
//2.通过WindowManagerImpl将DecorView展示出来
r.activity.makeVisible();
}
}
......
} else {
// If an exception was thrown when trying to resume, then
// just end this activity.
......
}
}
- 注释1最后会调用activity的onResume方法
- 注释2通过调用activity的makeVisible方法经WindowManagerImpl将DecorView展示出来
activity的makeVisible方法如下:
void makeVisible() {
if (!mWindowAdded) {
ViewManager wm = getWindowManager();
wm.addView(mDecor, getWindow().getAttributes());
mWindowAdded = true;
}
mDecor.setVisibility(View.VISIBLE);
}
这里的wm是一个WindowManagerImpl对象,然后调用了WindowManagerImpl的addView方法:
@Override
public void addView(@NonNull View view, @NonNull ViewGroup.LayoutParams params) {
applyDefaultToken(params);
mGlobal.addView(view, params, mDisplay, mParentWindow);
}
mGlobal是一个WindowManagerGlobal类型的实例,接着调用了WindowManagerGlobal的addView方法:
public void addView(View view, ViewGroup.LayoutParams params,
Display display, Window parentWindow) {
......
//1.将传进来的ViewGroup.LayoutParams类型的params转成 WindowManager.LayoutParams类型的wparams
final WindowManager.LayoutParams wparams = (WindowManager.LayoutParams) params;
//2.如果WindowManagerImpl是在activity的方法中被创建则不为空
if (parentWindow != null) {
parentWindow.adjustLayoutParamsForSubWindow(wparams);
} else {
......
}
ViewRootImpl root;
View panelParentView = null;
synchronized (mLock) {
......
root = new ViewRootImpl(view.getContext(), display);
view.setLayoutParams(wparams);
//3.将视图对象view,ViewRootImpl以及wparams分别存入相应集合的对应位置
mViews.add(view);
mRoots.add(root);
mParams.add(wparams);
}
// do this last because it fires off messages to start doing things
try {
//4.通过ViewRootImpl联系WindowManagerService将view绘制到屏幕上
root.setView(view, wparams, panelParentView);
} catch (RuntimeException e) {
// BadTokenException or InvalidDisplayException, clean up.
synchronized (mLock) {
final int index = findViewLocked(view, false);
if (index >= 0) {
removeViewLocked(index, true);
}
}
throw e;
}
}
这段代码有四个地方值得注意:
- 注释1将传入的ViewGroup.LayoutParams类型的params赋值给wparams以便viewRootImpl在与WindowManagerService联系中能提供窗口视图的属性
- 注释2如果调用是在activity中会通过PhoneWindow的adjustLayoutParamsForSubWindow调整子窗口的属性。这个在创建dialog的时候只能用activity作为context分析中有用。
- 注释3在WindowManagerGlobal有3个集合分别用来装描述窗口对象的3个对象DecorView,ViewRootImpl,WindowManager.LayoutParams对象,并且他们是一一对应的关系。
- 注释4通过ViewRootImpl与WindowManagerService交互,包括view的绘制流程和调用WindowManagerService的相关方法将视图对象绘制到手机屏幕上。
经过这四个步骤以后,activity的界面就能绘制到手机屏幕上了。
5.总结
Activity界面绘制流程包括应用程序窗口对象PhoneWindow的创建,包括应用程序窗口的管理对象WindowManagerImpl的创建以及窗口视图对象DecorView的生成和填充布局。有了这几个步骤activity就可以请求WindowManagerService将自己的视图绘制到屏幕上了。
网友评论