android - 从其他 APK 加载动态资源

标签 android resources

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

谢谢

最佳答案

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

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

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

  3. 从“动态”项目中删除所有不必要的东西。我已经删除了启动器可绘制对象以及任何其他资源,还从 build.gradle 中删除了 AppCompat 依赖项。我的 list 内容看起来很简约,像这样:<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 ,进入我们的主要项目的 Assets ,我。 e.至 app/src/main/assets/ .

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

  9. 编写代码,将 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);
        }
    }
    
  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 加载资源的代码。这是一个艰难的!所有构造函数和方法都是公开的,但它们是隐藏的。我以反射(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);
    
  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 进行签名,并在加载时检查其签名。当然,永远不要将其存储在 SD 卡上,否则您的 APK 可能会被仿冒!

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

有用的链接:

关于android - 从其他 APK 加载动态资源,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/7483568/

相关文章:

android - Retrofit 返回空数组列表,但给出了有效的 POJO 和 Json 来处理(尝试了其他问题,但对它们起作用的答案不在 mne 上)

php - 将我的 Controller 移动到 laravel 5 中的子文件夹后,获取 Controller 不存在

php - 在android中使用okhttp将数组或jsonobject传递给php url?

Android Jenkins Ant 构建失败

android - 检查/比较图像按钮资源

laravel - 在 Laravel 中创建资源路由,无需特定方法

java - 独立 Java 程序中的注释基础配置

c++ - 无论如何,我可以在不使用资源的情况下将标识符代码分配给 Win32 控件吗?

Android 缩进和悬挂缩进

android - Bottom Sheet 未展开或折叠