【问题标题】:Dynamic resource loading from other APK从其他 APK 动态加载资源
【发布时间】:2017-05-24 18:44:09
【问题描述】:

我正在考虑以某种方式从其他 APK 加载资源。例如,我在 sdcard 上有一个 APK(它只是 APK,它没有安装在手机内存中),其中包含我想在我安装的应用程序中使用的资源。是否可以从存储在 sdcard 上的 APK 中的 res/x 加载资源到我安装的应用程序(例如布局、图像、字符串等)。

谢谢

【问题讨论】:

  • 看来没人能解决。我个人认为这是不可能的,但你永远不知道......
  • 有可能但不起作用:ID 无论如何都会冲突。

标签: android resources


【解决方案1】:

在阅读和调试了一些 AOSP 代码之后,我终于设法动态加载资源。分步指南:

  1. 创建一个新的 Android 项目,我称之为“动态 APK 加载”,指定 whatever.dynamicapkloading 应用 ID,并留下“应用”模块名称。

  2. 创建另一个没有任何活动的 Android 应用程序模块。我在同一个项目中做过这个,但这取决于你。我将模块称为“动态”,并将应用程序 ID 设置为 whatever.dynamic

  3. 从“动态”项目中删除所有不必要的东西。我已经删除了启动器可绘制对象以及任何其他资源,并且还从 build.gradle 中删除了 AppCompat 依赖项。我的清单内容看起来很简约,如下所示:<application />

  4. 向“动态”项目添加一些代码。例如:

    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();
        }
    }
    
  5. 向“动态”项目添加一些资源。我创建了strings.xml:

    <resources>
        <string name="someString">This is a dynamically loaded string.</string>
        <string name="anotherString">Lol! That\'s it.</string>
    </resources>
    
  6. 将其添加到运行配置。将启动选项/启动设置为无。构建 -> 构建 APK!在这一步中,我有一个名为 dynamic-debug.apk 的 4.9 KB 文件。

  7. 将此文件 dynamic/build/outputs/apk/dynamic-debug.apk 移动到我们主要项目的资产中,即。 e.到app/src/main/assets/

  8. 创建/打开一个将加载代码和资源的类。比如说,这将是 DynamicActivity。

  9. 编写代码,将 APK 从资产复制到私有应用目录。这很无聊和琐碎,你知道的:

    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);
        }
    }
    
  10. 编写代码来加载必要的类并实例化它。

    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);
    
  11. 编写将从指定 APK 加载资源的代码。这是一个艰难的!所有构造函数和方法都是公共的,但它们是隐藏的。我以反思的方式编写了它,但为避免这种情况,您可以针对完整的框架 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);
    
  12. 使用这些资源!有两种获取资源 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,并在加载时检查其签名。当然,永远不要将它存储在 sdcard 上,否则您的 APK 可能会被欺骗!

您还应该静态缓存资源 ID,但在配置发生更改时重新创建 Resources 并重新查询资源值。

有用的链接:

【讨论】:

    【解决方案2】:

    您可以使用 PackageManager.getResourcesForApplication 访问在不同 apk 中找到的资源。

    【讨论】:

    • 看起来确实必须安装该 apk。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2014-06-21
    • 1970-01-01
    • 1970-01-01
    • 2012-07-04
    • 1970-01-01
    相关资源
    最近更新 更多