从Android应用程序启动过程中可以知道,Activity组件在启动的过程中,会调用ActivityThread类的成员函数handleLaunchActivity,来创建Activity,那么我们就来分析应用程序窗口的视图对象的创建过程。
开始本篇文章之前,如果对Android应用程序窗口(Activity)的窗口对象(Window)的创建过程分析不太清楚的可以点这个链接观看。
本篇文章主要分为这几个点:
- ActivityThread.handleLaunchActivity
- ActivityThread.performLaunchActivity
- Activity.setContentView
- PhoneWindow.setContentView
- PhoneWindow.installDecor
- ActivityThread.handleResumeActivity
- Activity.getWindowManager
- WindowManagerImpl.addView
- WindowManagerGlobal.addView
1. ActivityThread.handleLaunchActivity
public final class ActivityThread {
......
private final void handleLaunchActivity(ActivityClientRecord r, Intent customIntent) {
......
Activity a = performLaunchActivity(r, customIntent);
if (a != null) {
......
handleResumeActivity(r.token, false, r.isForward);
......
}
......
}
......
}
在这个函数中会调用performLaunchActivity来创建要启动的Activity组件,同时还会创建还会为该Activity组件创建窗口对象和视图对象。
然后通过调用ActivityThread类的成员函数handleResumeActivity来激活Activity。
接下来我们就一一分析
2. ActivityThread.performLaunchActivity
这个方法只要是创建一个Activity实例,然后调用Activity中的onCreate来进行一些初始化操作,而onCreate方法一般都是我们自己重写了,其中会在方法中设置布局文件setContentView(R.layout.main); 接下来就看下这个方法。
3. Activity.setContentView
public Window getWindow() {
return mWindow;
}
public void setContentView(@LayoutRes int layoutResID) {
getWindow().setContentView(layoutResID);
......
}
我们知道通过getWindow获得了Window对象,然后再调用这个窗口对象的setContentView,来实现创建窗口视图。
从Android应用程序窗口(Activity)的窗口对象(Window)的创建过程分析这片文章中我们可以知道这个mWindow具体是PhoneWindow,那么接下来就来看看PhoneWindow的setContentView方法。
4. PhoneWindow.setContentView
public void setContentView(int layoutResID) {
if (mContentParent == null) {
installDecor();
} else if (!hasFeature(FEATURE_CONTENT_TRANSITIONS)) {
mContentParent.removeAllViews();
}
if (hasFeature(FEATURE_CONTENT_TRANSITIONS)) {
......
} else {
mLayoutInflater.inflate(layoutResID, mContentParent);
}
final Callback cb = getCallback();
if (cb != null && !isDestroyed()) {
cb.onContentChanged();
}
......
}
PhoneWindow类的成员变量mContentParent是一个ViewGroup类型的成员变量,用作UI容器。当这个值为null的时候,说明这个这个窗口的视图还没有创建,那么就会调用installDecor();来创建窗口的视图对象。否则的话就要重新设置窗口的视图,在设置之前,会调用mContentParent.removeAllViews来移除原来的UI视图。
一开始创建的时候mContentParent 我们假设它为空,那么就会调用installDecor来创建应用程序窗口视图对象,紧接着会调用 mLayoutInflater.inflate来把参数为layoutResID的布局文件设置到窗口视图中,最后调用Callback接口的成员函数onContentChanged来通知对应的Activity组件,视图内容发生改变了。Activity组件自己实现了这个Callback接口,因此,前面实际调用的是Activity类的成员函数onContentChanged来发出一个视图内容变化通知。
5. PhoneWindow.installDecor
private void installDecor() {
mForceDecorInstall = false;
if (mDecor == null) {
mDecor = generateDecor(-1);
......
}
if (mContentParent == null) {
mContentParent = generateLayout(mDecor);
......
mTitleView = findViewById(R.id.title);
if (mTitleView != null) {
if ((getLocalFeatures() & (1 << FEATURE_NO_TITLE)) != 0) {
final View titleContainer = findViewById(R.id.title_container);
if (titleContainer != null) {
titleContainer.setVisibility(View.GONE);
} else {
mTitleView.setVisibility(View.GONE);
}
mContentParent.setForeground(null);
} else {
mTitleView.setText(mTitle);
}
}
}
}
首先进来会判断类型为DecorView的PhoneWindow类的成员变量mDecor的值是否等于null,如果为空那就调用成员函数generateLayout来创建一个DecorView对象,并用mDecor来保存。DecorView是一个FrameLayout,主要内容包含标题栏和内容栏,这个时候DecorView还是一个空白的FrameLayout
紧接着会调用另外一个成员函数generateLayout来加载当前应用程序窗口对应的布局,然后返回一个窗口UI容器,保存到成员变量mContentParent 中。
之后还会通过id来获取id为title的TexView,判断这个空间需不需要进行显示。
这一步执行完成之后,应用程序窗口视图就创建完成了,那如何让视图显示出来的那?我们接着看。
6. ActivityThread.handleResumeActivity
final void handleResumeActivity(IBinder token,
boolean clearHide, boolean isForward, boolean reallyResume, int seq, String reason) {
ActivityClientRecord r = mActivities.get(token);
.....
r = performResumeActivity(token, clearHide, reason);
if (r != null) {
final Activity a = r.activity;
......
// 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.
boolean willBeVisible = !a.mStartedActivity;
if (!willBeVisible) {
try {
willBeVisible = ActivityManager.getService().willActivityBeVisible(
a.getActivityToken());
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
}
if (r.window == null && !a.mFinished && willBeVisible) {
r.window = r.activity.getWindow();
View decor = r.window.getDecorView();
decor.setVisibility(View.INVISIBLE);
ViewManager wm = a.getWindowManager();
WindowManager.LayoutParams l = r.window.getAttributes();
a.mDecor = decor;
l.type = WindowManager.LayoutParams.TYPE_BASE_APPLICATION;
if (a.mVisibleFromClient) {
if (!a.mWindowAdded) {
a.mWindowAdded = true;
wm.addView(decor, l);
}
}
......
}
......
}
首先那会调用performResumeActivity来通知Activity要被激活了,然后返回来一个值为类型为ActivityClientRecord的 r 对象,其中r.activity返回的就是被激活的Activity 用a来表示。 那么就会使Activity组件调用成员函数onResume 。
接下来会判断当前正在激活的Activity组件接下来是否可见,这里有两个需要先弄明白的,一个是boolen 类型的willBeVisible ,true表示正在激活的Activity组件接下来是可见的,反之不可见,另一个是a.mStartedActivity获得的boolean类型的值,true来表示一个Activity组件正在启动一个新的Activity,并且等在这个新的Activity执行结束。返回结果之前,当前的Activity组件要保持不可见状态。总结一句话就是当Activity组件a的成员变量mStartedActivity的值等于true的时候,它接下来就是不可见的,否则的话,就是可见的。
但是有一种情况就是所启动的Activity的UI不是全屏的,但是mStartedActivity为true,那么原来的Activity的UI一部分是可见的,这样的话willBeVisible 必须为true才行,如果之前的willBeVisible为false那么接下来会通过ActivityManager.getService().willActivityBeVisible(
a.getActivityToken());来检查新建的Activity是否是全屏,按照结果来重新给willBeVisible 赋值。
接下来判断ActivityClientRecord 的r的成员变量window是否为空,如果为空则表示当前正在激活的Activity组件a关联的Window(应用程序窗口)还没有关联ViewRootImpl对象,如果正在激活的Activity a还活着,a.mFinished为false,并且可见willBeVisible为true,这时候就需要为a创建一个ViewRootImpl对象,那么就需要接下来的几步了。通过r.activity.getWindow()获得PhoneWindow,通过 r.window.getDecorView(),获得DecorView,(不懂的可以看这一篇Android应用程序窗口(Activity)的窗口对象(Window)的创建过程分析
)
7. Activity.getWindowManager
public class Activity extends ContextThemeWrapper
implements LayoutInflater.Factory,
Window.Callback, KeyEvent.Callback,
OnCreateContextMenuListener, ComponentCallbacks {
......
private WindowManager mWindowManager;
......
public WindowManager getWindowManager() {
return mWindowManager;
}
......
}
这里的mWindowManager指向的是WindowManagerImpl。
8.WindowManagerImpl.addView
private final WindowManagerGlobal mGlobal = WindowManagerGlobal.getInstance();
public void addView(@NonNull View view, @NonNull ViewGroup.LayoutParams params) {
applyDefaultToken(params);
mGlobal.addView(view, params, mContext.getDisplay(), mParentWindow);
}
这里调用的是类型为WindowManagerGlobal 的mGlobal的成员函数addView,WindowManagerGlobal相当于一个代理来管理窗口。
9.WindowManagerGlobal.addView
public void addView(View view, ViewGroup.LayoutParams params,
Display display, Window parentWindow) {
......
final WindowManager.LayoutParams wparams = (WindowManager.LayoutParams) params;
ViewRootImpl root;
......
root = new ViewRootImpl(view.getContext(), display);
view.setLayoutParams(wparams);
mViews.add(view);
mRoots.add(root);
mParams.add(wparams);
// do this last because it fires off messages to start doing things
try {
root.setView(view, wparams, panelParentView);
} catch (RuntimeException e) {
// BadTokenException or InvalidDisplayException, clean up.
if (index >= 0) {
removeViewLocked(index, true);
}
throw e;
}
}
}
在这个方法中我们可以看到创建了一个ViewRootImpl类型的root来实现与从窗口的关联。最后调用 root.setView(view, wparams, panelParentView);来实现。参数view和参数mparams描述的就是要关联的View对象和WindowManager.LayoutParams对象。