再学安卓 - 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
			}
			... ...
		}
	}
}
相关推荐
高林雨露35 分钟前
ImageView android:scaleType各种属性
android·imageview各种属性
Hi-Dison1 小时前
OpenHarmony系统中实现Android虚拟化、模拟器相关的功能,包括桌面显示,详细解决方案
android
事业运财运爆棚3 小时前
http 502 和 504 的区别
android
峥嵘life4 小时前
Android Studio新版本的一个资源id无法找到的bug解决
android·bug·android studio
编程乐学4 小时前
网络资源模板--Android Studio 实现绿豆通讯录
android·前端·毕业设计·android studio·大作业·安卓课设·绿豆通讯录
朴拙数科8 小时前
mysql报错解决 `1525 - Incorrect DATETIME value: ‘0000-00-00 00:00:00‘`
android·数据库·mysql
1登峰造极9 小时前
scroll-view 实现滑动显示,确保超出正常显示,小程序app,h5兼容
android·小程序
刘争Stanley10 小时前
Android 15(V)新功能适配,雕琢移动细节之美
android·kotlin·android 15·android v
小狗爱世界10 小时前
深入解析Binder源码
android·binder
qq_2146703510 小时前
android 聊天界面键盘、表情切换丝滑
android·gitee