原理
apk 中的資源放在 resources.arsc、assets 中,它們都要靠 AssetManager 來訪問,我們又通過 Resources 來調用 AssetManager。
要訪問外部 apk 中的資源,就要通過該 apk 的 resources.arsc,創建一個新的 AssetManager 和一個新的 Resources。
實例
下面的例子,會在宿主 apk 中訪問外部 apk 中的一個 String 資源。
在 demo 中加入一個 String 資源,打包 simple.apk。
<string name="hello_str">hello plugin</string>
通過 AS 查看 simple.apk,看到 R.string.hello_str 的值是 0x7f0b0028
。
代碼:
class ResourcesActivity : AppCompatActivity() {
companion object {
const val PLUGIN_APK_NAME = "simple.apk"
}
var pluginApkPath: String = ""
var mResources: Resources? = null
override fun attachBaseContext(newBase: Context?) {
super.attachBaseContext(newBase)
// 將 simple.apk 複製到 /data/data/packageName/files
val apkFile = newBase?.getFileStreamPath(PLUGIN_APK_NAME)
pluginApkPath = apkFile?.absolutePath ?: ""
FileUtil.copyFileFromAssets(newBase, PLUGIN_APK_NAME, apkFile, false)
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_resources)
// 創建新的 Resources 對象
loadResources()
// 7f0b0028,即 simple.apk 的 resources.arsc 中 R.string.hello_str 的 id 值
val s = mResources?.getString("7f0b0028".toInt(16))
tv_plugin_res_string.text = s
}
private fun loadResources() {
// 創建新的 AssetManager 對象
val mAssetManager = AssetManager::class.java.newInstance()
// 將 simple.apk 的路徑傳入 AssetManager
MethodUtils.invokeMethod(mAssetManager, "addAssetPath", pluginApkPath)
// 通過 AssetManager 創建 Resources
mResources = Resources(mAssetManager, resources.displayMetrics, resources.configuration)
}
}