说了说Binder。再看看Looper,睡到床上想想,接下来分析什么呢?第一时间越入脑海的就是应用进程的启动过程了。Android相比IOS是开放的(不能说开源,因为还是有些源代码没有对外,比如每个厂商的驱动等等),可以下载到基本上所有的源码,而且也提供了非常好的framework框架,现在的Android App到处都是,这些也是直接和用户打交道的,那么我们是否了解一个应用进程的启动过程呢?在之前的面试和培训中,我从身旁同事中了解的情况就是,相当大部分人都对我们应用进程的启动还秀陌生。我们今天就来看看Android应用进程是怎么样从无到有启动起来的。
当我们在Launcher桌面上点击一个应用的图标时,当然Launcher启动之后,已经把桌面上每个图标对应的信息都封装好了,用户点击之后,Launcher进程就会通过Binder进程间通信机制调用startActivity的方式去打开目标进程的入口Activity,指令传达到ActivityManagerService当中时,AMS会去检测当有的应用进程是否已经启动,如果没有启动,那么就会先将当前的目标进程启动起来,启动目标进程是通过调用startProcessLocked方法来完成的,该方法的实现在ActivityManagerService类中,目录路径为frameworks\base\services\core\java\com\android\server\am\ActivityManagerService.java,startProcessLocked方法的源码如下:
private final void startProcessLocked(ProcessRecord app,
String hostingType, String hostingNameStr) {
startProcessLocked(app, hostingType, hostingNameStr, null /* abiOverride */,
null /* entryPoint */, null /* entryPointArgs */);
}
该方法就是直接调用另一个重载方法来实现在,该重载方法的源码如下:
private final void startProcessLocked(ProcessRecord app, String hostingType,
String hostingNameStr, String abiOverride, String entryPoint, String[] entryPointArgs) {
long startTime = SystemClock.elapsedRealtime();
if (app.pid > 0 && app.pid != MY_PID) {
checkTime(startTime, "startProcess: removing from pids map");
synchronized (mPidsSelfLocked) {
mPidsSelfLocked.remove(app.pid);
mHandler.removeMessages(PROC_START_TIMEOUT_MSG, app);
}
checkTime(startTime, "startProcess: done removing from pids map");
app.setPid(0);
}
if (DEBUG_PROCESSES && mProcessesOnHold.contains(app)) Slog.v(TAG_PROCESSES,
"startProcessLocked removing on hold: " + app);
mProcessesOnHold.remove(app);
checkTime(startTime, "startProcess: starting to update cpu stats");
updateCpuStats();
checkTime(startTime, "startProcess: done updating cpu stats");
try {
try {
final int userId = UserHandle.getUserId(app.uid);
AppGlobals.getPackageManager().checkPackageStartable(app.info.packageName, userId);
} catch (RemoteException e) {
throw e.rethrowAsRuntimeException();
}
int uid = app.uid;
int[] gids = null;
int mountExternal = Zygote.MOUNT_EXTERNAL_NONE;
if (!app.isolated) {
int[] permGids = null;
try {
checkTime(startTime, "startProcess: getting gids from package manager");
final IPackageManager pm = AppGlobals.getPackageManager();
permGids = pm.getPackageGids(app.info.packageName,
MATCH_DEBUG_TRIAGED_MISSING, app.userId);
StorageManagerInternal storageManagerInternal = LocalServices.getService(
StorageManagerInternal.class);
mountExternal = storageManagerInternal.getExternalStorageMountMode(uid,
app.info.packageName);
} catch (RemoteException e) {
throw e.rethrowAsRuntimeException();
}
/*
* Add shared application and profile GIDs so applications can share some
* resources like shared libraries and access user-wide resources
*/
if (ArrayUtils.isEmpty(permGids)) {
gids = new int[3];
} else {
gids = new int[permGids.length + 3];
System.arraycopy(permGids, 0, gids, 3, permGids.length);
}
gids[0] = UserHandle.getSharedAppGid(UserHandle.getAppId(uid));
gids[1] = UserHandle.getCacheAppGid(UserHandle.getAppId(uid));
gids[2] = UserHandle.getUserGid(UserHandle.getUserId(uid));
}
checkTime(startTime, "startProcess: building args");
if (mFactoryTest != FactoryTest.FACTORY_TEST_OFF) {
if (mFactoryTest == FactoryTest.FACTORY_TEST_LOW_LEVEL
&& mTopComponent != null
&& app.processName.equals(mTopComponent.getPackageName())) {
uid = 0;
}
if (mFactoryTest == FactoryTest.FACTORY_TEST_HIGH_LEVEL
&& (app.info.flags & ApplicationInfo.FLAG_FACTORY_TEST) != 0) {
uid = 0;
}
}
int debugFlags = 0;
if ((app.info.flags & ApplicationInfo.FLAG_DEBUGGABLE) != 0) {
debugFlags |= Zygote.DEBUG_ENABLE_JDWP;
debugFlags |= Zygote.DEBUG_JAVA_DEBUGGABLE;
// Also turn on CheckJNI for debuggable apps. It's quite
// awkward to turn on otherwise.
debugFlags |= Zygote.DEBUG_ENABLE_CHECKJNI;
}
// Run the app in safe mode if its manifest requests so or the
// system is booted in safe mode.
if ((app.info.flags & ApplicationInfo.FLAG_VM_SAFE_MODE) != 0 ||
mSafeMode == true) {
debugFlags |= Zygote.DEBUG_ENABLE_SAFEMODE;
}
if ("1".equals(SystemProperties.get("debug.checkjni"))) {
debugFlags |= Zygote.DEBUG_ENABLE_CHECKJNI;
}
String genDebugInfoProperty = SystemProperties.get("debug.generate-debug-info");
if ("true".equals(genDebugInfoProperty)) {
debugFlags |= Zygote.DEBUG_GENERATE_DEBUG_INFO;
}
if ("1".equals(SystemProperties.get("debug.jni.logging"))) {
debugFlags |= Zygote.DEBUG_ENABLE_JNI_LOGGING;
}
if ("1".equals(SystemProperties.get("debug.assert"))) {
debugFlags |= Zygote.DEBUG_ENABLE_ASSERT;
}
if (mNativeDebuggingApp != null && mNativeDebuggingApp.equals(app.processName)) {
// Enable all debug flags required by the native debugger.
debugFlags |= Zygote.DEBUG_ALWAYS_JIT; // Don't interpret anything
debugFlags |= Zygote.DEBUG_GENERATE_DEBUG_INFO; // Generate debug info
debugFlags |= Zygote.DEBUG_NATIVE_DEBUGGABLE; // Disbale optimizations
mNativeDebuggingApp = null;
}
String invokeWith = null;
if ((app.info.flags & ApplicationInfo.FLAG_DEBUGGABLE) != 0) {
// Debuggable apps may include a wrapper script with their library directory.
String wrapperFileName = app.info.nativeLibraryDir + "/wrap.sh";
StrictMode.ThreadPolicy oldPolicy = StrictMode.allowThreadDiskReads();
try {
if (new File(wrapperFileName).exists()) {
invokeWith = "/system/bin/logwrapper " + wrapperFileName;
}
} finally {
StrictMode.setThreadPolicy(oldPolicy);
}
}
String requiredAbi = (abiOverride != null) ? abiOverride : app.info.primaryCpuAbi;
if (requiredAbi == null) {
requiredAbi = Build.SUPPORTED_ABIS[0];
}
String instructionSet = null;
if (app.info.primaryCpuAbi != null) {
instructionSet = VMRuntime.getInstructionSet(app.info.primaryCpuAbi);
}
app.gids = gids;
app.requiredAbi = requiredAbi;
app.instructionSet = instructionSet;
// the per-user SELinux context must be set
if (TextUtils.isEmpty(app.info.seInfoUser)) {
Slog.wtf(TAG, "SELinux tag not defined",
new IllegalStateException("SELinux tag not defined for "
+ app.info.packageName + " (uid " + app.uid + ")"));
}
final String seInfo = app.info.seInfo
+ (TextUtils.isEmpty(app.info.seInfoUser) ? "" : app.info.seInfoUser);
// Start the process. It will either succeed and return a result containing
// the PID of the new process, or else throw a RuntimeException.
boolean isActivityProcess = (entryPoint == null);
if (entryPoint == null) entryPoint = "android.app.ActivityThread";
Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "Start proc: " +
app.processName);
checkTime(startTime, "startProcess: asking zygote to start proc");
ProcessStartResult startResult;
if (hostingType.equals("webview_service")) {
startResult = startWebView(entryPoint,
app.processName, uid, uid, gids, debugFlags, mountExternal,
app.info.targetSdkVersion, seInfo, requiredAbi, instructionSet,
app.info.dataDir, null, entryPointArgs);
} else {
startResult = Process.start(entryPoint,
app.processName, uid, uid, gids, debugFlags, mountExternal,
app.info.targetSdkVersion, seInfo, requiredAbi, instructionSet,
app.info.dataDir, invokeWith, entryPointArgs);
}
checkTime(startTime, "startProcess: returned from zygote!");
Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
mBatteryStatsService.noteProcessStart(app.processName, app.info.uid);
checkTime(startTime, "startProcess: done updating battery stats");
EventLog.writeEvent(EventLogTags.AM_PROC_START,
UserHandle.getUserId(uid), startResult.pid, uid,
app.processName, hostingType,
hostingNameStr != null ? hostingNameStr : "");
try {
AppGlobals.getPackageManager().logAppProcessStartIfNeeded(app.processName, app.uid,
seInfo, app.info.sourceDir, startResult.pid);
} catch (RemoteException ex) {
// Ignore
}
if (app.persistent) {
Watchdog.getInstance().processStarted(app.processName, startResult.pid);
}
checkTime(startTime, "startProcess: building log message");
StringBuilder buf = mStringBuilder;
buf.setLength(0);
buf.append("Start proc ");
buf.append(startResult.pid);
buf.append(':');
buf.append(app.processName);
buf.append('/');
UserHandle.formatUid(buf, uid);
if (!isActivityProcess) {
buf.append(" [");
buf.append(entryPoint);
buf.append("]");
}
buf.append(" for ");
buf.append(hostingType);
if (hostingNameStr != null) {
buf.append(" ");
buf.append(hostingNameStr);
}
Slog.i(TAG, buf.toString());
app.setPid(startResult.pid);
app.usingWrapper = startResult.usingWrapper;
app.removed = false;
app.killed = false;
app.killedByAm = false;
checkTime(startTime, "startProcess: starting to update pids map");
ProcessRecord oldApp;
synchronized (mPidsSelfLocked) {
oldApp = mPidsSelfLocked.get(startResult.pid);
}
// If there is already an app occupying that pid that hasn't been cleaned up
if (oldApp != null && !app.isolated) {
// Clean up anything relating to this pid first
Slog.w(TAG, "Reusing pid " + startResult.pid
+ " while app is still mapped to it");
cleanUpApplicationRecordLocked(oldApp, false, false, -1,
true /*replacingPid*/);
}
synchronized (mPidsSelfLocked) {
this.mPidsSelfLocked.put(startResult.pid, app);
if (isActivityProcess) {
Message msg = mHandler.obtainMessage(PROC_START_TIMEOUT_MSG);
msg.obj = app;
mHandler.sendMessageDelayed(msg, startResult.usingWrapper
? PROC_START_TIMEOUT_WITH_WRAPPER : PROC_START_TIMEOUT);
}
}
checkTime(startTime, "startProcess: done updating pids map");
} catch (RuntimeException e) {
Slog.e(TAG, "Failure starting process " + app.processName, e);
// Something went very wrong while trying to start this process; one
// common case is when the package is frozen due to an active
// upgrade. To recover, clean up any active bookkeeping related to
// starting this process. (We already invoked this method once when
// the package was initially frozen through KILL_APPLICATION_MSG, so
// it doesn't hurt to use it again.)
forceStopPackageLocked(app.info.packageName, UserHandle.getAppId(app.uid), false,
false, true, false, false, UserHandle.getUserId(app.userId), "start failure");
}
}
该方法中int uid = app.uid可以获取到目标进程的uid,uid是标识目标进程身份的一个整数,它最原始的分配是在应用进程安装时,由PackageManagerService分配好的,后续我们在分析应用进程的安装过程时也会说到这点,如果大家平时有遇到一些关于uid的问题,想要查应用进程的uid的话,可以在手机/data/system/目录下,找packages.xml、packages-backup.xml这两个文件,里边记录了当前手机上所有安装过的应用进程的信息,包括权限、uid等等。接下搂重要的工作就是根据该方法的传入的第一个参数ProcessRecord app中封装好的信息去给局部变量debugFlags赋值,debug.checkjni属性就是在进行JNI调用时是否要进行合法性检查,大家平时处理问题单的时候应该也经常见过,比如我们把一个应用进程杀掉,然后开始记录目录,在Launcher上点击该应用启动它,大家就会看到debug.checkjni = false的日志,默认场景下,该值为false,如果打开它的话,那么就会使我们的调用过程变慢。String requiredAbi = (abiOverride != null) ? abiOverride : app.info.primaryCpuAbi;这句逻辑是判断目标应用启动时,复制它要使用的Zygote是使用32位还是64位的,这个值后面会用到,该值最初的赋值是在Build类中的SUPPORTED_ABIS成员变量,它是一个String类型的数组,Build类的目录路径为frameworks\base\core\java\android\os\Build.java,成员变量SUPPORTED_ABIS的定义源码为:
/**
* An ordered list of ABIs supported by this device. The most preferred ABI is the first
* element in the list.
*
* See {@link #SUPPORTED_32_BIT_ABIS} and {@link #SUPPORTED_64_BIT_ABIS}.
*/
public static final String[] SUPPORTED_ABIS = getStringList("ro.product.cpu.abilist", ",");
/**
* An ordered list of <b>32 bit</b> ABIs supported by this device. The most preferred ABI
* is the first element in the list.
*
* See {@link #SUPPORTED_ABIS} and {@link #SUPPORTED_64_BIT_ABIS}.
*/
public static final String[] SUPPORTED_32_BIT_ABIS =
getStringList("ro.product.cpu.abilist32", ",");
/**
* An ordered list of <b>64 bit</b> ABIs supported by this device. The most preferred ABI
* is the first element in the list.
*
* See {@link #SUPPORTED_ABIS} and {@link #SUPPORTED_32_BIT_ABIS}.
*/
public static final String[] SUPPORTED_64_BIT_ABIS =
getStringList("ro.product.cpu.abilist64", ",");
如果获取到的requiredAbi为空,那么就会默认使用Build.SUPPORTED_ABIS[0]来启动目标进程,一般就是32位的Zygote了,接下来有一个非常重要的参数if (entryPoint == null) entryPoint = "android.app.ActivityThread",看到它我们应用非常熟悉了,就是我们前面讲Looper循环时提到的ActivityThread类了,它就是我们应用进程Java层的入口类,它的main函数就是应用的入口函数,这个局部变量entryPoint的命名感觉非常合适,很准确的表达了该参数的意图。上面的参数封装完成后,接下来就会调用Process.start来执行真正的启动了(我们假设hostingType.equals("webview_service")为false),启动完成后得到返回结果ProcessStartResult startResult,后面还会记录相关的日志,这些日志对我们分析问题也有很大的帮助。好,我们接下来看一下Process.start方法是如何启动应用进程的。Process类的目录路径为frameworks\base\core\java\android\os\Process.java,start方法的源码如下:
/**
* Start a new process.
*
* <p>If processes are enabled, a new process is created and the
* static main() function of a <var>processClass</var> is executed there.
* The process will continue running after this function returns.
*
* <p>If processes are not en