App的启动流程

App的启动,关于系统层面的Zygote -> ActivityThread (中间的步骤先滤过)我们后面再讲,这里着重讲系统服务调用ActivityThread之后的流程.

首先看一下流程图
image.png

如上图中,当我们点击一个App启动的时候Zygote底层会经过一系列的调用,最终会调到ActivityThread的main()方法.
在main方法中主要做了一些初始化的工作,开启Looper循环,new ActivityThread(),调用attach方法

image.png

再来看看attach方法中做了什么? 调用系统的ActivityManagerService通过Binder获得IActivityManager来绑定ApplicationThread,通过ApplicationThread来响应Activity的各种状态调用生命周期方法.

image.png

来看ApplicationThread中是如何启动Activity?通过系统服务来调用scheduleLaunchActivity()方法,我们可以看到ActivityClientRecord 相当于Activity的一个包装类,内部存储着Activity,我们会在后面看到它的作用,之后会调用sendMessage(),点击去看sendMessage方法,可以看到很熟悉的代码,没错就是Message,很显然通过Handler发送一个消息.

image.png

那么我们在来看一下Handler是如何处理LAUNCH_ACTIVITY这个消息的呢? 从字面意思上看就是启动Activity,我们来看如何启动的.在Handler内部会找到一个方法handleMessage来处理消息,如果遇到LAUNCH_ACTIVITY就会执行handleLaunchActivity()方法,我们来看一下这个方法中做了些什么?
image.png

可以看到,在这个方法中,调用performLaunchActivity()方法,这个方法返回了一个Activity,那么在这个方法中,肯定new 了一个Activity.来看一下这个方法.
image.png

image.png

image.png

看上面三个图,我从performLaunchActivity()找到了三个关键的部分,首先从第一张图,它竟然使用了反射来创建了一个Activity,看来底层代码,也用到了很多反射.第二张图,创建完Activity后,调用了Activity的attach()方法.第三张图,回调了onCreate(). 原来如此,我们经常使用的onCreate()方法就是在这里回调的.
关于mInstrumentation是一个辅助的类,如下图,它是这样调用onCreate方法的
image.png

image.png

通过上述流程,启动了一个Activity,并且调用了Activity的onCreate()方法,但是Activity的界面还没有绘制出来,我们都知道在Activity的onCreate()方法中会调用setContentView(),我们看一下这个方法内部的做了什么?

setConentView执行流程

我们可以看到在Activity中的setContentView()方法,显然调用了Window的setContentView,那么这个window是从那里来的呢?

  1. public void setContentView(@LayoutRes int layoutResID) {
  2. //PhoneWindow加载布局资源
  3. getWindow().setContentView(layoutResID);
  4. //初始化ActionBar
  5. initWindowDecorActionBar();
  6. }

如下代码,原来在attach中初始化了Window,这个window就是PhoneWindow,我们都知道attach方法是在上述的启动Activity中调用的它是在onCreate之前就被调用了.

  1. final void attach(Context context, ActivityThread aThread,
  2. Instrumentation instr, IBinder token, int ident,
  3. Application application, Intent intent, ActivityInfo info,
  4. CharSequence title, Activity parent, String id,
  5. NonConfigurationInstances lastNonConfigurationInstances,
  6. Configuration config, String referrer, IVoiceInteractor voiceInteractor,
  7. Window window, ActivityConfigCallback activityConfigCallback) {
  8. attachBaseContext(context);
  9. mFragments.attachHost(null /*parent*/);
  10. //TODO 此处Window被赋值 是PhoneWindow
  11. mWindow = new PhoneWindow(this, window, activityConfigCallback);
  12. }

那么接下来我们来看一下PhoneWindow中的setContentView方法. 看下面的代码,我们需要注意几个点,从下面代码可以看到我们将我们的资源xml添加到了mContentParent中,这个mContentParent是在installDecor中初始化的,那么接下来的步骤就是研究installDecor方法.

  1. @Override
  2. public void setContentView(int layoutResID) {
  3. // 注意:在安装窗口的过程中可能会设置FEATURE_CONTENT_TRANSITIONS
  4. // Note: FEATURE_CONTENT_TRANSITIONS may be set in the process of installing the window
  5. // decor,当主题属性等明确化时。不检查功能
  6. // decor, when theme attributes and the like are crystalized. Do not check the feature
  7. // 在此发生之前
  8. // before this happens.
  9. if (mContentParent == null) {//mContentParent = android.id.content
  10. //装载DecorView 最上层的View
  11. installDecor();
  12. } else if (!hasFeature(FEATURE_CONTENT_TRANSITIONS)) {
  13. mContentParent.removeAllViews();
  14. }
  15. ......
  16. }

看下面的代码,mDecor其实我们猜测就可以知道.它就是DecorView.都知道DecorView是最上层的View,我们来看一下generateDecor()方法是否初始化了DecorView.

  1. private void installDecor() {
  2. mForceDecorInstall = false;
  3. if (mDecor == null) {
  4. //DecorView 初始化 new DecorView
  5. mDecor = generateDecor(-1);
  6. mDecor.setDescendantFocusability(ViewGroup.FOCUS_AFTER_DESCENDANTS);
  7. mDecor.setIsRootNamespace(true);
  8. if (!mInvalidatePanelMenuPosted && mInvalidatePanelMenuFeatures != 0) {
  9. mDecor.postOnAnimation(mInvalidatePanelMenuRunnable);
  10. }
  11. } else {
  12. mDecor.setWindow(this);
  13. }
  14. if (mContentParent == null) {
  15. //内容初始化 所有的xml布局都在其中
  16. mContentParent = generateLayout(mDecor);
  17. ...
  18. }
  19. ....
  20. }

来看generateDecor方法,和我们猜测的没错它就是new 了一个 DecorView.那么到这里其实就很明显了,mContentParent其实就是我们经常使用的android.R.id.content,不信我们看一下generateLayout

  1. protected DecorView generateDecor(int featureId) {
  2. // System process doesn't have application context and in that case we need to directly use
  3. // the context we have. Otherwise we want the application context, so we don't cling to the
  4. // activity.
  5. Context context;
  6. if (mUseDecorContext) {
  7. Context applicationContext = getContext().getApplicationContext();
  8. if (applicationContext == null) {
  9. context = getContext();
  10. } else {
  11. context = new DecorContext(applicationContext, getContext().getResources());
  12. if (mTheme != -1) {
  13. context.setTheme(mTheme);
  14. }
  15. }
  16. } else {
  17. context = getContext();
  18. }
  19. return new DecorView(context, featureId, this, getAttributes());
  20. }

generateLayout()方法如下,代码比较多,我们从头分析,首先映入眼帘的是requestFeature(),这个方法大家肯定非常熟悉,getWindow().requestFeature(),因为我们经常会调用这个方法,这也是为什么我们必须要设置在setContentView前面的原因.其实从源码中可以发现很多乐趣.

  1. //requestFeature 在window上设置requestFeature必须找setContentView 之前的原因
  2. if (a.getBoolean(R.styleable.Window_windowNoTitle, false)) {
  3. requestFeature(FEATURE_NO_TITLE);
  4. } else if (a.getBoolean(R.styleable.Window_windowActionBar, false)) {
  5. // Don't allow an action bar if there is no title.
  6. requestFeature(FEATURE_ACTION_BAR);
  7. }
  8. if (a.getBoolean(R.styleable.Window_windowActionBarOverlay, false)) {
  9. requestFeature(FEATURE_ACTION_BAR_OVERLAY);
  10. }
  11. if (a.getBoolean(R.styleable.Window_windowActionModeOverlay, false)) {
  12. requestFeature(FEATURE_ACTION_MODE_OVERLAY);
  13. }
  14. if (a.getBoolean(R.styleable.Window_windowSwipeToDismiss, false)) {
  15. requestFeature(FEATURE_SWIPE_TO_DISMISS);
  16. }
  17. if (a.getBoolean(R.styleable.Window_windowFullscreen, false)) {
  18. setFlags(FLAG_FULLSCREEN, FLAG_FULLSCREEN & (~getForcedWindowFlags()));
  19. }

然后我们继续看generateLayout()方法,下面的代码.这里我们会发现它竟然加载了布局文件,并且添加到了DecorView中,我们随便点开一个布局看看是否是系统给我们提供的布局.

  1. //TODO 会加载一些最基础的布局 可能有状态栏可能没有状态栏的基础布局 有多种基础布局
  2. int layoutResource;
  3. int features = getLocalFeatures();
  4. // System.out.println("Features: 0x" + Integer.toHexString(features));
  5. if ((features & (1 << FEATURE_SWIPE_TO_DISMISS)) != 0) {
  6. layoutResource = R.layout.screen_swipe_dismiss;
  7. setCloseOnSwipeEnabled(true);
  8. } else if ((features & ((1 << FEATURE_LEFT_ICON) | (1 << FEATURE_RIGHT_ICON))) != 0) {
  9. if (mIsFloating) {
  10. TypedValue res = new TypedValue();
  11. getContext().getTheme().resolveAttribute(
  12. R.attr.dialogTitleIconsDecorLayout, res, true);
  13. layoutResource = res.resourceId;
  14. } else {
  15. layoutResource = R.layout.screen_title_icons;
  16. }
  17. // XXX Remove this once action bar supports these features.
  18. removeFeature(FEATURE_ACTION_BAR);
  19. // System.out.println("Title Icons!");
  20. } else if ((features & ((1 << FEATURE_PROGRESS) | (1 << FEATURE_INDETERMINATE_PROGRESS))) != 0
  21. && (features & (1 << FEATURE_ACTION_BAR)) == 0) {
  22. // Special case for a window with only a progress bar (and title).
  23. // XXX Need to have a no-title version of embedded windows.
  24. layoutResource = R.layout.screen_progress;
  25. // System.out.println("Progress!");
  26. } else if ((features & (1 << FEATURE_CUSTOM_TITLE)) != 0) {
  27. // Special case for a window with a custom title.
  28. // If the window is floating, we need a dialog layout
  29. if (mIsFloating) {
  30. TypedValue res = new TypedValue();
  31. getContext().getTheme().resolveAttribute(
  32. R.attr.dialogCustomTitleDecorLayout, res, true);
  33. layoutResource = res.resourceId;
  34. } else {
  35. layoutResource = R.layout.screen_custom_title;
  36. }
  37. // XXX Remove this once action bar supports these features.
  38. removeFeature(FEATURE_ACTION_BAR);
  39. } else if ((features & (1 << FEATURE_NO_TITLE)) == 0) {
  40. // If no other features and not embedded, only need a title.
  41. // If the window is floating, we need a dialog layout
  42. if (mIsFloating) {
  43. TypedValue res = new TypedValue();
  44. getContext().getTheme().resolveAttribute(
  45. R.attr.dialogTitleDecorLayout, res, true);
  46. layoutResource = res.resourceId;
  47. } else if ((features & (1 << FEATURE_ACTION_BAR)) != 0) {
  48. layoutResource = a.getResourceId(
  49. R.styleable.Window_windowActionBarFullscreenDecorLayout,
  50. R.layout.screen_action_bar);
  51. } else {
  52. layoutResource = R.layout.screen_title;
  53. }
  54. // System.out.println("Title!");
  55. } else if ((features & (1 << FEATURE_ACTION_MODE_OVERLAY)) != 0) {
  56. layoutResource = R.layout.screen_simple_overlay_action_mode;
  57. } else {
  58. // Embedded, so no decoration is needed.
  59. // 绘制一个最基础的布局
  60. layoutResource = R.layout.screen_simple;
  61. // System.out.println("Simple!");
  62. }
  63. mDecor.startChanging();
  64. mDecor.onResourcesLoaded(mLayoutInflater, layoutResource);
  65. //在这个方法中进行了添加view的操作
  66. void onResourcesLoaded(LayoutInflater inflater, int layoutResource) {
  67. mStackId = getStackId();
  68. if (mBackdropFrameRenderer != null) {
  69. loadBackgroundDrawablesIfNeeded();
  70. mBackdropFrameRenderer.onResourcesLoaded(
  71. this, mResizingBackgroundDrawable, mCaptionBackgroundDrawable,
  72. mUserCaptionBackgroundDrawable, getCurrentColor(mStatusColorViewState),
  73. getCurrentColor(mNavigationColorViewState));
  74. }
  75. mDecorCaptionView = createDecorCaptionView(inflater);
  76. final View root = inflater.inflate(layoutResource, null);
  77. if (mDecorCaptionView != null) {
  78. if (mDecorCaptionView.getParent() == null) {
  79. addView(mDecorCaptionView,
  80. new ViewGroup.LayoutParams(MATCH_PARENT, MATCH_PARENT));
  81. }
  82. mDecorCaptionView.addView(root,
  83. new ViewGroup.MarginLayoutParams(MATCH_PARENT, MATCH_PARENT));
  84. } else {
  85. // Put it below the color views.
  86. addView(root, 0, new ViewGroup.LayoutParams(MATCH_PARENT, MATCH_PARENT));
  87. }
  88. mContentRoot = (ViewGroup) root;
  89. initializeElevation();
  90. }

我们来看一下R.layout.screen_simple这个布局文件,代码如下.是不是很熟悉的代码,action_mode_bar_stub应该是ActionBar,同时我们还看到了@android:id/content,原来如此.在我们设置自己的布局文件的是否,系统会根据样式加载一个基础布局,然后我们自己的布局文件添加到基础布局的content中.

  1. <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
  2. android:layout_width="match_parent"
  3. android:layout_height="match_parent"
  4. android:fitsSystemWindows="true"
  5. android:orientation="vertical">
  6. <ViewStub android:id="@+id/action_mode_bar_stub"
  7. android:inflatedId="@+id/action_mode_bar"
  8. android:layout="@layout/action_mode_bar"
  9. android:layout_width="match_parent"
  10. android:layout_height="wrap_content"
  11. android:theme="?attr/actionBarTheme" />
  12. <FrameLayout
  13. android:id="@android:id/content"
  14. android:layout_width="match_parent"
  15. android:layout_height="match_parent"
  16. android:foregroundInsidePadding="false"
  17. android:foregroundGravity="fill_horizontal|top"
  18. android:foreground="?android:attr/windowContentOverlay" />
  19. </LinearLayout>

image.png

我们继续看,generateLayout()这个方法,很明显将android.R.id.content的ViewGroup返回了.

  1. //加载内容 这里才会将你创建的xml放到这个view中
  2. ViewGroup contentParent = (ViewGroup)findViewById(ID_ANDROID_CONTENT);
  3. ....
  4. return contentParent;

installDecor()方法执行完毕之后,这时候我们得到了mDecormContentParent,我们再回到setContentView(),可以看到我们熟悉的方法inflate,将我们自己的资源xml添加到了mContentParent

  1. if (hasFeature(FEATURE_CONTENT_TRANSITIONS)) {
  2. final Scene newScene = Scene.getSceneForLayout(mContentParent, layoutResID,
  3. getContext());
  4. transitionTo(newScene);
  5. } else {
  6. //加载配置文件,放到mContentParent 中
  7. mLayoutInflater.inflate(layoutResID, mContentParent);
  8. }
  9. mContentParent.requestApplyInsets();
  10. final Callback cb = getCallback();
  11. if (cb != null && !isDestroyed()) {
  12. cb.onContentChanged();
  13. }
  14. mContentParentExplicitlySet = true;

经过以上的流程,onCreate()方法算是执行完毕了,但是页面并没有进行绘制,那么页面是在那里进行绘制的呢?有过经验的同学肯定知道页面的绘制是在onResume()方法执行之后进行绘制的.

UI是如何绘制的?

既然在上述的流程中我们猜测页面的绘制是在onResume()方法回调之后进行绘制的,那我们在回到ActivityThread的ApplicationThread,我们知道系统服务通过调用ApplicationThread,来调用了Activity的各种状态.我们可以从handleMessage中可以找到类似:RESUME_ACTIVITY 没错这个就是onResume()的回调

  1. final void handleResumeActivity(IBinder token,
  2. boolean clearHide, boolean isForward, boolean reallyResume, int seq, String reason) {
  3. ActivityClientRecord r = mActivities.get(token);
  4. ....
  5. r = performResumeActivity(token, clearHide, reason);//调用Activity的onResume()方法
  6. ....
  7. if (r.window == null && !a.mFinished && willBeVisible) {
  8. r.window = r.activity.getWindow();
  9. //获取布局
  10. View decor = r.window.getDecorView();
  11. decor.setVisibility(View.INVISIBLE);
  12. ViewManager wm = a.getWindowManager();
  13. //所有自己的资源
  14. WindowManager.LayoutParams l = r.window.getAttributes();
  15. a.mDecor = decor;
  16. l.type = WindowManager.LayoutParams.TYPE_BASE_APPLICATION;
  17. l.softInputMode |= forwardBit;
  18. if (r.mPreserveWindow) {
  19. a.mWindowAdded = true;
  20. r.mPreserveWindow = false;
  21. // Normally the ViewRoot sets up callbacks with the Activity
  22. // in addView->ViewRootImpl#setView. If we are instead reusing
  23. // the decor view we have to notify the view root that the
  24. // callbacks may have changed.
  25. ViewRootImpl impl = decor.getViewRootImpl();
  26. if (impl != null) {
  27. impl.notifyChildRebuilt();
  28. }
  29. }
  30. if (a.mVisibleFromClient) {
  31. if (!a.mWindowAdded) {
  32. a.mWindowAdded = true;
  33. //TODO 添加View
  34. wm.addView(decor, l);
  35. } else {
  36. // The activity will get a callback for this {@link LayoutParams} change
  37. // earlier. However, at that time the decor will not be set (this is set
  38. // in this method), so no action will be taken. This call ensures the
  39. // callback occurs with the decor set.
  40. a.onWindowAttributesChanged(l);
  41. }
  42. }
  43. // If the window has already been added, but during resume
  44. // we started another activity, then don't yet make the
  45. // window visible.
  46. }
  47. .......
  48. }

从上述代码中,我们可以看到先执行了onResume()方法然后在进行了UI的绘制,从源码中,我们可以看出很多问题,第一点就是我们不能在onResume()方法中执行耗时的操作,否则会影响UI的绘制,影响用户体验.
我们再回到代码中,可以看到通过activity获取到window的DecorView,然后将DecorView交给ViewManager进行绘制,最终我们可以找到WindowManagerGlobal来实现ViewManager接口的方法.然后我们去看其中的addView的方法,addView()方法将DecorView和LayoutParams传递了过去.最终调用了ViewRootImpl的setView()方法,将DecorView和LayoutParams传递了过去.

  1. //
  2. root = new ViewRootImpl(view.getContext(), display);
  3. //DecorView 调用了setLayoutParams
  4. view.setLayoutParams(wparams);
  5. //优化性能坑、可能
  6. mViews.add(view);//DecorView
  7. mRoots.add(root);//ViewRootImpl
  8. mParams.add(wparams);//layout参数
  9. // do this last because it fires off messages to start doing things
  10. try {
  11. //ViewRootImpl 中调用了 requestLayout
  12. root.setView(view, wparams, panelParentView);
  13. } catch (RuntimeException e) {
  14. // BadTokenException or InvalidDisplayException, clean up.
  15. if (index >= 0) {
  16. removeViewLocked(index, true);
  17. }
  18. throw e;
  19. }

我们继续看ViewRootImpl中的setView()的方法.我们可以看到主要执行了requestLayout()方法,requestLayout()做了什么呢?最终执行了doTraversal()方法

  1. @Override
  2. public void requestLayout() {
  3. if (!mHandlingLayoutInLayoutRequest) {
  4. checkThread();
  5. mLayoutRequested = true;
  6. scheduleTraversals();
  7. }
  8. }
  9. void scheduleTraversals() {
  10. if (!mTraversalScheduled) {
  11. mTraversalScheduled = true;
  12. mTraversalBarrier = mHandler.getLooper().getQueue().postSyncBarrier();
  13. mChoreographer.postCallback(
  14. Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);
  15. if (!mUnbufferedInputDispatch) {
  16. scheduleConsumeBatchedInput();
  17. }
  18. notifyRendererOfFramePending();
  19. pokeDrawLockIfNeeded();
  20. }
  21. }
  22. final class TraversalRunnable implements Runnable {
  23. @Override
  24. public void run() {
  25. doTraversal();
  26. }
  27. }

继续看doTraversal()方法,做了什么主要执行了performTraversal()方法,在这个方法中执行view的绘制.其实主要执行了三个方法performMeasure() -> performLayout() -> performDraw().这里其实都是调用了view的measure() -> layout() -> draw(),这三个方法大家肯定非常熟悉的,因为这时自定义view的必须要重写的方法啊有不有!!! 这样整个流程下来,我们写的布局资源文件就在Activity中显示出来了

  1. private void performTraversals() {
  2. ....
  3. //TODO 1 加载窗体的一些资源
  4. WindowManager.LayoutParams lp = mWindowAttributes;
  5. ....
  6. //TODO 2先画了个矩形 确定位置
  7. Rect frame = mWinFrame;
  8. ....
  9. //TODO 3测量View
  10. performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);
  11. ....
  12. //TODO 4 调用完测量后 开始进行布局摆放
  13. performLayout(lp, mWidth, mHeight);
  14. .....
  15. if (!cancelDraw && !newSurface) {
  16. if (mPendingTransitions != null && mPendingTransitions.size() > 0) {
  17. for (int i = 0; i < mPendingTransitions.size(); ++i) {
  18. mPendingTransitions.get(i).startChangingAnimations();
  19. }
  20. mPendingTransitions.clear();
  21. }
  22. //TODO 开始绘制
  23. performDraw();
  24. } else {
  25. if (isViewVisible) {
  26. // Try again
  27. scheduleTraversals();
  28. } else if (mPendingTransitions != null && mPendingTransitions.size() > 0) {
  29. for (int i = 0; i < mPendingTransitions.size(); ++i) {
  30. mPendingTransitions.get(i).endChangingAnimations();
  31. }
  32. mPendingTransitions.clear();
  33. }
  34. }
  35. }

整个UI绘制的流程如下图所示:

image.png

总结

从上述的UI绘制流程中,我们可以学习到很多知识,包括Activity的启动和绘制以及在setContentView()之前我们可以做的一些配置,还有不能在onResume()方法中进行耗时操作,否则会影响界面的绘制.
但是我们只知道了UI的绘制流程,那么UI如何具体绘制的呢? 我会在下一章中进行讲解,UI的具体绘制过程.

作者: @JakePrim(jakeprim)