Android应用冷启动流程,以及view.post()为什么能获取到view的宽高

原文链接

1.应用启动流程

应用的冷启动流程-》首先要和zygote建立socket连接,将创建进程需要的参数发给zygote,zygote服务端收到参数之后调用ZygoteConnection.processOneCommand()处理参数,并fork出应用进程。最后通过findStaticMain()找到ActivityThread类的main()方法并执行,应用进程启动。

ActivityThread不是一个线程类,但它运行在主线程,可以把它认为是主线程也没关系。在main()中,创建ActivityThread对象,调用其attach(),并开启主线程消息循环,基于事件的消息队列机制开始工作了。

在ActivityThread.attach()方法,Binder调用了AMS.attachApplication(),主要完成了两件事:

  1. 将当前进程进程和AMS进行绑定,然后Binder调用回应用进程的Application.bindApplication()方法,进行客户端的准备工作,创建Context,创建Application等等。

  2. mStackSupervisor.attachApplicationLocked(app),最终调用到realStartActivityLocked()启动Activity

庖丁解牛 Activity 启动流程

> ActivityThread.java

private Activity performLaunchActivity(ActivityClientRecord r, Intent customIntent) {
    ...
    // 获取 ComponentName
    ComponentName component = r.intent.getComponent();
    ...
    // 创建 ContextImpl 对象
    ContextImpl appContext = createBaseContextForActivity(r);
    ...
    // 反射创建 Activity 对象
    activity = mInstrumentation.newActivity(cl, component.getClassName(), r.intent);
    ......
    // 创建 Application 对象
    Application app = r.packageInfo.makeApplication(false, mInstrumentation);
    ......      
	// attach 方法中会创建 PhoneWindow 对象
    activity.attach(appContext, this, getInstrumentation(), r.token,
            r.ident, app, r.intent, r.activityInfo, title, r.parent,
            r.embeddedID, r.lastNonConfigurationInstances, config,
            r.referrer, r.voiceInteractor, window, r.configCallback);

             
    // 执行 onCreate()
    if (r.isPersistable()) {
        mInstrumentation.callActivityOnCreate(activity, r.state, r.persistentState);
    } else {
        mInstrumentation.callActivityOnCreate(activity, r.state);
    }
    ...
    return activity;
}

mInstrumentation.callActivityOnCreate方法最终会回调Activity.onCreat()方法。到这里setContentView()方法就被执行了。setContentView最终的结果就是创建DecorView,然后根据布局id解析xml,将得到的view塞进DecorView。到这一步我们只是得到了一个空壳子View树,它并没有被添加到屏幕上。
然后跳过onStart()去看onResume()

Activity 的生命周期是由 ClientLifecycleManager 类来调度的,具体原理可以看这篇文章 从源码看 Activity 生命周期

> ActivityThread.java

public void handleResumeActivity(IBinder token, boolean finalStateRequest, boolean isForward,
       String reason) {
   ...
   // 1. 回调 onResume
   final ActivityClientRecord r = performResumeActivity(token, finalStateRequest, reason);
   ···
   View decor = r.window.getDecorView();
   decor.setVisibility(View.INVISIBLE);
   ViewManager wm = a.getWindowManager();
   WindowManager.LayoutParams l = r.window.getAttributes();
   // 2. 添加 decorView 到 WindowManager
   wm.addView(decor, l);
   ...
}

wm.addView(decor, l) 最终调用到 WindowManagerGlobal.addView() 。


public void addView(View view, ViewGroup.LayoutParams params,
       Display display, Window parentWindow) {
   ...
   // 1. 重点,初始化 ViewRootImpl
   root = new ViewRootImpl(view.getContext(), display);
   // 2. 重点,发起绘制并显示到屏幕上
   root.setView(view, wparams, panelParentView);

public ViewRootImpl(Context context, Display display) {
   ...
   // 1. IWindowSession 代理对象,与 WMS 进行 Binder 通信
   mWindowSession = WindowManagerGlobal.getWindowSession();
   ...
   // 2.
   mWidth = -1;
   mHeight = -1;
   ...
   // 3. 初始化 AttachInfo
   // 记住 mAttachInfo 是在这里被初始化的
   mAttachInfo = new View.AttachInfo(mWindowSession, mWindow, display, this, mHandler, this,
           context);
   ...
   // 4. 初始化 Choreographer,通过 Threadlocal 存储
   mChoreographer = Choreographer.getInstance();
}

  1. 初始化 mWindowSession,它可以 WMS 进行 Binder 通信
  2. 这里能看到宽高还未赋值
  3. 初始化 AttachInfo,这里着重记一下,后面会再提到
  4. 初始化 Choreographer,上篇文章 面试官:如何监测应用的 FPS ? 详细介绍过

> ViewRootImpl.java

// 参数 view 就是 DecorView
public void setView(View view, WindowManager.LayoutParams attrs, View panelParentView) {
   synchronized (this) {
       if (mView == null) {
           mView = view;

           // 1. 发起首次绘制
           requestLayout();

           // 2. Binder 调用 Session.addToDisplay(),将 window 添加到屏幕
                   res = mWindowSession.addToDisplay(mWindow, mSeq, mWindowAttributes,
                           getHostVisibility(), mDisplay.getDisplayId(), mWinFrame,
                           mAttachInfo.mContentInsets, mAttachInfo.mStableInsets,
                           mAttachInfo.mOutsets, mAttachInfo.mDisplayCutout, mInputChannel);

           // 3. 将 decorView 的 parent 赋值为 ViewRootImpl
           view.assignParent(this);
       }
   }
}
> ViewRootImpl.java

public void requestLayout() {
    if (!mHandlingLayoutInLayoutRequest) {
		// 检查线程
        checkThread();
        mLayoutRequested = true;
        // 重点
        scheduleTraversals();
    }
}

ViewRootImpl.scheduleTraversals() 方法在 上篇文章 中详细介绍过,这里大致总结一下:

ViewRootImpl.scheduleTraversals() 方法中会建立同步屏障,优先处理异步消息。通过 Choreographer.postCallback() 方法提交了任务 mTraversalRunnable,这个任务就是负责 View 的测量,布局,绘制。
Choreographer.postCallback() 方法通过 DisplayEventReceiver.nativeScheduleVsync() 方法向系统底层注册了下一次 vsync 信号的监听。当下一次 vsync 来临时,系统会回调其 dispatchVsync() 方法,最终回调 FrameDisplayEventReceiver.onVsync() 方法。
FrameDisplayEventReceiver.onVsync() 方法中取出之前提交的 mTraversalRunnable 并执行。这样就完成了一次绘制流程。

mTraversalRunnable 中执行的是 doTraversal() 方法。

> ViewRootImpl.java

void doTraversal() {
if (mTraversalScheduled) {
    // 1. mTraversalScheduled 置为 false
    mTraversalScheduled = false;
    // 2. 移除同步屏障
    mHandler.getLooper().getQueue().removeSyncBarrier(mTraversalBarrier);

    // 3. 开始布局,测量,绘制流程
    performTraversals();
    ......
}
> ViewRootImpl.java

private void performTraversals() {
    ...
    // 1. 绑定 Window,重点记忆一下
    host.dispatchAttachedToWindow(mAttachInfo, 0);
    mAttachInfo.mTreeObserver.dispatchOnWindowAttachedChange(true);

    getRunQueue().executeActions(mAttachInfo.mHandler);

    // 2. 请求 WMS 计算窗口大小
    relayoutResult = relayoutWindow(params, viewVisibility, insetsPending);

    // 3. 测量
    performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);

    // 4. 布局
    performLayout(lp, mWidth, mHeight);

    // 5. 绘制
    performDraw();
}

到此整个布局的测量和绘制都结束,接下啦所以我们就可以明白为什么在activity中的onCreate以及onResume中为什么无法直接获取到view的宽高。

2.view.post()

> View.java

public boolean post(Runnable action) {
  final AttachInfo attachInfo = mAttachInfo;
  if (attachInfo != null) {
      // 1. attachInfo 不为空,通过 mHandler 发送
      return attachInfo.mHandler.post(action);
  }
  // 2. attachInfo 为空,放入队列中
  getRunQueue().post(action);
  return true;
}

  1. attachInfo 是在 ViewRootImpl 的构造函数中初始化的,
  2. ViewRootImpl 是在 WindowManagerGlobal.addView() 创建的
  3. WindowManagerGlobal.addView() 是在 ActivityThread 的 handleResumeActivity() 中调用的,但是是在 Activity.onResume() 回调之后

根据 ViewRootImpl 是否已经创建,View.post() 会执行不同的逻辑。如果 ViewRootImpl 已经创建,即 mAttachInfo 已经初始化,直接通过 Handler 发送消息来执行任务。如果 ViewRootImpl 未创建,即 View 尚未开始绘制,会将任务保存为 HandlerAction,暂存在队列 HandlerActionQueue 中,等到 View 开始绘制,执行 performTraversal() 方法时,在 dispatchAttachedToWindow() 方法中通过 Handler 分发 HandlerActionQueue 中暂存的任务。
另外要注意,View 绘制是发生在一次 Meesage 处理过程中的,View.post() 执行的任务也是发生在一次 Message 处理过程中的,它们一定是有先后顺序的。

参考链接:

庖丁解牛 Activity 启动流程