加载已安装应用、未安装 apk 中的资源
加载已安装应用、未安装 apk 中的资源,其思路主要是获取到对应的 ClassLoader/Context,通过 ClassLoader 加载 R.java 等类,再通过反射获取对应的资源 id 及资源。
加载已安装应用资源
sharedUserId
在当前应用中加载已安装的其他应用资源,需要二者有相同的sharedUserId
,这样 Android 系统为二者分配同一个 Linux 用户 ID,两个 App 可以相互访问代码、资源等。
通过 Shared User id,拥有同一个 User id 的多个 APK 可以配置成运行在同一个进程中。所以默认就是可以互相访问任意数据。也可以配置成运行成不同的进程,同时可以访问其他 APK 的数据目录下的数据库和文件。就像访问本程序的数据一样。
具体设置方法如下
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="cf.android666.dynamicloadapk"
android:sharedUserId="cf.android666.dynamic">
</manifest>
筛选所有已安装应用信息
private var packageBeanList: ArrayList<PackageInfoBean> = arrayListOf()
private var packageInfoList: ArrayList<PackageInfo> = arrayListOf()
var packageInfoList = packageManager.getInstalledPackages(PackageManager.GET_UNINSTALLED_PACKAGES) as ArrayList<PackageInfo>
if (packageInfoList.isNotEmpty()) {
for (x in packageInfoList) {
if (x.sharedUserId != null
&& x.sharedUserId.equals(sharedUid)
&& !x.packageName.equals(packageName)) {
//sharedUserId 与当前 App 相同,且 packageName 和当前 App 不同的 App 信息,即插件 App
packageBeanList.add(PackageInfoBean(packageManager
.getApplicationLabel(x.applicationInfo).toString(), x.packageName))
}
}
}
生成插件 App 的 Context
activity.createPackageContext("cf.android666.pluginapp",
Context.CONTEXT_INCLUDE_CODE | Context.CONTEXT_IGNORE_SECURITY)
通过 Context 反射获取插件 App 中的资源
//获取 ClassLoader
var pClassLoader = PathClassLoader(pluginContext.packageResourcePath
, ClassLoader.getSystemClassLoader())
//反射获取该类及其资源
var clazz = pluginContext.classLoader
.loadClass(pluginContext.packageName + ".R\$mipmap")
var abc = clazz.getField(s)
var id = abc.getInt(R.mipmap::class.java)
//调用插件 App 的 Context 获取其资源
var bg = pluginContext.resources.getDrawable(id)
加载未安装 Apk 内资源
获取 apk 信息
val sdPath = Environment.getExternalStorageDirectory().absolutePath
val apkPath = "$sdPath/plugin/plugin.apk"
var info = packageManager.getPackageArchiveInfo(apkPath, PackageManager.GET_ACTIVITIES)//获取未安装 apk 的 packageInfo
获取 ClassLoader
var file = getDir("dex", Context.MODE_PRIVATE)
var dexClassLoader = DexClassLoader(apkPath, file.absolutePath, null, ClassLoader.getSystemClassLoader())
getDir() 调用了 Context 的 getDir()
Retrieve, creating if needed, a new directory in which the application can place its own custom data files. You can use the returned File object to create and access files in this directory. Note that files created through a File object will only be accessible by your own application; you can only set the mode of the entire directory, not of individual files.
通过反射加载类,获取资源
var drawableClazz = dexClassLoader.loadClass("cf.android666.pluginapp.R\$drawable")
var onePng = drawableClazz.getDeclaredField("abc")
var onId = onePng.getInt(R.id::class.java)//反射获取资源 id
var resources = getUninstallApkResource()//resource 也是通过反射获取到
var drawable = resources.getDrawable(onId)
AssetManager.addAssetPath()
方法是用来将 apk 等中的资源添加到AssetManager
中,再通过其获取到Resources对象
,这样就获取到未安装 apk 中的资源了。
fun getUninstallApkResource(): Resources {
var assetManager = AssetManager::class.java.newInstance()
var addAssetPath = assetManager.javaClass.getMethod("addAssetPath",String::class.java)
addAssetPath.invoke(assetManager, apkPath)//设置了 apkPath
return Resources(assetManager, resources.displayMetrics, resources.configuration)
}
参考资源
Android 之 Android apk 动态加载机制的研究(二):资源加载和 activity 生命周期管理 - lee0oo0 - 博客园