8.最简单的插件化解决方案
- 插件化技术
1.合并所有插件的dex,解决插件类加载问题
2.预先在宿主中声明所有插件中得四大组件
3.把插件中的所有资源一次合并到宿主的资源中。
8.1 在宿主清单文件里声明插件中的组件
8.2hostapp 加载插件中的类
/**
* * 由于应用程序使用的ClassLoader为PathClassLoader
* 最终继承自 BaseDexClassLoader
* 查看源码得知,这个BaseDexClassLoader加载代码根据一个叫做
* dexElements的数组进行, 因此我们把包含代码的dex文件插入这个数组
* 系统的classLoader就能帮助我们找到这个类
*/
public static void patchClassLoader(ClassLoader cl, File apkFile, File optDexFile)
throws IllegalAccessException, NoSuchMethodException, IOException, InvocationTargetException, InstantiationException, NoSuchFieldException {
// 获取 BaseDexClassLoader : pathList
Object pathListObj = RefInvoke.getFieldObject(DexClassLoader.class.getSuperclass(), cl, "pathList");
// 获取 PathList: Element[] dexElements
Object[] dexElements = (Object[]) RefInvoke.getFieldObject(pathListObj, "dexElements");
// Element 类型
Class<?> elementClass = dexElements.getClass().getComponentType();
// 创建一个数组, 用来替换原始的数组
Object[] newElements = (Object[]) Array.newInstance(elementClass, dexElements.length + 1);
// 构造插件Element(File file, boolean isDirectory, File zip, DexFile dexFile) 这个构造函数
Class[] p1 = {File.class, boolean.class, File.class, DexFile.class};
Object[] v1 = {apkFile, false, apkFile, DexFile.loadDex(apkFile.getCanonicalPath(), optDexFile.getAbsolutePath(), 0)};
Object o = RefInvoke.createObject(elementClass, p1, v1);
Object[] toAddElementArray = new Object[] { o };
// 把原始的elements复制进去
System.arraycopy(dexElements, 0, newElements, 0, dexElements.length);
// 插件的那个element复制进去
System.arraycopy(toAddElementArray, 0, newElements, dexElements.length, toAddElementArray.length);
// 替换
RefInvoke.setFieldObject(pathListObj, "dexElements", newElements);
}
8.4加载插件中的资源
private static void reloadInstalledPluginResources(ArrayList<String> pluginPaths) {
try {
AssetManager assetManager = AssetManager.class.newInstance();
Method addAssetPath = AssetManager.class.getMethod("addAssetPath", String.class);
addAssetPath.invoke(assetManager, mBaseContext.getPackageResourcePath());
for(String pluginPath: pluginPaths) {
addAssetPath.invoke(assetManager, pluginPath);
}
Resources newResources = new Resources(assetManager,
mBaseContext.getResources().getDisplayMetrics(),
mBaseContext.getResources().getConfiguration());
// RefInvoke.setFieldObject(mBaseContext, "mResources", newResources);
//这是最主要的需要替换的,如果不支持插件运行时更新,只留这一个就可以了
RefInvoke.setFieldObject(mPackageInfo, "mResources", newResources);
//清除一下之前的resource的数据,释放一些内存
//因为这个resource有可能还被系统持有着,内存都没被释放
//clearResoucesDrawableCache(mNowResources);
mNowResources = newResources;
//需要清理mtheme对象,否则通过inflate方式加载资源会报错
//如果是activity动态加载插件,则需要把activity的mTheme对象也设置为null
// RefInvoke.setFieldObject(mBaseContext, "mTheme", null);
} catch (Throwable e) {
e.printStackTrace();
}
}
9.Activity的插件化解决方案
9.1启动没有在清单文件中声明的插件activity
hook1:ActivityManagerNative 的gDefault变量 是个单例对象。获取泛型对象
hook2:ActivityThread类中mH,handler中callback回调,如果是startActivity方法,把包装的Intent转换成真正要启动的Activity.
hook3:启动activity之前 AMS 会检查当前启动页面的信息,也就是LoadedApk对象
case LAUNCH_ACTIVITY: {
Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "activityStart");
final ActivityClientRecord r = (ActivityClientRecord) msg.obj;
r.packageInfo = getPackageInfoNoCheck(
r.activityInfo.applicationInfo, r.compatInfo);
handleLaunchActivity(r, null, "LAUNCH_ACTIVITY");
Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
} break;
private LoadedApk getPackageInfo(ApplicationInfo aInfo, CompatibilityInfo compatInfo,
ClassLoader baseLoader, boolean securityViolation, boolean includeCode,
boolean registerPackage) {
final boolean differentUser = (UserHandle.myUserId() != UserHandle.getUserId(aInfo.uid));
synchronized (mResourcesManager) {
WeakReference<LoadedApk> ref;
if (differentUser) {
// Caching not supported across users
ref = null;
} else if (includeCode) {
ref = mPackages.get(aInfo.packageName);
} else {
ref = mResourcePackages.get(aInfo.packageName);
}
LoadedApk packageInfo = ref != null ? ref.get() : null;
if (packageInfo == null || (packageInfo.mResources != null
&& !packageInfo.mResources.getAssets().isUpToDate())) {
if (localLOGV) Slog.v(TAG, (includeCode ? "Loading code package "
: "Loading resource-only package ") + aInfo.packageName
+ " (in " + (mBoundApplication != null
? mBoundApplication.processName : null)
+ ")");
packageInfo =
new LoadedApk(this, aInfo, compatInfo, baseLoader,
securityViolation, includeCode &&
(aInfo.flags&ApplicationInfo.FLAG_HAS_CODE) != 0, registerPackage);
if (mSystemThread && "android".equals(aInfo.packageName)) {
packageInfo.installSystemApplicationInfo(aInfo,
getSystemContext().mPackageInfo.getClassLoader());
}
if (differentUser) {
// Caching not supported across users
} else if (includeCode) {
mPackages.put(aInfo.packageName,
new WeakReference<LoadedApk>(packageInfo));
} else {
mResourcePackages.put(aInfo.packageName,
new WeakReference<LoadedApk>(packageInfo));
}
}
return packageInfo;
}
}
9.2 故意命中缓存
public class LoadedApkClassLoaderHookHelper {
public static Map<String, Object> sLoadedApk = new HashMap<String, Object>();
public static void hookLoadedApkInActivityThread(File apkFile) throws ClassNotFoundException,
NoSuchMethodException, InvocationTargetException, IllegalAccessException, NoSuchFieldException, InstantiationException {
// 先获取到当前的ActivityThread对象
Object currentActivityThread = RefInvoke.invokeStaticMethod("android.app.ActivityThread", "currentActivityThread");
// 获取到 mPackages 这个静态成员变量, 这里缓存了dex包的信息
Map mPackages = (Map) RefInvoke.getFieldObject(currentActivityThread, "mPackages");
//准备两个参数
// android.content.res.CompatibilityInfo
Object defaultCompatibilityInfo = RefInvoke.getStaticFieldObject("android.content.res.CompatibilityInfo", "DEFAULT_COMPATIBILITY_INFO");
//从apk中取得ApplicationInfo信息
ApplicationInfo applicationInfo = generateApplicationInfo(apkFile);
//调用ActivityThread的getPackageInfoNoCheck方法loadedApk,得到,上面两个数据都是用来做参数的
Class[] p1 = {ApplicationInfo.class, Class.forName("android.content.res.CompatibilityInfo")};
Object[] v1 = {applicationInfo, defaultCompatibilityInfo};
Object loadedApk = RefInvoke.invokeInstanceMethod(currentActivityThread, "getPackageInfoNoCheck", p1, v1);
//为插件造一个新的ClassLoader
String odexPath = Utils.getPluginOptDexDir(applicationInfo.packageName).getPath();
String libDir = Utils.getPluginLibDir(applicationInfo.packageName).getPath();
ClassLoader classLoader = new CustomClassLoader(apkFile.getPath(), odexPath, libDir, ClassLoader.getSystemClassLoader());
RefInvoke.setFieldObject(loadedApk, "mClassLoader", classLoader);
//把插件LoadedApk对象放入缓存
WeakReference weakReference = new WeakReference(loadedApk);
mPackages.put(applicationInfo.packageName, weakReference);
// 由于是弱引用, 因此我们必须在某个地方存一份, 不然容易被GC; 那么就前功尽弃了.
sLoadedApk.put(applicationInfo.packageName, loadedApk);
}
/**
* 这个方法的最终目的是调用
* android.content.pm.PackageParser#generateActivityInfo(android.content.pm.PackageParser.Activity, int, android.content.pm.PackageUserState, int)
*/
public static ApplicationInfo generateApplicationInfo(File apkFile)
throws ClassNotFoundException, NoSuchMethodException, IllegalAccessException, InstantiationException, InvocationTargetException, NoSuchFieldException {
// 找出需要反射的核心类: android.content.pm.PackageParser
Class<?> packageParserClass = Class.forName("android.content.pm.PackageParser");
Class<?> packageParser$PackageClass = Class.forName("android.content.pm.PackageParser$Package");
Class<?> packageUserStateClass = Class.forName("android.content.pm.PackageUserState");
// 我们的终极目标: android.content.pm.PackageParser#generateApplicationInfo(android.content.pm.PackageParser.Package,
// int, android.content.pm.PackageUserState)
// 要调用这个方法, 需要做很多准备工作; 考验反射技术的时候到了 - -!
// 下面, 我们开始这场Hack之旅吧!
// 首先拿到我们得终极目标: generateApplicationInfo方法
// API 23 !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
// public static ApplicationInfo generateApplicationInfo(Package p, int flags,
// PackageUserState state) {
// 其他Android版本不保证也是如此.
// 首先, 我们得创建出一个Package对象出来供这个方法调用
// 而这个需要得对象可以通过 android.content.pm.PackageParser#parsePackage 这个方法返回得 Package对象得字段获取得到
// 创建出一个PackageParser对象供使用
Object packageParser = packageParserClass.newInstance();
// 调用 PackageParser.parsePackage 解析apk的信息
// 实际上是一个 android.content.pm.PackageParser.Package 对象
Class[] p1 = {File.class, int.class};
Object[] v1 = {apkFile, 0};
Object packageObj = RefInvoke.invokeInstanceMethod(packageParser, "parsePackage", p1, v1);
// 第三个参数 mDefaultPackageUserState 我们直接使用默认构造函数构造一个出来即可
Object defaultPackageUserState = packageUserStateClass.newInstance();
// 万事具备!!!!!!!!!!!!!!
Class[] p2 = {packageParser$PackageClass, int.class, packageUserStateClass};
Object[] v2 = {packageObj, 0, defaultPackageUserState};
ApplicationInfo applicationInfo = (ApplicationInfo)RefInvoke.invokeInstanceMethod(packageParser, "generateApplicationInfo", p2, v2);
String apkPath = apkFile.getPath();
applicationInfo.sourceDir = apkPath;
applicationInfo.publicSourceDir = apkPath;
return applicationInfo;
}
}