第一步、先制做一个有我们需要的图片资源的APK
如下图,这里有个about_log.png,我们需要生成apk文件。
生成的apk文件如果你不到项目的文件夹里面去取apk,想通过命令放到手机里面去可以快速用下面命令
1)、在手机里面通过包名找到apk路径,一定不要忘记有 -f
adb shell pm list package -f | grep com.example.testclassloader
得到如下结果
package:/data/app/com.example.testclassloader-2/base.apk=com.example.testclassloader
2)、把base.apk拉到本地然后改名字,命令如下
adb shell pull /data/app/com.example.testclassloader-2/base.apk testClassLoader.apk
3)、把testClassLoader.apk放到手机里面去,命令如下
adb shell push testClassLoader.apk /sdcard/
4)、去手机文件管理器里面找看是否有testClassLoader.apk文件
第二步、获取为安装apk包名的信息(假设前提不知道)
我们可以通过这个方法得到
public PackageInfo getPackageArchiveInfo(String archiveFilePath, int flags)
具体方法如下
/** * 获取未安装apk的信息 * @param context * @param apkPath apk文件的path * @return */ private Map<String,String> getUninstallApkInfo(Context context, String apkPath) { Map hashMap = new HashMap<String,String>(); PackageManager pm = context.getPackageManager(); PackageInfo pkgInfo = pm.getPackageArchiveInfo(apkPath, PackageManager.GET_ACTIVITIES); if (null != pkgInfo) { ApplicationInfo appInfo = pkgInfo.applicationInfo; String pkgName = appInfo.packageName;//包名 hashMap.put(PKG_NAME, pkgName); } else { Log.d(TAG, "program don't get apk package information"); } return hashMap; }
第三步、获取未安装apk(插件)的Resource
因为没有安装,所以不能得到context,所以我们需要未安装apk的Resource,我们可以通过反射来获取,代码如下
/** * @param apkPath * @return 得到对应插件的Resource对象 */ private Resources getPluginResources(String apkPath) { try { AssetManager assetManager = AssetManager.class.newInstance(); //反射调用方法addAssetPath(String path) Method addAssetPath = assetManager.getClass().getMethod(ADDSSETPATH, String.class); //将未安装的Apk文件的添加进AssetManager中,第二个参数是apk的路径 addAssetPath.invoke(assetManager, apkPath); Resources superRes = this.getResources(); Resources mResources = new Resources(assetManager, superRes.getDisplayMetrics(), superRes.getConfiguration()); return mResources; } catch (Exception e) { e.printStackTrace(); } return null; }
第四步、用DexClassLoader加载apk资源文件替换背景
如果你多DexClassLoader用法和原理不熟悉,可以参考我之前的博客
Android插件化开发之DexClassLoader动态加载dex、jar小Demo http://blog.csdn.net/u011068702/article/details/53263442
Android插件化开发之动态加载基础之ClassLoader工作机制 http://blog.csdn.net/u011068702/article/details/53248960
代码如下:
/** * 加载apk获得内部资源,并且替换背景 * @param apkDir apk目录 * @param apkName apk名字,带.apk * @throws Exception */ private void dynamicLoadApk(String apkPath, String apkPackageName) throws Exception { //在应用安装目录下创建一个名为app_dex文件夹目录,如果已经存在则不创建,这个目录主要是最优化目录,用于缓存dex文件 File optimizedDirectoryFile = getDir(DEX, Context.MODE_PRIVATE); //打印路径 理论上是/data/data/package/app_dex Log.v(TAG, optimizedDirectoryFile.getPath().toString()); //构建DexClassLoader DexClassLoader dexClassLoader = new DexClassLoader(apkPath, optimizedDirectoryFile.getPath(), null, ClassLoader.getSystemClassLoader()); //通过使用apk自己的类加载器,反射出R类中相应的内部类进而获取我们需要的资源id Class<?> clazz = dexClassLoader.loadClass(apkPackageName + DRAWABLE); //得到名为about_log的这张图片字段,这个图片是为安装apk里面的图片 Field field = clazz.getDeclaredField(IMAGE_ID); //得到图片id int resId = field.getInt(R.id.class); //得到插件apk中的Resource Resources mResources = getPluginResources(apkPath); if (mResources != null) { //通过插件apk中的Resource得到resId对应的资源 Drawable btnDrawable = mResources.getDrawable(resId); mLayout.setBackgroundDrawable(btnDrawable); } else { Log.d(TAG, "mResources is null"); } }