首页   

再学安卓 - APP进程

鸿洋  · android  · 昨天

正文

系列:
再学安卓 - SystemServer
再学安卓 - Zygote

再学安卓 - init进程

前言

到上一篇结束,我们已经概括的梳理出系统启动过程中创建的主要进程,本篇我们就来看看与我们息息相关的APP进程从无到有是怎么诞生的。本篇内容也是系统启动章节的最后一篇。
既然是关注从无到有,那么我们就明确一点,本篇讲述的是Launcher冷启动APP的主线流程,着重关注Zygote和SystemServer如何配合创建APP进程的过程。AMS (ActivitiyManagerService)和ATMS (ActivityTaskManagerService)对于Activity的管理逻辑、启动之前的条件逻辑等我们都暂时略过。
以下是Zygote、SystemServer、APP进程之间的通信关系概括图。
1
Launcher

Launcher在收到点击图标事件后会辗转来到Activity.startActivity(),然后间接调用以下函数。
// frameworks/base/core/java/android/app/Instrumentation.java

public ActivityResult execStartActivity(  
        Context who, IBinder contextThread, IBinder token, Activity target,  
        Intent intent, int requestCode, Bundle options
{
        ... ...
        // ① 通过binder跨进程调用ATMS的接口
        int result = ActivityTaskManager.getService().startActivity(whoThread,  
        who.getOpPackageName(), who.getAttributionTag(), intent,  
        intent.resolveTypeIfNeeded(who.getContentResolver()), token,  
        target != null ? target.mEmbeddedID : null, requestCode, 0null, options);
        ... ...}

ActivityTaskManager.getService()通过ServiceManager获取到ActivityTaskManagerService的对外binder接口。其中whoThread参数为IApplicationThread.Stub.Proxy,又是一个binder接口,是本进程(APP进程)的对外接口。在我们这个场景下whoThread接口就是Launcher应用的接口。

2
AMS


这里虽然标题是AMS,但其中很大一部分逻辑由ATMS负责,我们可以概括的认为ATMS属于AMS的一部分。
// frameworks/base/services/core/java/com/android/server/wm/ActivityTaskManagerService.java

private int startActivityAsUser(IApplicationThread caller, String callingPackage,  
        @Nullable String callingFeatureId, Intent intent, String resolvedType,  
        IBinder resultTo, String resultWho, int requestCode, int startFlags,  
        ProfilerInfo profilerInfo, Bundle bOptions, int userId, boolean validateIncomingUser) {
        ... ...
        // 构造ActivityStarter
         return getActivityStartController().obtainStarter(intent, "startActivityAsUser")
         // caller(IApplicationThread.Stub.Proxy):发起进程(Launcher)的对外接口
         .setCaller(caller)
         ... ...
         // resultTo(ActivityRecord.Token):流程发起Activity的Token
         .setResultTo(resultTo)
         ... ...
         .execute();
}

接下来的流程主要任务是处理Activity相关的信息和数据,经过ActivityStarter、Task、ActivityTaskSupervisor等类的构建和检查,最终启动信息被打包成一个带Runnable的msg由ATMS投递到Looper中,而这个Runnable中携带一个函数指针指向ActivityManagerServicestartProcess函数。
// frameworks/base/services/core/java/com/android/server/wm/ActivityTaskManagerService.java

void startProcessAsync(ActivityRecord activity, boolean knownToBeDead, boolean isTop, String hostingType) {
    ... ...
    final Message m = PooledLambda.obtainMessage(ActivityManagerInternal::startProcess,  
        mAmInternal, activity.processName, activity.info.applicationInfo, knownToBeDead,  
        isTop, hostingType, activity.intent.getComponent());
    mH.sendMessage(m);
    ... ...}
下面的流程从消息队列取出msg执行Runnable函数开始,主要任务是处理启动进程相关信息和数据。流程比较长,且过程中的函数参数非常多,我们只关注几个重要的节点即可,别忘记我们的初衷:着重创建流程中跨进程的交互逻辑。
流程辗转来到以下函数,startProcess函数开启最后的请求创建过程,且返回创建结果。后面紧跟handleProcessStartedLocked函数处理创建结果。我们先继续往下跟,待会儿再回来看看后续处理流程。
// frameworks/base/services/core/java/com/android/server/am/ProcessList.java

boolean startProcessLocked(HostingRecord hostingRecord, String entryPoint, ProcessRecord app, int uid, int[] gids, int runtimeFlags, int zygotePolicyFlags, int mountExternal, String seInfo, String requiredAbi, String instructionSet, String invokeWith, long startUptime, long startElapsedTime) {
    ... ...
    final Process.ProcessStartResult startResult = startProcess(hostingRecord,  
        entryPoint, app,  
        uid, gids, runtimeFlags, zygotePolicyFlags, mountExternal, seInfo,  
        requiredAbi, instructionSet, invokeWith, startUptime);
    // 获得创建结果后,处理后续流程
    handleProcessStartedLocked(app, startResult.pid, startResult.usingWrapper,  
        startSeq, false);
    ... ...
}

private Process.ProcessStartResult startProcess(HostingRecord hostingRecord, String entryPoint, ProcessRecord app, int uid, int[] gids, int runtimeFlags, int zygotePolicyFlags, int mountExternal, String seInfo, String requiredAbi, String instructionSet, String invokeWith, long startTime) {
    ... ...
    if (hostingRecord.usesWebviewZygote()) {
        startResult = startWebView(...);
    } else if (hostingRecord.usesAppZygote()) {
        startResult = appZygote.getProcess().start(...);
    } else {
        // 启动普通APP进程,上面两个分支都是通过特殊的Zygote启动相应进程
        startResult = Process.start(...);
    }
}

又经过一连串的“函数轰炸”,终于它来了!我们看到了Socket的IO操作。
// frameworks/base/core/java/android/os/ZygoteProcess.java

private Process.ProcessStartResult attemptZygoteSendArgsAndGetResult(  
        ZygoteState zygoteState, String msgStr)
 throws ZygoteStartFailedEx 
{
    try {  
        final BufferedWriter zygoteWriter = zygoteState.mZygoteOutputWriter;  
        final DataInputStream zygoteInputStream = zygoteState.mZygoteInputStream;  

        // 写入请求数据
        zygoteWriter.write(msgStr);
        // 发送数据
        zygoteWriter.flush();

        Process.ProcessStartResult result = new Process.ProcessStartResult();  
        // 阻塞,读取结果
        result.pid = zygoteInputStream.readInt();
        result.usingWrapper = zygoteInputStream.readBoolean();

        if (result.pid < 0) {  
            throw new ZygoteStartFailedEx("fork() failed");  
        }  

        return result;  
    } catch (IOException ex) {  
        zygoteState.close();  
        Log.e(LOG_TAG, "IO Exception while communicating with Zygote - "  
                + ex.toString());  
        throw new ZygoteStartFailedEx(ex);  
    }
}


3
Zygote

还记得吗,前面的章节我们讲到Zygote启动后就在runSelectLoop函数中等待Socket消息,来回顾一下:
// frameworks/base/core/java/com/android/internal/os/ZygoteServer.java

Runnable runSelectLoop(String abiList) {
    ... ...
    while (true) {
        ... ...
        // pollReturnValue返回值:
        // 为0:没有文件描述符可用,即没有需要处理的消息
        // 为正:有文件描述符可用,即需要处理消息
        // 为负:出现错误
        pollReturnValue = Os.poll(pollFDs, pollTimeoutMs);
        ... ...
        if (pollReturnValue == 0) {
            // 没有消息,继续循环等待
            ... ...
        } else {
            ... ...
            // 需要处理消息
            final Runnable command = connection.processCommand(this, multipleForksOK);
            ... ...
        }
    }
}
// frameworks/base/core/java/com/android/internal/os/ZygoteConnection.java

Runnable processCommand(ZygoteServer zygoteServer, boolean multipleOK) {
    ... ...
    // 间接调用系统调用fork
    pid = Zygote.forkAndSpecialize(parsedArgs.mUid, parsedArgs.mGid,  
        parsedArgs.mGids, parsedArgs.mRuntimeFlags, rlimits,  
        parsedArgs.mMountExternal, parsedArgs.mSeInfo, parsedArgs.mNiceName,  
        fdsToClose, fdsToIgnore, parsedArgs.mStartChildZygote,  
        parsedArgs.mInstructionSet, parsedArgs.mAppDataDir,  
        parsedArgs.mIsTopApp, parsedArgs.mPkgDataInfoList,  
        parsedArgs.mAllowlistedDataInfoList, parsedArgs.mBindMountAppDataDirs,  
        parsedArgs.mBindMountAppStorageDirs);
    ... ...
    if (pid == 0) {
            // 新的APP进程中
             ... ...
             return handleChildProc(parsedArgs, childPipeFd, parsedArgs.mStartChildZygote);  
         } else {
             // Zygote进程中
             ... ...
             handleParentProc(pid, serverPipeFd);  
             return null;  
         }
}

新的进程创建完毕之后,程序“分叉”,先看新进程怎么走。
// frameworks/base/core/java/com/android/internal/os/ZygoteConnection.java

private Runnable handleChildProc(ZygoteArguments parsedArgs,  
        FileDescriptor pipeFd, boolean isZygote)
 
{
    // 关闭socket连接
    closeSocket();
    //设置进程名
    Zygote.setAppProcessName(parsedArgs, TAG);

    if (parsedArgs.mInvokeWith != null) {  
        // ① 处理Wrapper方式启动的进程的后续流程
        WrapperInit.execApplication(parsedArgs.mInvokeWith,  
                parsedArgs.mNiceName, parsedArgs.mTargetSdkVersion,  
                VMRuntime.getCurrentInstructionSet(),  
                pipeFd, parsedArgs.mRemainingArgs); 
        throw new IllegalStateException("WrapperInit.execApplication unexpectedly returned");  
    } else {  
        if (!isZygote) {  
            // ② 处理普通APP进程的后续流程
            return ZygoteInit.zygoteInit(parsedArgs.mTargetSdkVersion,  
                    parsedArgs.mDisabledCompatChanges,  
                    parsedArgs.mRemainingArgs, null /* classLoader */);  
        } else {  
            // ③ 处理特殊zygote孵化的进程的后续流程,例如:WebViewZygote孵化的进程  
            return ZygoteInit.childZygoteInit(  
                    parsedArgs.mRemainingArgs  /* classLoader */);  
        }  
    }
}

① 在某些分析或开发场景下,需要由分析工具来启动对应的APP进程(方便读取内存等进程相关数据),而不是Zygote来启动,因此,Android提供这种Wrapper方式来满足此需求。
② 这里的后续流程和SystemServer的创建就一样了,唯一不同的是传入参数为android.app.ActivityThread,那么接下来就会反射调用ActivityThread的main函数,接着就是进入Looper.loop()循环,APP进程就这样跑起来了。
③ WebViewZygote孵化WebView进程后的处理流程。WebViewZygote主要用于提升WebView使用体验,加快启动速度、减少内存占用、与APP进程隔离等。
再来看另一条岔路,Zygote进程在创建新进程之后做了什么?
// frameworks/base/core/java/com/android/internal/os/ZygoteConnection.java

private void handleParentProc(int pid, FileDescriptor pipeFd) {
    ... ...
    // 通过socket连接回写pid和是否使用wrapper方式启动
    mSocketOutStream.writeInt(pid);  
    mSocketOutStream.writeBoolean(usingWrapper);
    ... ...
}

回写到哪里去了?当然是与Zygote建立socket通信的System Server。前面我们分析到最后SystemServer发出请求时是在attemptZygoteSendArgsAndGetResult函数中。

// frameworks/base/core/java/android/os/ZygoteProcess.java

private Process.ProcessStartResult attemptZygoteSendArgsAndGetResult(  
        ZygoteState zygoteState, String msgStr)
 throws ZygoteStartFailedEx 
{
    ... ...  
        // 阻塞,读取结果
        result.pid = zygoteInputStream.readInt();
        result.usingWrapper = zygoteInputStream.readBoolean();

        if (result.pid < 0) {  
            throw new ZygoteStartFailedEx("fork() failed");  
        }  

        return result;  
    } catch (IOException ex) {  
        zygoteState.close();  
        Log.e(LOG_TAG, "IO Exception while communicating with Zygote - "  
                + ex.toString());  
        throw new ZygoteStartFailedEx(ex);  
    }
}


将pid和usingWrapper打包进ProcessStartResult,然后一路返回,直到ProcessList中。
// frameworks/base/services/core/java/com/android/server/am/ProcessList.java

boolean startProcessLocked(HostingRecord hostingRecord, String entryPoint, ProcessRecord app, int uid, int[] gids, int runtimeFlags, int zygotePolicyFlags, int mountExternal, String seInfo, String requiredAbi, String instructionSet, String invokeWith, long startUptime, long startElapsedTime) {
    ... ...
    final Process.ProcessStartResult startResult = startProcess(hostingRecord,  
        entryPoint, app,  
        uid, gids, runtimeFlags, zygotePolicyFlags, mountExternal, seInfo,  
        requiredAbi, instructionSet, invokeWith, startUptime);
    // 获得创建结果后,处理后续流程
    handleProcessStartedLocked(app, startResult.pid, startResult.usingWrapper,  
        startSeq, false);
    ... ...
}

boolean handleProcessStartedLocked(ProcessRecord app, int pid, boolean usingWrapper, long expectedStartSeq, boolean procAttached) {
    ... ...
  // 检查Zygote启动的进程是否合法
  final String reason = isProcStartValidLocked(app, expectedStartSeq);
    if (reason != null) {
        ... ...
        // 进程不合法,杀死进程
        killProcessQuiet(pid);
        ... ...
        return false;
    }
    ... ...
    // ① EventLog打印日志
    EventLog.writeEvent(EventLogTags.AM_PROC_START,  
        UserHandle.getUserId(app.getStartUid()), pid, app.getStartUid(),  
        app.processName, app.getHostingRecord().getType(),  
        app.getHostingRecord().getName() != null ? app.getHostingRecord().getName() : "");
    ... ...
    // 在ProcessRecord中记录进程信息
    app.setPid(pid);  
    app.setUsingWrapper(usingWrapper);  
    app.setPendingStart(false);
    ... ...
}

① 这里就是我们常见的那句EventLog打印的地方,出现这条日志,表明APP进程完全启动成功。
12-02 15:01:30.013 635 677 I am_proc_start: [0,2741,10136,com.vincent.sample,next-top-activity,{com.vincent.sample/com.vincent.sample.MainActivity}]

对照这打印日志的代码,再看这条日志,其中每一项的含义就非常容易理解了,因此,学习看EventLog最好的方法就是直接看日志打印代码,源码真是好东西,无需过多的文字描述,一切尽在不言中。

4
总结


经过本篇的探索,开头那幅概括图的粒度就太粗了,看了跟没看差不多,我们就来细化一下它,加深印象。
5
Q&A


调试AMS时发现参数、局部变量值无法查看。

原因是services.jar默认开启了混淆,需要将混淆开关关闭,重新编译上传。
// frameworks/base/services/Android.bp
system_optimized_java_defaults {
    name: "services_java_defaults",
    soong_config_variables: {
        SYSTEM_OPTIMIZE_JAVA: {
            optimize: {
                enabled: true//将这一行的值改为false
            }
            ... ...
        }
    }
}

扫一扫 关注我的公众号

如果你想要跟大家分享你的文章,欢迎投稿~


┏(^0^)┛明天见!


© 2024 精读
删除内容请联系邮箱 2879853325@qq.com