android:process 属性详解及注意事项
Posted by Roger on March 17, 2016
android:process 的坑,你懂吗?
许多知识知其然而不知其所以然,这也许就是大神与菜鸟的区别吧。
最近排查问题时发现一个问题: 一个在 Application 中启动的定时任务在运行时会被调用多次,诡异的很,最后发现是一个前人留下的坑,原因就是对 android:process 不知其所以然造成的。
android:process 属性
关于 android:process 属性,相信大家都不陌生,android 官网是这样说明的 :
默认情况下,同一应用的所有组件均在相同的进程中运行,且大多数应用都不会改变这一点。 但是,如果您发现需要控制某个组件所属的进程,则可在清单文件中执行此操作。
各类组件元素的清单文件条目—
、
、
和
—均支持 android:process 属性,此属性可以指定该组件应在哪个进程运行。您可以设置此属性,使每个组件均在各自的进程中运行,或者使一些组件共享一个进程,而其他组件则不共享。 此外,您还可以设置 android:process,使不同应用的组件在相同的进程中运行,但前提是这些应用共享相同的 Linux 用户 ID 并使用相同的证书进行签署。
此外,
元素还支持 android:process 属性,以设置适用于所有组件的默认值。
如果内存不足,而其他为用户提供更紧急服务的进程又需要内存时,Android 可能会决定在某一时刻关闭某一进程。在被终止进程中运行的应用组件也会随之销毁。 当这些组件需要再次运行时,系统将为它们重启进程。
决定终止哪个进程时,Android 系统将权衡它们对用户的相对重要程度。例如,相对于托管可见 Activity 的进程而言,它更有可能关闭托管屏幕上不再可见的 Activity 进程。 因此,是否终止某个进程的决定取决于该进程中所运行组件的状态。 下面,我们介绍决定终止进程所用的规则。
在需要使用到新进程时,可以使用 android:process 属性,如果被设置的进程名是以一个冒号开头的,则这个新的进程对于这个应用来说是私有的,当它被需要或者这个服务需要在新进程中运行的时候,这个新进程将会被创建。如果这个进程的名字是以小写字符开头的,则这个服务将运行在一个以这个名字命名的全局的进程中,当然前提是它有相应的权限。这将允许在不同应用中的各种组件可以共享一个进程,从而减少资源的占用。具体可以参考博客:apk,task,android:process与android:sharedUserId的区别
重点来了,因为设置了 android:process 属性将组件运行到另一个进程,相当于另一个应用程序,所以在另一个线程中也将新建一个 Application 的实例。因此,每新建一个进程 Application 的 onCreate 都将被调用一次。 如果在 Application 的 onCreate 中有许多初始化工作并且需要根据进程来区分的,那就需要特别注意了。
让我们到 Framework 中看看新建进程的逻辑,请打开老罗的博客 : Android系统在新进程中启动自定义服务过程(startService)的原理分析
详细介绍了新进程启动的过程,其中我们重点看到 Step 17. ActivityThread.handleCreateService
中
public final class ActivityThread {
......
private final void handleCreateService(CreateServiceData data) {
// If we are getting ready to gc after going to the background, well
// we are back active so skip it.
unscheduleGcIdler();
LoadedApk packageInfo = getPackageInfoNoCheck(
data.info.applicationInfo);
Service service = null;
try {
java.lang.ClassLoader cl = packageInfo.getClassLoader();
service = (Service) cl.loadClass(data.info.name).newInstance();
} catch (Exception e) {
if (!mInstrumentation.onException(service, e)) {
throw new RuntimeException(
"Unable to instantiate service " + data.info.name
+ ": " + e.toString(), e);
}
}
try {
if (localLOGV) Slog.v(TAG, "Creating service " + data.info.name);
ContextImpl context = new ContextImpl();
context.init(packageInfo, null, this);
Application app = packageInfo.makeApplication(false, mInstrumentation);
context.setOuterContext(service);
service.attach(context, this, data.info.name, data.token, app,
ActivityManagerNative.getDefault());
service.onCreate();
mServices.put(data.token, service);
try {
ActivityManagerNative.getDefault().serviceDoneExecuting(
data.token, 0, 0, 0);
} catch (RemoteException e) {
// nothing to do.
}
} catch (Exception e) {
if (!mInstrumentation.onException(service, e)) {
throw new RuntimeException(
"Unable to create service " + data.info.name
+ ": " + e.toString(), e);
}
}
}
......
}
看到这行 Application app = packageInfo.makeApplication(false, mInstrumentation);
在这里创建了 Application 。
解决方案
获取当前运行进程的名称:
public static String getProcessName(Context cxt, int pid) {
ActivityManager am = (ActivityManager) cxt.getSystemService(Context.ACTIVITY_SERVICE);
List<RunningAppProcessInfo> runningApps = am.getRunningAppProcesses();
if (runningApps == null) {
return null;
}
for (RunningAppProcessInfo procInfo : runningApps) {
if (procInfo.pid == pid) {
return procInfo.processName;
}
}
return null;
}
在 Application 的 onCreate 中获取进程名称并进行相应的判断,例如:
String processName = getProcessName(this, android.os.Process.myPid());
if (!TextUtils.isEmpty(processName) && processName.equals(this.getPackageName())) {//判断进程名,保证只有主进程运行
//主进程初始化逻辑
....
}
总结
知其然还需知其所以然,这才是总结并提高的法宝。希望能帮到有需要的同学 :)
Have a good day ~