从其他APK加载动态资源

时间:2011-09-20 10:17:24

标签: android resources

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

由于

2 个答案:

答案 0 :(得分:2)

您可以使用PackageManager.getResourcesForApplication访问其他apk中的资源。

答案 1 :(得分:2)

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

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

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

  3. 从“动态”项目中删除所有不必要的内容。我已经删除了启动器drawable和任何其他资源,并从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. 将其添加到“运行配置”。设置启动选项/启动到无。构建 - &gt;建立APK!在这一步中,我有4.9 KB文件名为dynamic-debug.apk

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

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

  9. 编写将APK从资产复制到私有app目录的代码。这很乏味&amp;琐碎,你知道:

    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);
    
  13. 就是这样!这对我有用。 Full DynamicActivity.java code

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

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

    有用的链接: