LJ的Blog

学海无涯苦做舟

0%

Android加载未安装apk中的资源

上篇介绍了如何从dex中加载类,这篇尝试了一下从apk中加载资源,用的同样是DexClassLoader。同样还是那个kotlin项目,简单的尝试从另一个apk的drawable中加载一张图片,个人感觉还是挺麻烦的。

先准备另一个项目

新建另一个项目,这个项目只在drawable下放了一张名为ssm的图片。然后生成一个debug的apk包。将这个apk拷贝到本项目的assets下(只是为了方便,也可以从远程获取这个apk)。
debug apk

我们的目标就是通过这个debug的apk来加载这张图片,这里我将这个apk的名字改为了plugin1.apk。

简单的代码

这里不得不说一下,kotlin对java的兼容做的真的不错,这里的反射本来还有点担心该怎么做,后来发现比较容易的就实现了。只不过语法上有一些小小的不同。在Java中通过类名.class就可以访问该类的Class对象,而在Kotlin中则需要类名::class.java,可以访问到java的Class对象。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
fun dynamicLoadApk() {
val pm = packageManager
// 在应用安装目录下创建一个名为plugin的文件夹目录
// 我进了程序目录看了一下,叫app_plugin
val optDir = getDir("plugin", Context.MODE_PRIVATE)
// 生成输出文件
val desFile = File(optDir.path + File.separator + "plugin1.apk")
println(desFile.path)
println(desFile.absolutePath)
// 如果不存在,将assets下的plugin1.apk复制到输出文件中
if (!desFile.exists()) {
desFile.createNewFile()
copyFiles("plugin1.apk", desFile)
}

// plugin1.apk 获取包名
val pkInfo = pm.getPackageArchiveInfo(desFile.path,
PackageManager.GET_ACTIVITIES)
val packageName = pkInfo.applicationInfo.packageName

// 访问AssetManager的Class对象,生成AssetManager实例对象
val assetManager = AssetManager::class.java.newInstance()
// 通过反射拿到隐藏方法
val addAssetPath = assetManager.javaClass.getMethod("addAssetPath", String::class.java)

val loader = DexClassLoader(optDir.path + File.separator +
"plugin1.apk", optDir.path, null,
ClassLoader.getSystemClassLoader())


addAssetPath.invoke(assetManager, desFile.path)
val superRes = resources
val mResources = Resources(assetManager, superRes.displayMetrics, superRes.configuration)

val clz = loader.loadClass("$packageName.R\$drawable")
val field = clz.getDeclaredField("ssm")
val resId = field.getInt(R.id::class)
println("resId: $resId")
val iv_img = findViewById(R.id.iv_img) as ImageView
iv_img.setImageDrawable(mResources.getDrawable(resId))

}

效果图:

少司命

的确可以加载成功。但是到这,这两篇只能算是hello world,比起普通的资源加载,更令人向往的是启动各个未安装的apk中的Activity和各种Service。不过比起到现在为止的直接反射暴力新建对象,Activity作为系统的组件,需要系统来初始化,来调用各个生命周期方法。后续就是就是要关注一下如何调用这些资源了。