一、我们要知道的几个点
-
- 桌面点击某个应用后的启动流程?
-
- setContentView流程,系统的布局,应用内的布局view如何准备和实例化?
-
- 2中的View数据实例化好之后交给谁?
二、 首先我们知道了App启动流程
Launcher.app --> 点击应用图标 --> 与AMS进行binder通信,告知请求 --> AMS与Zygote通过Socket通信 --> Zygote进程fork出一个App进程
ActivityThread.main方法中thread.attach 来通过和AMS通信实例化和启动Application实例。
启动Application实例后,紧接着开始实例和启动main Activity。最终会调到ActivityStackSupervisor.realStartActivityLocked方法。它通过添加ClientTransaction事务的方式回调ActivityThread。ClientTransaction中会添加Item:它们会分别处理Activity不同生命周期回调(LaunchActivityItem、StartActivityItem、ResumeActivityItem、PauseActivityItem、StopActivityItem)。它们分别对应ActivityThread中的handleLaunchActivity、handleStartActivity、handleResumeActivity、handlePauseActivity、handleStopActivity。跟踪它们就能看到Activity中不同生命周期方法是如何调起的。
TransactionExecutor中的execute方法中 executeCallbacks(transaction);和executeLifecycleState(transaction);就是上一个生命周期调用下一个生命周期的逻辑。
三、setContentView流程,系统的布局,应用内的布局view如何准备和实例化?
看setContentView有两种,一种MainActivity继承至Activity;一种是MainActivity继承至AppCompatActivity;
3.1 AppCompatActivity这种的话,最终是到AppCompatDelegateImpl的setContentView方法
@Override
public void setContentView(View v) {
ensureSubDecor();
ViewGroup contentParent = mSubDecor.findViewById(android.R.id.content);
contentParent.removeAllViews();
contentParent.addView(v);
mAppCompatWindowCallback.getWrapped().onContentChanged();
}
在setContentView里,有ensureSubDecor方法,因为AppCompatActivity是androidx包下的,在ensureSubDecor中就开始了狸猫换太子之旅,它将自己的xml布局换成了系统的android.R.id.content。
...
final ContentFrameLayout contentView = (ContentFrameLayout) subDecor.findViewById(
R.id.action_bar_activity_content);
final ViewGroup windowContentView = (ViewGroup) mWindow.findViewById(android.R.id.content);
if (windowContentView != null) {
// There might be Views already added to the Window's content view so we need to
// migrate them to our content view
while (windowContentView.getChildCount() > 0) {
final View child = windowContentView.getChildAt(0);
windowContentView.removeViewAt(0);
contentView.addView(child);
}
// Change our content FrameLayout to use the android.R.id.content id.
// Useful for fragments.
windowContentView.setId(View.NO_ID);
contentView.setId(android.R.id.content);
// The decorContent may have a foreground drawable set (windowContentOverlay).
// Remove this as we handle it ourselves
if (windowContentView instanceof FrameLayout) {
((FrameLayout) windowContentView).setForeground(null);
}
}
// Now set the Window's content view with the decor
mWindow.setContentView(subDecor);
...
3.2 上面的AppCompatActivity就可以结束了,接着着重看Activity中的setContentView()
-
3.2.1 Activity.setContentView实际调用的是PhoneWindow的setContentView
public void setContentView(@LayoutRes int layoutResID) {
getWindow().setContentView(layoutResID);
initWindowDecorActionBar();
}
-
3.2.2 在PhoneWindow中的setContentView就是根据App所选择的系统样式获得系统的根布局,然后在把传过来的App自己的View解析反射实例化,add到mContentParent中去,下面分别说明
public void setContentView(int layoutResID) {
if (mContentParent == null) {
installDecor();
} else if (!hasFeature(FEATURE_CONTENT_TRANSITIONS)) {
mContentParent.removeAllViews();
}
...
final Callback cb = getCallback();
if (cb != null && !isDestroyed()) {
cb.onContentChanged();
}
mContentParentExplicitlySet = true;
}
-
3.2.3 installDecor()方法先获取系统配置的布局样式,得到App能显示的mContentParent区域。
- 3.2.3.1 首先,mDecor为空的话,则创建一个DecorView。mDecorView实际上就是一个FrameLayout
private void installDecor() {
mForceDecorInstall = false;
if (mDecor == null) {
mDecor = generateDecor(-1);
...
}
if (mContentParent == null) {
mContentParent = generateLayout(mDecor);
}
...
//后面没啥了,基本就是mContentParent一些属性相关的设置
}
- 3.2.3.2 然后,就开始通过generateLayout方法来给mContentParent赋值。它根据不同的Feature来选择不同的xml布局,如你选择的Activity是有title的、无title等。这里我们看到R.layout.screent_simple。(一般在sdk目录/platforms/选择不同的SDK版本/data/res/layout里以screen为头的xml)。在screen_simple.xm中我们可以看到熟悉的“@android:id/content”,这里就是我们自己Activity布局所要显示的父控件。
protected ViewGroup generateLayout(DecorView decor) {
...
//直接跳到根据Feature来选择不同xml布局来
int layoutResource;
int features = getLocalFeatures();
if ((features & ((1 << FEATURE_LEFT_ICON) | (1 << FEATURE_RIGHT_ICON))) != 0) {
layoutResource = R.layout.screen_title_icons;
...
} else {
layoutResource = R.layout.screen_simple;
}
//将选择好的layout的id交由DecorView去按层级解析,反射实例化并一层层addView绑定
mDecor.onResourcesLoaded(mLayoutInflater, layoutResource);
...
//找到id为android.R.id.content的控件
ViewGroup contentParent = (ViewGroup)findViewById(ID_ANDROID_CONTENT);
}
...
//将contentParent返回
return contentParent;
screen_simple.xml布局
-
3.2.4 PhoneWindow的setContentView方法,初始化DecorView和mContentParent之后,就会将传入的App的Activity的layoutResID,使用LayoutInflater.inflate()来进行解析,解析XML、反射实例化View、并与mContentLayout按层级进行addView.
public void setContentView(int layoutResID) {
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 {
mLayoutInflater.inflate(layoutResID, mContentParent);
}
...
}
四、setContentView基本先找到系统的XML布局,实例化。然后在将自己的xml实例化绑定到mContentLayout中,但是到这一步貌似onCreate已经J结束了。那下一步去哪呢?我们下一步根据Activit生命周期方法,应该到了onResume了。
4.1 在App启动流程中的TransactionExcuter.execute中我们提过,Activity上一个生命周期方法执行完,就调用下一个生命周期方法。如下executeLifecycleState()方法。onCreate执行完后,其实是执行ResumeActivityItem中的execute方法。它会回调ActivityThread中的handleResumeActivity方法。
public void execute(ClientTransaction transaction) {
...
executeCallbacks(transaction);
executeLifecycleState(transaction);
}
4.2 在handleResumeActivity方法中有一个地方值得注意wm.addView(decor, l),它将我们初始化好的DecorView加入到了wm中,这个wm就是performLaunchActivity方法activity.attach()里实例化的mWindowManager,它实际上是WindowManagerImpl对象。
public void handleResumeActivity(IBinder token, boolean finalStateRequest, boolean isForward,
String reason) {
...
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;
...
if (a.mVisibleFromClient) {
if (!a.mWindowAdded) {
a.mWindowAdded = true;
wm.addView(decor, l);
} else {
...
}
}
...
}
4.3 在WindowManagerImpl.addView方法中,实际上调用的是mGlobal.addView方法,而mGlobal实际上是WindowManagerGlobal对象。
public void addView(@NonNull View view, @NonNull ViewGroup.LayoutParams params) {
applyDefaultToken(params);
mGlobal.addView(view, params, mContext.getDisplayNoVerify(), mParentWindow,
mContext.getUserId());
}
4.4 进入到WindowManagerGlobal的addView方法,它会实例化一个ViewRootImpl对象,然后调用它的setView()方法。(ViewRootImpl用来管理所有的view的绘制策略,你怎么绘制由这个类管理)
public void addView(View view, ViewGroup.LayoutParams params,
Display display, Window parentWindow, int userId) {
ViewRootImpl root;
synchronized (mLock) {
mViews.add(view);
mRoots.add(root);
mParams.add(wparams);
try {
root.setView(view, wparams, panelParentView, userId);
} catch (RuntimeException e) {
}
}
}