主程序专门开一个PluginActivity,在该Activity里通过dexClassLoader动态加载apk,通过反射的方式向被加载apk的mainActivity里传递PluginAvtivity这个参数,相当于在主程序的PluginActivity里画Apk中Activity的View。
PluginActivity中的核心方法:loadApk
DexClassLoader这个类加载器用来从.jar和.apk类型的文件内部加载classes.dex文件。通过这种方式可以用来执行非安装的程序代码,作为程序的一部分进行运行。这个装载类需要一个程序私有的,可写的文件目录去存放优化后的classes文件。通过Contexct.getDir(String, int)来创建这个目录:File dexOutputDir = context.getDir("dex", 0);
public File getDir(String name, int mode):name目录名称、mode权限,如果传入的目录不存在,系统会创建此目录,路径为"/data/data/程序Package Name/app_name",name就是传入的name。
通过以下代码来获得DexClassLoader。dexPath是需要装载的APK的路径
DexClassLoader localDexClassLoader = new DexClassLoader(dexpath, dexOutputDir.getAbsolutePath(), null, ClassLoader.getSystemClassLoader().getParent());
通过以下代码获得应用信息
PackageInfo pkgInfo = getPackageManager().getPackageArchiveInfo(dexpath,
PackageManager.GET_ACTIVITIES);
if ((pkgInfo.activities != null) && (pkgInfo.activities.length > 0)) {
String activityname = pkgInfo.activities[0].name; //获得应用信息里的第一个Activity名
localClass = localDexClassLoader.loadClass(activityname);//通过反射加载类型
mActivityClass = localClass;
Constructor localConstructor = localClass.getConstructor(new Class[] {});//获得构造方法
instance = localConstructor.newInstance(new Object[] {}); //创建对象实例
mActivityInstance = instance;
Method localMethodSetActivity = localClass.getDeclaredMethod("setActivity", new Class[] { Activity.class });
localMethodSetActivity.setAccessible(true);
localMethodSetActivity.invoke(instance, new Object[] { this }); //相当于调用MainActivity里的setActivity方法,把PluginActivity传递了进去
此外,还需要获取未安装apk的资源——getApkResoures方法
反射调用android.content.res.AssetManager类,新建个实例,调用隐藏的方法addAssetPath(String path)将为安装APK文件的添加进去,然后用这个AssetManager来构建出一个Resource实例
try {
Class<?> class_AssetManager = Class.forName("android.content.res.AssetManager");
Object assetMag = class_AssetManager.newInstance();
Method method_addAssetPath = class_AssetManager
.getDeclaredMethod("addAssetPath", String.class);
String path = 路径;
String fileName = 文件名;
method_addAssetPath.invoke(assetMag, path + fileName);
Resources res = context.getResources();
Constructor<?> constructor_Resources = Resources.class
.getConstructor(class_AssetManager, res.getDisplayMetrics()
.getClass(), res.getConfiguration().getClass());
res = (Resources) constructor_Resources.newInstance(assetMag,
res.getDisplayMetrics(), res.getConfiguration());
} catch (Exception e) {
e.printStackTrace();
}
在MainActivity里设一个setResources方法,反射调用它,把 getApkResoures方法返回的Resource实例传递进去。
最后反射调用MainActivity的onCreate方法
Method methodonCreate = localClass.getDeclaredMethod("onCreate", new Class[] { Bundle.class });
methodonCreate.setAccessible(true);
methodonCreate.invoke(instance, savedInstanceState);
二,如何更换应用主题时同时同步到插件里去?
插件加载资源时先判断Activity的资源里有没有它,有就调用,没有再调用自己的Resource