再学安卓 - APP进程

前言

到上一篇结束,我们已经概括的梳理出系统启动过程中创建的主要进程,本篇我们就来看看与我们息息相关的APP进程从无到有是怎么诞生的。本篇内容也是系统启动章节的最后一篇。

既然是关注从无到有,那么我们就明确一点,本篇讲述的是Launcher冷启动APP的主线流程,着重关注Zygote和SystemServer如何配合创建APP进程的过程。AMS(ActivitiyManagerService)和ATMS(ActivityTaskManagerService)对于Activity的管理逻辑、启动之前的条件逻辑等我们都暂时略过。

以下是Zygote、SystemServer、APP进程之间的通信关系概括图。

Launcher

Launcher在收到点击图标事件后会辗转来到Activity.startActivity(),然后间接调用以下函数。

java 复制代码
// 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, 0, null, options);
        ... ...
}

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

AMS

这里虽然标题是AMS,但其中很大一部分逻辑由ATMS负责,我们可以概括的认为ATMS属于AMS的一部分。

java 复制代码
// 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中携带一个函数指针指向ActivityManagerService的startProcess函数。

java 复制代码
// 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函数处理创建结果。我们先继续往下跟,待会儿再回来看看后续处理流程。

java 复制代码
// 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操作。

java 复制代码
// 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);  
	}
}

Zygote

还记得吗,前面的章节我们讲到Zygote启动后就在runSelectLoop函数中等待Socket消息,来回顾一下:

java 复制代码
// 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);
		    ... ...
		}
	}
}
java 复制代码
// 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;  
	     }
}

新的进程创建完毕之后,程序"分叉",先看新进程怎么走。

java 复制代码
// 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进程在创建新进程之后做了什么?

java 复制代码
// 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函数中。

java 复制代码
// 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中。

java 复制代码
// 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最好的方法就是直接看日志打印代码,源码真是好东西,无需过多的文字描述,一切尽在不言中。

总结

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

Q&A

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

原因是services.jar默认开启了混淆,需要将混淆开关关闭,重新编译上传。

bp 复制代码
// frameworks/base/services/Android.bp
system_optimized_java_defaults {
	name: "services_java_defaults",
	soong_config_variables: {
		SYSTEM_OPTIMIZE_JAVA: {
			optimize: {
				enabled: true, //将这一行的值改为false
			}
			... ...
		}
	}
}
相关推荐
千里马学框架2 天前
一起学 Android 14:ShellTransition 屏幕旋转过程深度剖析
android·智能手机·性能优化·framework·性能·屏幕旋转·rotation
美狐美颜SDK开放平台2 天前
开发直播APP时如何接入视频美颜SDK?开发流程与注意事项
android·人工智能·计算机视觉·音视频·直播美颜sdk
AFinalStone2 天前
Android7 SystemUI源码解析(七)Keyguard锁屏模块深度解析
android·systemui
致远ccc2 天前
Google Play 上架前如何测试 App?多国家 Android 环境测试
android·app测试·googleplay·多国家应用测试
ttyyttemo2 天前
Kotlin 协程中的 Job 结构化并发与取消
android
我爱cope2 天前
【操作系统 | 计算机硬件:CPU、内存与 I/O 如何支撑操作系统?】
学习·操作系统
sun0077002 天前
tbox 4g/5g切换,导致wan ip 改变,导致车机旧网络不可用。需要重启车机才行
android
其实防守也摸鱼2 天前
内网穿透与反向代理:原理、工具与实战指南
android·大数据·运维·安全·网络安全·自动化·渗透
AFinalStone3 天前
Android7 SystemUI 源码解析(四)NavigationBar 导航栏与 SystemBars
android·systemui
JMchen3 天前
属性动画原理与高级动画实现
android·kotlin·canvas