从其他APK动态加载资源

发布于 2024-12-05 06:20:41 字数 146 浏览 1 评论 0原文

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

谢谢

I am thinking about some way of loading resources from other APKs. For example, I have an APK (it is just APK, it is not installed in phone memory) on sdcard, which contains resources I want to use in my installed application. Is it possible to load resources from res/x from APK stored on the sdcard to my installed application (e.g. layout, images, strings, ...).

Thanks

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(2

抚笙 2024-12-12 06:20:41

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

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

  2. 创建另一个没有任何 Activity 的 Android 应用程序模块。我已经在同一个项目中完成了此操作,但这取决于您。我将模块命名为“dynamic”,并将应用程序 ID 设置为 whatever.dynamic

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

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

    打包whatever.dynamic;
    
    导入 android.content.Context;
    导入 android.widget.Toast;
    
    公共最终类代码实现 Runnable {
    
        私有最终 Context 上下文;
    
        公共代码(上下文上下文){
            this.context = 上下文;
        }
    
        @覆盖
        公共无效运行(){
            Toast.makeText(context, "运行动态代码!",
                    Toast.LENGTH_SHORT).show();
        }
    }
    
  5. 向“动态”项目添加一些资源。我创建了 strings.xml

    <资源>
        这是一个动态加载的字符串。
        哈哈!就是这样。
    
    
  6. 将其添加到运行配置中。将启动选项/启动设置为无。构建->构建APK!在此步骤中,我获得了名为 dynamic-debug.apk 的 4.9 KB 文件。

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

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

  9. 编写代码,将 APK 从资产复制到私有应用程序目录。这很无聊&微不足道,你知道:

    File targetApk = new File(getDir("dex", Context.MODE_PRIVATE), "app.apk");
    // 将 APK 从 asset 复制到私有目录
    // 删除此条件以保持动态 APK 新鲜
    if (!targetApk.exists() || !targetApk.isFile()) {
        尝试(BufferedInputStream之二=新的BufferedInputStream(
                getAssets().open("dynamic-debug.apk"));
             输出流 dexWriter = 新的 BufferedOutputStream(
                    新的 FileOutputStream(targetApk))) {
    
            字节[] buf = 新字节[4096];
            int 长度;
            while((len = bis.read(buf, 0, 4096)) > 0) {
                dexWriter.write(buf, 0, len);
            }
        } catch (IOException e) {
            抛出新的运行时异常(e);
        }
    }
    
  10. 编写将加载必要的类并实例化它的代码。

    PathClassLoader 加载器 = new PathClassLoader(
            targetApk.getAbsolutePath(), getClassLoader());
    类 DynamicClass = loader.loadClass("whatever.dynamic.Code");
    构造函数 ctor =dynamicClass.getConstructor(Context.class);
    DynamicInstance = (Runnable) ctor.newInstance(this);
    
  11. 编写将从指定 APK 加载资源的代码。这太难了!所有构造函数和方法都是公共的,但它们是隐藏的。我以反射的方式编写了它,但为了避免这种情况,您可以根据完整的框架 jar 编译代码或在 Smali 中编写部分代码。

    AssetManager 资产 = AssetManager.class.newInstance();
    方法 addAssetPath = AssetManager.class
            .getMethod("addAssetPath", String.class);
    if (addAssetPath.invoke(assets, targetApk.getAbsolutePath()) ==
            整数.valueOf(0)) {
        抛出新的运行时异常();
    }
    
    类 resourcesImpl = Class.forName("android.content.res.ResourcesImpl");
    类 daj = Class.forName("android.view.DisplayAdjustments");
    对象实现 = resourcesImpl
            .getConstructor(AssetManager.class, DisplayMetrics.class,
                    配置.class,daj)
            .newInstance(资产, getResources().getDisplayMetrics(),
                    getResources().getConfiguration(), daj.newInstance());
    
    动态资源 = Resources.class.getConstructor(ClassLoader.class)
            .newInstance(加载器);
    方法 setImpl = Resources.class.getMethod("setImpl",
            Class.forName("android.content.res.ResourcesImpl"));
    setImpl.invoke(dynamicResources, impl);
    
  12. 使用这些资源!有两种获取资源 ID 的方法。

    int someStringId =dynamicResources.getIdentifier(
            “someString”,“字符串”,“whatever.dynamic”);
    String someString =dynamicResources.getString(someStringId);
    
    类 rString = Class.forName("whatever.dynamic.R$string", true, loader);
    anotherStringId = rString.getField("anotherString").getInt(null);
    String anotherString =dynamicResources.getString(anotherStringId);
    

就是这样!这对我有用。 完整的 DynamicActivity.java 代码

在实际项目中,您必须在构建时签署 APK 并检查其加载时签名。当然,切勿将其存储在 SD 卡上,否则您的 APK 可能会被欺骗!

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

有用的链接:

After reading and debugging some AOSP code, I've finally managed to load resources dynamically. Step-by-step guide:

  1. Create a new Android project, I've called it 'Dynamic APK Loading', specified whatever.dynamicapkloading app ID, and left 'app' module name.

  2. Create another Android application module without any Activities. I've done this in the same project, but this is up to you. I've called module 'dynamic', and set application ID to whatever.dynamic.

  3. Remove all unnecessary things from 'dynamic' project. I've removed launcher drawables along with any other resources, and also removed AppCompat depencency from build.gradle. My manifest content looked minimalistic, like this: <application />.

  4. Add some code to 'dynamic' project. For example:

    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. Add some resources to 'dynamic' project. I've created strings.xml:

    <resources>
        <string name="someString">This is a dynamically loaded string.</string>
        <string name="anotherString">Lol! That\'s it.</string>
    </resources>
    
  6. Add it to Run configurations. Set Launch Options / Launch to Nothing. Build -> Build APK! On this step I've got 4.9 KB file called dynamic-debug.apk.

  7. Move this file, dynamic/build/outputs/apk/dynamic-debug.apk, into our main project's assets, i. e. to app/src/main/assets/.

  8. Create/open a class which will load code & resources. Say, this will be DynamicActivity.

  9. Write code which will copy APK from assets to private app directory. It's boring & trivial, you know:

    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. Write code which will load necessary class and instantiate it.

    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. Write code which will load resources from the specified APK. This was a hard one! All constructors and methods are public, but they are hidden. I've written it in reflective way, but to avoid this you can either compile your code against full framework jar or write a part of code in 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. Use these resources! There are two ways of getting resource IDs.

    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);
    

That's it! This worked for me. Full DynamicActivity.java code

In real-world projects you must sign APK while building and check its signature while loading. And, of course, never store it on sdcard, otherwise your APK may be spoofed!

You should also cache resource IDs statically, but re-create Resources and re-query resource values when configuration gets changed.

Useful links:

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文