(本文来自于和博客上一个朋友的聊天,但可惜我回复后一直没收到这位朋友的回答。故在此把这个问题和大家介绍下,希望能抛砖引玉)
这位朋友的问题是这样的:
应用程序A运行时跑在进程A中,它可以在运行时通过ClassLoader加载另外一个应用程序B。
当然,应用程序B也是可以运行的,它运行在进程B中。
在Android 5.0以前,进程B和进程A是没有关系。但是5.0以后,如果进程B被stop的话,进程A一样会被干掉。
这就是Android 5.0带来的进程A/B依赖关系。
这个问题有几个关键技术点:
1 进程A如何加载应用程序B。这是通过ClassLoader来完成的,主要代码如下所示:
(来自这位朋友的提示:)
private static Class<?> loadPluginClass(Context launcherContext, String packageName, String className, int type) { try { Context context = launcherContext.createPackageContext(packageName, Context.CONTEXT_IGNORE_SECURITY | Context.CONTEXT_INCLUDE_CODE); final String apkPath = context.getApplicationInfo().sourceDir; PathClassLoader l = new PathClassLoader(apkPath, context.getClassLoader()); return l.loadClass(className); } ...... }
在load的时候:
1 在Android 5.0以前,进程A和应用程序B的关系:没有显示的关系。就是普通的加载
2 在Android 5.0及以后,进程A和应用程序B的关系:显示的绑定。代码在LoadedApk.java中
-->每一个被加载的Apk都对应一个LoaderApk对象。代码在LoadedApk.java中
public LoadedApk(ActivityThread activityThread, ApplicationInfo aInfo,
CompatibilityInfo compatInfo, ClassLoader baseLoader,
boolean securityViolation, boolean includeCode, boolean registerPackage) {
final int myUid = Process.myUid();
aInfo = adjustNativeLibraryPaths(aInfo);
......
mRegisterPackage = registerPackage; // 在这里被“绑定”了
mDisplayAdjustments.setCompatibilityInfo(compatInfo);
}
Android 5.0之后为什么会这样呢?进程A和APK B这种依赖关系有什么用呢?
原来,Android 5.0之后:
1 当进程A加载一个package的时候,framework将调用ActivityManagerService的addPackageDependency
这个函数将把进程A和APK B(也就是Package B)绑定到一起去
这个函数在哪里调用的呢?也在LoadedApk.java中:
public ClassLoader getClassLoader() { synchronized (this) { if (mClassLoader != null) { return mClassLoader; } if (mIncludeCode && !mPackageName.equals("android")) { ...... if (mRegisterPackage) { try { ActivityManagerNative.getDefault().addPackageDependency(mPackageName); } catch (RemoteException e) { } }
2 进程B(也就是Package B本身运行时所在的进程)被杀时,有依赖关系的进程A也会被干掉。这里有几个注意点:
2.1 如果进程B是自己crash或者被shell kill掉,那么依赖关系不会影响进程A
2.2 如果进程B是被调用killBackgroundProcess或者forceStopPackage的话,由于ActivityManagerService真正调用的是killPackageProcessesLocked
那么依赖关系会导致A被干掉。 从设计角度来看,这本身也是对的,因为APK B运行在自己的进程B,同时也被加载到进程A去运行。
kill package B的时候就应该stop进程B和A。
当然,由于5.0之前google没有考虑这么细,所以没有处理这个问题。
当然,这种依赖关系的引入还有一个原因是Android 5.0在应用程序安装方面的一些新的特性:
1 以前的apk文件 side load到/daa/app等监控目录下不会导致PacakgeManagerService去安装它们。而是需要等到下次重启扫描后,系统才会扫描并安装它们
2 adb install安装的APK,在/data/app目录下会创建一个Package-name(比如com.google.xxx)的文件夹,而apk文件被放到这个package-name目录下,改名叫base.apk
这么搞的目的是因为:Android终于支持一个进程可以加载多个APK了(当然以前也可以,但现在安装的时候,base-apk是主要逻辑,以后升级了,或者添加新的功能,就不需要重新安装新的base,而是安装一个额外的新apk,这个新apk会被加载到base的那个进程里。)这个功能在SDK文档中已经有展示,但是内容不是很详细
总之,base apk和新apk需要package名, 签名都一致才可以。