四大组件启动流程

272 阅读8分钟

Context通过外观模式为我们封装了组件启动的繁琐流程

Activity

启动入口:startActivity(requestCode = -1)、startActivityForResult

@Override
public void startActivity(Intent intent, @Nullable Bundle options) {
    if (options != null) {
        startActivityForResult(intent, -1, options);
    } else {
        startActivityForResult(intent, -1);
    }
}

最终通过Binder调用跳转到ActivityTaskManagerService.startActivity,传递的关键参数有:IApplicationThread(进程相关PID、UID)、Token、RequestCode、Intent

@Override
public final int startActivity(
        IApplicationThread caller, IBinder resultTo, int requestCode, Intent intent, 
        String resultWho, String callingPackage, String resolvedType, 
        int startFlags, ProfilerInfo profilerInfo, Bundle bOptions) {
    return startActivityAsUser(caller, resultTo, requestCode, intent, resultWho, 
            callingPackage, resolvedType, startFlags, profilerInfo, bOptions,
            UserHandle.getCallingUserId());
}

在ActivityStarter.startActivity中创建sourceRecord、resultRecord

ActivityRecord sourceRecord = null;
ActivityRecord resultRecord = null;
if (resultTo != null) {
    sourceRecord = mRootActivityContainer.isInAnyStack(resultTo);
    if (sourceRecord != null) {
        if (requestCode >= 0 && !sourceRecord.finishing) {
            resultRecord = sourceRecord;
        }
    }
}

if ((launchFlags & Intent.FLAG_ACTIVITY_FORWARD_RESULT) != 0 && sourceRecord != null) {
    if (requestCode >= 0) {
        SafeActivityOptions.abort(options);
        return ActivityManager.START_FORWARD_AND_REQUEST_CONFLICT;
    }
    resultRecord = sourceRecord.resultTo;
    resultWho = sourceRecord.resultWho;
    requestCode = sourceRecord.requestCode;
    sourceRecord.resultTo = null;
}

最终通过resultRecord创建当前Activity对应的ActivityRecord

ActivityRecord r = new ActivityRecord(mService, callerApp, callingPid, callingUid,
        callingPackage, intent, resolvedType, aInfo, mService.getGlobalConfiguration(),
        resultRecord, resultWho, requestCode, componentSpecified, voiceSession != null,
        mSupervisor, checkedOptions, sourceRecord);

根据launchMode、taskAffinity、Intent Flag来获取当前Activity对应的ActivityTask,如果是SingleTask且栈中存在实例则通过performClearTaskLocked方法执行清栈操作,非当前显示的Activity依次调用onDestory(10秒超时)

mRootActivityContainer.resumeFocusedStacksTopActivities(
        mTargetStack, mStartActivity, mOptions);
  • 接着pause正在显示的Activity
    • 将Activity对应堆栈中的mPausingActivity赋值为当前的Activity
    • 将Activity对应堆栈中的mResumedActivity赋值为NULL
  • 通过ClientTransaction(PauseActivityItem)通知客户端
  • Pause结束分两种情况:500ms超时/操作完成后通过activityPaused通知到AMS
    • 清空Activity对应堆栈中的mPausingActivity
    • 获取focusedActivityStack(isFocusableAndVisible),判断ActivityStack中是否存在mResumedActivity,在上面的步骤中,我们已经清空了mResumedActivity,因此会直接启动目标Activity,启动Activity分为两种情况:进程已启动,进程未启动

进程已启动

创建ClientTransaction,执行IAppliactionThread.scheduleTransaction跳转到用户端,用户端执行executeCallbacks、executeLifecycleState,进而执行handleLaunchActivity和handleResumeActivity

  • handleLaunchActivity:
    • 创建ContextImpl、创建Activity
    • 执行Activity.attach
      • 创建PhoneWindow
      • 创建WindowManagerImpl
    • 执行Activity.onCreate
  • handleResumeActivity:onStart、onResume、Activity.makeVisible
    • Activity.makeVisible:WindowManagerImpl.addView(DecorView)
      • WindowManagerImpl.addView(DecorView):WindowManagerGlobal(保存params、roots、views)
        • ViewRootImpl.addView:requestLayout、IWindow、IWindowSession
          • WindowManagerService:持有WindowState、WindowToken,IWindowSession持有ISurfaceSession

进程未启动

  • 启动进程:Runtime.init
    • initZygote
    • initApplication
      • Looper.prepare
      • 执行AMS.attachApplication
        • 回调执行IApplicationThread.bindAppliation
          • 创建ContextImpl
          • 创建Application
          • 启动ContentProvider
          • 执行Application.onCreate
        • 执行RootActivityContainer.attachApplication
          • 判断mTmpActivityList中ActivityRecord的进程信息是否与当前进程相符,并且能够匹配到focusedStack的topActivity,如果匹配则继续走Activity启动流程
      • Looper.loop

Service

startService

关键参数:(IApplicationThread、Intent、requestForeground)

AMS逻辑:判断当前进程是否是前台进程:callerFg、通过PMS解析得到ServiceLookupResult进而创建ServiceRecord,走启动逻辑:

  • Service已启动:sendServiceArgs-> onStartCommand
  • 进程已启动,Service未启动:realStartServiceLocked:
    • scheduleCreateService:执行onCreate生命周期
    • sendServiceArgsLocked:执行onStart生命周期
      • 如果是前台服务,执行超时逻辑:scheduleServiceForegroundTransitionTimeoutLocked
  • 进程未启动:
    • 启动进程
    • 在attachAppliaction中执行ActiveServices.attachApplication
      • 启动mPendingServices
  • bumpServiceExecutingLocked:发送ANR检测消息(20/200),AMS.serviceDoneExecuting
    • bumpServiceExecutingLocked(r, execInFg, "create");
    • bumpServiceExecutingLocked(r, execInFg, "start");
    • bumpServiceExecutingLocked(r, execInFg, "bind");
    • bumpServiceExecutingLocked(s, false, "unbind");
    • bumpServiceExecutingLocked(r, false, "destroy");

bindService

  • 封装ServiceConnection
  • 判断是否调用了onBind,不会再次调用
  • 如果调用了onUnbind,下次绑定判断onUnbind返回值,如果返回true,调用onRebind,否则不再调用任何生命周期

ContentProvider

正常情况下,在bindApplication中通过installContentProviders完成初始化,内部执行intallProvider,然后执行publishContentProviders通知到AMS

ContentProvider中存在一个比较特殊的数据结构:ContentProviderHolder,当我们调用AMS.getContentProvider时

  • 如果ContentProvider能够在当前进程运行(multiproc/processName相等),则返回空的Holder,由本地进行填充(我理解是防止死锁)
  • 如果无法在当前进程运行,则判断目标进程是否启动
    • 如果进程已启动,则通过IApplicationThread.scheduleInstallProvider安装
    • 如果进程未启动,则启动进程,并加入mLaunchingProviders中
    • 请求逻辑原地等待,20秒超时
  • 在AMS.attachApplication中发送CONTENT_PROVIDER_PUBLISH_TIMEOUT_MSG消息
  • 在publishContentProviders中移除CONTENT_PROVIDER_PUBLISH_TIMEOUT_MSG消息

BroadcastReceiver

普通广播(可带权限)、有序广播(可带权限)、粘性广播、本地广播,8.0开始禁用隐式广播

对应广播队列有前台队列、后台队列,每个队列里还有并行和串行列表

广播可以动态注册、也可以静态注册。动态注册会判断当前是否有符合的粘性广播,如有,则接收

当我们发送广播时

  • 首先在AMS侧设置Flag,增加NOT_INCLUDE_STOP_PACKAGE、如果系统未完成启动,增加REGISTERED_ONLY
  • 判断权限,如果非系统进程使用了保护广播,则抛出异常
  • 如果是粘性广播,则记录到stickyBroadcastList中
  • 查询receivers(静态注册的广播接受者)和registeredReceivers(动态注册的广播接受者)
  • 如果发送的是普通广播(非顺序广播),则通过parallelQueue来通知registeredReceivers
  • 否则,将receivers和registeredReceivers按照优先级合并,顺序发送广播,客户端完成广播处理后,通过sendFinished告知AMS,调用cancelBroadcastTimeoutLocked取消超时广播
  • 如果是串行广播,触发processNextBroadcastLocked方法,该方法会通过setBroadcastTimeoutLocked发送超时消息

onWindowFocusChanged

activityPaused -> WMS.H(IWindow.windowFocusChanged(false)) -> onWindowFocusChanged(false)
IWindowSession.relayout -> WMS.H(IWindow.windowFocusChanged(true)) -> onWindowFocusChanged(true)
onWindowFocusChanged函数是Activity的首帧渲染时间,并不是首页第一屏展示时间

1944 performTraversals 1944~2770
2028 dispatchAttachedToWindow
2264 relayoutWindow
2541 performMeasure
2590 performLayout
2755 performDraw

安卓系统启动流程

  • 当按电源键触发开机,首先会从 ROM 中预定义的地方加载引导程序 BootLoader 到 RAM 中,并执行 BootLoader 程序启动 Linux Kernel, 然后启动用户级别的第一个进程:init 进程
  • init 进程会解析 init.rc 脚本做一些初始化工作,包括挂载文件系统、创建工作目录以及启动系统服务进程,包括 Zygote、ServiceManager、Media 等
  • 在 Zygote 中会进一步去启动 system_server 进程,然后在 system_server 进程中会启动 AMS、WMS、PMS 等服务,等这些服务启动之后,AMS 中就会启动 Launcher 应用来现实桌面

system_server 为什么要在 Zygote 中启动,而不是由 init 直接启动呢

Zygote 作为一个孵化器,可以提前加载一些资源,这样 fork() 时基于 Copy-On-Write 机制创建的其他进程就能直接使用这些资源,而不用重新加载。比如 system_server 就可以直接使用 Zygote 中的 JNI 函数、共享库、常用的类、以及主题资源

为什么要专门使用 Zygote 进程去孵化应用进程,而不是让 system_server 去孵化呢

首先 system_server 相比 Zygote 多运行了 AMS、WMS 等服务,这些对一个应用程序来说是不需要的。另外进程的 fork() 对多线程不友好,仅会将发起调用的线程拷贝到子进程,这可能会导致死锁,而 system_server 中肯定是有很多线程的

能说说具体是怎么导致死锁的吗

fork() 时只会把调用线程拷贝到子进程、其他线程都会立即停止,那如果一个线程在 fork() 前占用了某个互斥量,fork() 后被立即停止,这个互斥量就得不到释放,再去请求该互斥量就会发生死锁了

Zygote 为什么不采用 Binder 机制进行 IPC 通信

  • 1、多线程问题:Linux中fork进程是不推荐fork一个多线程的进程的,因为如果存在锁的情况下,会导致锁异常
    • Binder 机制中存在 Binder 线程池,是多线程的,如果 Zygote 采用 Binder 的话就存在上面说的 fork() 与 多线程的问题了
    • 严格来说,Binder 机制不一定要多线程,所谓的 Binder 线程只不过是在循环读取 Binder 驱动的消息而已,只注册一个 Binder 线程也是可以工作的,比如 service manager 就是这样的
    • 实际上 Zygote 尽管没有采取 Binder 机制,它也不是单线程的,但它在 fork() 前主动停止了其他线程,fork() 后重新启动了
  • 2、效率问题:AMS和Zygote之间使用的LocalSocket,相对于网络Socket,减少了数据验证等环节,所以其实效率相对于正常的网络Socket会大幅的提升。虽然还是要经过两次拷贝,但是由于数据量并不大,所以其实影响并不明显
  • 3、Binder拷贝问题:如果使用Binder机制的话,从Zygote中fork出子进程会拷贝Zygote中Binder对象。从而多占用了一块无用的内存区域

fork进程哪些内存段可以被继承

  • 代码段:包含可执行程序的指令,子进程会继承父进程的代码段
  • 数据段:包含已初始化的全局变量和静态变量,子进程会继承父进程的数据段
  • BSS段:包含未初始化的全局变量和静态变量,子进程会继承父进程的BSS段,并且该段会被清零

以下内存段不会被继承:

  • 堆段:存放动态分配的内存,比如通过malloc或new分配的内存。在执行fork创建新进程时,新进程的堆段不会继承父进程的内容,但是在Linux中,通常使用写时复制(Copy-On-Write)技术,因此只有在堆段中被修改时才会进行实际的复制
  • 堆栈顶指针:指向栈段的顶部,子进程会获得与父进程相同的堆栈顶指针
  • 栈段:存放函数调用时的局部变量、函数参数等。在执行fork创建新进程时,新进程会继承父进程的栈段。在Linux中,栈段通常也是写时复制的,因此只有在栈段中被修改时才会进行实际的复制

参考文献