我正在考虑从其他 APK 加载资源的某种方式。例如,我在 sdcard 上有一个 APK(它只是 APK,它没有安装在手机内存中),其中包含我要在我安装的应用程序中使用的资源。是否可以从存储在 SD 卡上的 APK 中的 res/x 加载资源到我安装的应用程序(例如布局、图像、字符串……)。
谢谢
最佳答案
在阅读和调试一些 AOSP 代码后,我终于设法动态加载资源。分步指南:
创建一个新的 Android 项目,我将其命名为“动态 APK 加载”,指定为
whatever.dynamicapkloading
应用 ID,并留下“应用”模块名称。创建另一个没有任何 Activity 的 Android 应用程序模块。我在同一个项目中做过这个,但这取决于你。我将模块称为“动态”,并将应用程序 ID 设置为
whatever.dynamic
.从“动态”项目中删除所有不必要的东西。我已经删除了启动器可绘制对象以及任何其他资源,还从 build.gradle 中删除了 AppCompat 依赖项。我的 list 内容看起来很简约,像这样:
<application />
.向“动态”项目添加一些代码。例如:
package whatever.dynamic; import android.content.Context; import android.widget.Toast; public final class Code implements Runnable { private final Context context; public Code(Context context) { this.context = context; } @Override public void run() { Toast.makeText(context, "Running dynamic code!", Toast.LENGTH_SHORT).show(); } }
向“动态”项目添加一些资源。我创建了
strings.xml
:<resources> <string name="someString">This is a dynamically loaded string.</string> <string name="anotherString">Lol! That\'s it.</string> </resources>
将其添加到运行配置。将启动选项/启动设置为无。构建 -> 构建 APK!在此步骤中,我得到了一个名为
dynamic-debug.apk
的 4.9 KB 文件。 .移动此文件,
dynamic/build/outputs/apk/dynamic-debug.apk
,进入我们的主要项目的 Assets ,我。 e.至app/src/main/assets/
.创建/打开一个将加载代码和资源的类。比如说,这将是 DynamicActivity。
编写代码,将 APK 从 Assets 复制到私有(private)应用程序目录。这很无聊而且琐碎,你知道的:
File targetApk = new File(getDir("dex", Context.MODE_PRIVATE), "app.apk"); // copy APK from assets to private directory // remove this condition in order to keep dynamic APK fresh if (!targetApk.exists() || !targetApk.isFile()) { try (BufferedInputStream bis = new BufferedInputStream( getAssets().open("dynamic-debug.apk")); OutputStream dexWriter = new BufferedOutputStream( new FileOutputStream(targetApk))) { byte[] buf = new byte[4096]; int len; while((len = bis.read(buf, 0, 4096)) > 0) { dexWriter.write(buf, 0, len); } } catch (IOException e) { throw new RuntimeException(e); } }
编写代码加载必要的类并实例化它。
PathClassLoader loader = new PathClassLoader( targetApk.getAbsolutePath(), getClassLoader()); Class<?> dynamicClass = loader.loadClass("whatever.dynamic.Code"); Constructor<?> ctor = dynamicClass.getConstructor(Context.class); dynamicInstance = (Runnable) ctor.newInstance(this);
编写将从指定 APK 加载资源的代码。这是一个艰难的!所有构造函数和方法都是公开的,但它们是隐藏的。我以反射(reflection)的方式编写了它,但要避免这种情况,您可以针对完整的框架 jar 编译代码或在 Smali 中编写部分代码。
AssetManager assets = AssetManager.class.newInstance(); Method addAssetPath = AssetManager.class .getMethod("addAssetPath", String.class); if (addAssetPath.invoke(assets, targetApk.getAbsolutePath()) == Integer.valueOf(0)) { throw new RuntimeException(); } Class<?> resourcesImpl = Class.forName("android.content.res.ResourcesImpl"); Class<?> daj = Class.forName("android.view.DisplayAdjustments"); Object impl = resourcesImpl .getConstructor(AssetManager.class, DisplayMetrics.class, Configuration.class, daj) .newInstance(assets, getResources().getDisplayMetrics(), getResources().getConfiguration(), daj.newInstance()); dynamicResources = Resources.class.getConstructor(ClassLoader.class) .newInstance(loader); Method setImpl = Resources.class.getMethod("setImpl", Class.forName("android.content.res.ResourcesImpl")); setImpl.invoke(dynamicResources, impl);
使用这些资源!有两种获取资源 ID 的方法。
int someStringId = dynamicResources.getIdentifier( "someString", "string", "whatever.dynamic"); String someString = dynamicResources.getString(someStringId); Class<?> rString = Class.forName("whatever.dynamic.R$string", true, loader); anotherStringId = rString.getField("anotherString").getInt(null); String anotherString = dynamicResources.getString(anotherStringId);
就是这样!这对我有用。 Full DynamicActivity.java code
在实际项目中,您必须在构建时对 APK 进行签名,并在加载时检查其签名。当然,永远不要将其存储在 SD 卡上,否则您的 APK 可能会被仿冒!
您还应该静态缓存资源 ID,但重新创建 Resources
并在配置更改时重新查询资源值。
有用的链接:
- My another answer about dynamic class loading
- Grab'n Run library ,旨在动态加载代码并执行一些安全检查。但是,我还没有亲自测试过。
- Android – hidden and internal API’s revisited ,一篇关于链接完整框架 jar 的文章
- Dynamically Retrieving Resources in Android — 这篇文章说获取资源 ID 的反射方式更快。我从未检查过。
关于android - 从其他 APK 加载动态资源,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/7483568/