再学安卓 - 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
			}
			... ...
		}
	}
}
相关推荐
踏雪羽翼6 小时前
android TextView实现文字字符不同方向显示
android·自定义view·textview方向·文字方向·textview文字显示方向·文字旋转·textview文字旋转
lxysbly6 小时前
安卓玩MRP冒泡游戏:模拟器下载与使用方法
android·游戏
夏沫琅琊9 小时前
Android 各类日志全面解析(含特点、分析方法、实战案例)
android
程序员JerrySUN9 小时前
OP-TEE + YOLOv8:从“加密权重”到“内存中解密并推理”的完整实战记录
android·java·开发语言·redis·yolo·架构
TeleostNaCl10 小时前
Android | 启用 TextView 跑马灯效果的方法
android·经验分享·android runtime
TheNextByte111 小时前
Android USB文件传输无法使用?5种解决方法
android
quanyechacsdn12 小时前
Android Studio创建库文件用jitpack构建后使用implementation方式引用
android·ide·kotlin·android studio·implementation·android 库文件·使用jitpack
程序员陆业聪13 小时前
聊聊2026年Android开发会是什么样
android
编程大师哥13 小时前
Android分层
android
极客小云15 小时前
【深入理解 Android 中的 build.gradle 文件】
android·安卓·安全架构·安全性测试