// 实际上是一个 android.content.pm.PackageParser.Package 对象
Object packageObj = parsePackageMethod.invoke(packageParser, apkFile, 0);
这样,我们就得到了generateApplicationInfo的第一个参数;第二个参数是解析包使用的flag,我们直接选择解析全部信息,也就是0;
构建PackageUserState
第三个参数是PackageUserState,代表不同用户中包的信息。由于Android是一个多任务多用户系统,因此不同的用户同一个包可能有不同的状态;这里我们只需要获取包的信息,因此直接使用默认的即可.
至此,generateApplicaionInfo的参数我们已经全部构造完成,直接调用此方法即可得到我们需要的applicationInfo对象;在返回之前我们需要做一点小小的修改:使用系统的这个方法解析得到的ApplicationInfo对象中并没有apk文件本身的信息,所以我们把解析的apk文件的路径设置一下(ClassLoader依赖dex文件以及apk的路径):
// 第三个参数 mDefaultPackageUserState 我们直接使用默认构造函数构造一个出来即可
Object defaultPackageUserState = packageUserStateClass.newInstance();
ApplicationInfo applicationInfo = (ApplicationInfo) generateApplicationInfoMethod.invoke(packageParser,
packageObj, 0, defaultPackageUserState);
String apkPath = apkFile.getPath();
applicationInfo.sourceDir = apkPath;
applicationInfo.publicSourceDir = apkPath;
替换ClassLoader
获取LoadedApk信息
我们最终的目的是调用getPackageInfoNoCheck得到LoadedApk的信息,并替换其中的mClassLoader然后把把添加到ActivityThread的mPackages缓存中;从而达到我们使用自己的ClassLoader加载插件中的类的目的。
现在我们已经拿到了getPackageInfoNoCheck这个方法中至关重要的第一个参数applicationInfo;上文提到第二个参数CompatibilityInfo代表设备兼容性信息,直接使用默认的值即可;因此,两个参数都已经构造出来,我们可以调用getPackageInfoNoCheck获取LoadedApk:
// android.content.res.CompatibilityInfo
Class<?> compatibilityInfoClass = Class.forName(“android.content.res.CompatibilityInfo”);
Method getPackageInfoNoCheckMethod = activityThreadClass.getDeclaredMethod(“getPackageInfoNoCheck”, ApplicationInfo.class, compatibilityInfoClass);
Field defaultCompatibilityInfoField = compatibilityInfoClass.getDeclaredField(“DEFAULT_COMPATIBILITY_INFO”);
defaultCompatibilityInfoField.setAccessible(true);
Object defaultCompatibilityInfo = defaultCompatibilityInfoField.get(null);
ApplicationInfo applicationInfo = generateApplicationInfo(apkFile);
Object loadedApk = getPackageInfoNoCheckMethod.invoke(currentActivityThread, applicationInfo, defaultCompatibilityInfo);
我们成功地构造出了LoadedAPK, 接下来我们需要替换其中的ClassLoader,然后把它添加进ActivityThread的mPackages中:
String odexPath = Utils.getPluginOptDexDir(applicationInfo.packageName).getPath();
String libDir = Utils.getPluginLibDir(applicationInfo.packageName).getPath();
ClassLoader classLoader = new CustomClassLoader(apkFile.getPath(), odexPath, libDir, ClassLoader.getSystemClassLoader());
Field mClassLoaderField = loadedApk.getClass().getDeclaredField(“mClassLoader”);
mClassLoaderField.setAccessible(true);
mClassLoaderField.set(loadedApk, classLoader);
// 由于是弱引用, 因此我们必须在某个地方存一份, 不然容易被GC;
sLoadedApk.put(applicationInfo.packageName, loadedApk);
WeakReference weakReference = new WeakReference(loadedApk);
mPackages.put(applicationInfo.packageName, weakReference);
我们的这个CustomC