在没有安装apk的情况下提供资源给宿主app使用
使用DexClassLoader这个类来加载未安装的apk,提供资源供宿主app使用
首先的思路肯定也是下载到我们手机的某个目录里。然后分别得到插件apk的信息(名称、包名等),然后显示可用的插件,最后动态加载apk获得资源
然后要想到的问题就是要怎么获取到apk的包信息,因为现在要的是获取未安装apk的资源,无法通过createPackageContext(…);方法来构建出一个context,所以这时只有在Resource上下功夫
1,在PackageManager类中有这样的一个方法getPackageArchiveInfo(),是用来获取未安装apk的信息
public PackageInfo getPackageArchiveInfo(String archiveFilePath, int flags)
这个方法的参数刚好是传入一个FilePath,然后返回apk文件的PackageInfo信息:
/**
* 获取未安装apk的信息
* @param context
* @param archiveFilePath apk文件的path
* @return
*/
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对象,我们需要通过反射来获得:
/**
* @param apkName
* @return 得到对应插件的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;
}
通过得到AssetManager中的内部的方法addAssetPath,将未安装的apk路径传入从而添加进assetManager中,然后通过new Resource把assetManager传入构造方法中,进而得到未安装apk对应的Resource对象。
解决了上面的两个问题,接下来就是加载未安装的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));
}
}
其中通过new DexClassLoader()来创建未安装apk的类加载器,我们来看看它的参数:
public DexClassLoader(String dexPath, String optimizedDirectory, String libraryPath, ClassLoader parent)
dexPath就是apk文件的路径,
optimizedDirectory就是apk解压缩后的存放dex的目录,要注意的是,在4.1以后该目录不允许在sd卡上,所以我们用getDir()方法在应用内部创建一个dexOutputDir。
libraryPath为本地的library,一般为null。 parent为 父加载器
后面就是用反射来获取出需要的资源。
下面是demo演示的效果,我是把三个apk插件先放在assets目录下,然后copy到sd上来模仿下载过程,然后加载出相应插件的资源:
copyApkFile("apkthemeplugin-1.apk");
copyApkFile("apkthemeplugin-2.apk");
copyApkFile("apkthemeplugin-3.apk");
显示前
这里写图片描述
显示后
这里写图片描述
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。