上篇 介绍了如何从dex中加载类,这篇尝试了一下从apk中加载资源,用的同样是DexClassLoader。同样还是那个kotlin项目,简单的尝试从另一个apk的drawable中加载一张图片,个人感觉还是挺麻烦的。
先准备另一个项目 新建另一个项目,这个项目只在drawable下放了一张名为ssm的图片。然后生成一个debug的apk包。将这个apk拷贝到本项目的assets下(只是为了方便,也可以从远程获取这个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 val optDir = getDir("plugin" , Context.MODE_PRIVATE) val desFile = File(optDir.path + File.separator + "plugin1.apk" ) println(desFile.path) println(desFile.absolutePath) if (!desFile.exists()) { desFile.createNewFile() copyFiles("plugin1.apk" , desFile) } val pkInfo = pm.getPackageArchiveInfo(desFile.path, PackageManager.GET_ACTIVITIES) val packageName = pkInfo.applicationInfo.packageName 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作为系统的组件,需要系统来初始化,来调用各个生命周期方法。后续就是就是要关注一下如何调用这些资源了。