动态加载:
加载已安装和未安装的apk
动态加载技术就是使用类加载器加载相应的apk、dex、jar(必须含有dex文件),再通过反射获得该apk、dex、jar内部的资源
(class、图片、color等等)进而供宿主app使用。
PathClassLoader 获取已安装的apk
linux user id宿主app和插件app的manifest上都定义一个相同的sharedUserId
PackageInfo.sharedUserId
获取手机内存在的所有插件,没有找到则提示去下载
/第一个参数为包含dex的apk或者jar的路径,第二个参数为父加载器
PathClassLoader pathClassLoader = new PathClassLoader(pluginContext.getPackageResourcePath
(),ClassLoader.getSystemClassLoader());
两种方式获取
1、Class<?> clazz = pathClassLoader.loadClass(packageName + ".R$mipmap");//通过使用自身的加载器反射出mipmap类进而使用
该类的功能
2、//参数:1、类的全名,2、是否初始化类,3、加载时使用的类加载器
Class<?> clazz = Class.forName(packageName + ".R$mipmap", true, pathClassLoader);
Field field = clazz.getDeclaredField("one");
int resourceId = field.getInt(R.mipmap.class);
创建插件的上下文
Context plugnContext = this.createPackageContext(packageName, CONTEXT_IGNORE_SECURITY | CONTEXT_INCLUDE_CODE);
DexClassLoader不需要提前安装apk
首先我们得到事先知道我们的插件apk存放在哪个目录下,然后分别得到插件apk的信息(名称、包名等),然后显示可用的插件,最
后动态加载apk获得资源
1、获取未安装的apk信息public PackageInfo getPackageArchiveInfo(String archiveFilePath, int flags)
private String[] getUninstallApkInfo(Context context, String archiveFilePath) {
String[] info = new String[2];
PackageManager pm = context.getPackageManager();
PackageInfo pkgInfo = pm.getPackageArchiveInfo(archiveFilePath, PackageManager.GET_ACTIVITIES);
if (pkgInfo != null) {
ApplicationInfo appInfo = pkgInfo.applicationInfo;
String versionName = pkgInfo.versionName;//版本号
Drawable icon = pm.getApplicationIcon(appInfo);//图标
String appName = pm.getApplicationLabel(appInfo).toString();//app名称
String pkgName = appInfo.packageName;//包名
info[0] = appName;
info[1] = pkgName;
}
return info;
2、得到对应未安装apk的Resource对象,我们需要通过反射来获得:反射调用addAssetPath,将未安装的Apk文件的添加进
AssetManager中,第二个参数为apk文件的路径带apk名
通过得到AssetManager中的内部的方法addAssetPath,将未安装的apk路径传入从而添加进assetManager中,然后通过new Resource把
assetManager传入构造方法中,进而得到未安装apk对应的Resource对象。
private Resources getPluginResources(String apkName) {
try {
AssetManager assetManager = AssetManager.class.newInstance();
Method addAssetPath = assetManager.getClass().getMethod("addAssetPath", String.class);//反射调用方法
addAssetPath(String path)
//第二个参数是apk的路径:Environment.getExternalStorageDirectory().getPath()+File.separator
+"plugin"+File.separator+"apkplugin.apk"
addAssetPath.invoke(assetManager, apkDir+File.separator+apkName);//将未安装的Apk文件的添加进AssetManager
中,第二个参数为apk文件的路径带apk名
Resources superRes = this.getResources();
Resources mResources = new Resources(assetManager, superRes.getDisplayMetrics(),
superRes.getConfiguration());
return mResources;
} catch (Exception e) {
e.printStackTrace();
}
return null;
3、加载未安装的apk获得它的内部资源
/**
* 加载apk获得内部资源
* @param apkDir apk目录
* @param apkName apk名字,带.apk
* @throws Exception
*/
private void dynamicLoadApk(String apkDir, String apkName, String apkPackageName) throws Exception {
File optimizedDirectoryFile = getDir("dex", Context.MODE_PRIVATE);//在应用安装目录下创建一个名为app_dex文件夹
目录,如果已经存在则不创建
Log.v("zxy", optimizedDirectoryFile.getPath().toString());// /data/data/com.example.dynamicloadapk/app_dex
//参数:1、包含dex的apk文件或jar文件的路径,2、apk、jar解压缩生成dex存储的目录,3、本地library库目录,一般为
null,4、父ClassLoader
DexClassLoader dexClassLoader = new DexClassLoader(apkDir+File.separator+apkName,
optimizedDirectoryFile.getPath(), null, ClassLoader.getSystemClassLoader());
Class<?> clazz = dexClassLoader.loadClass(apkPackageName + ".R$mipmap");//通过使用apk自己的类加载器,反射出R
类中相应的内部类进而获取我们需要的资源id
Field field = clazz.getDeclaredField("one");//得到名为one的这张图片字段
int resId = field.getInt(R.id.class);//得到图片id
Resources mResources = getPluginResources(apkName);//得到插件apk中的Resource
if (mResources != null) {
//通过插件apk中的Resource得到resId对应的资源
findViewById(R.id.background).setBackgroundDrawable(mResources.getDrawable(resId));
}
首先通过类加载器去加载apk中activity的类并创建一个新对象,然后通过反射去调用这个对象的setProxy方法和onCreate方法,
setProxy方法的作用是将activity内部的执行全部交由宿主程序中的proxy(也是一个activity),onCreate方法是activity的入口,
setProxy以后就调用onCreate方法,这个时候activity就被调起来了。