【问题标题】:How can I load a class from a jar file packaged in my .apk file?如何从打包在 .apk 文件中的 jar 文件加载类?
【发布时间】:2012-05-13 10:53:54
【问题描述】:

我正在尝试从 .apk 文件的 /assets 目录中的 jar 文件加载接口的插件实现。我能够让它工作的唯一方法是将 jar 文件提取到私有外部存储,然后将该文件传递给 DexClassLoader。

这行得通,但为什么 jar 必须存在于两个地方(.apk 和私有外部存储)? DexClassLoader 必须有一个文件路径作为它的参数。

有没有办法为其提供指向 /assets 文件夹中文件的直接路径,这样我就不必用完外部存储来获取已经存在的文件的额外副本?

以下是相关代码sn-ps:

// somewhere in my main Activity ...
  final File aExtractedDexFile = new File(getDir("dex", Context.MODE_PRIVATE),
                                  LIBRARY_DEX_JAR);
  extractDexTo(aExtractedDexFile);
  loadLibraryProvider(aExtractedDexFile);

/** Extract the jar file that contains the implementation class.dex and place in private storage */
private void extractDexTo(File tJarInternalStoragePath) {
  BufferedInputStream aJarInputStream = null;
  OutputStream aDexOutputStream = null;

  try {
    aJarInputStream = new BufferedInputStream(getAssets().open(LIBRARY_DEX_JAR));
    aJarOutputStream = new BufferedOutputStream(new FileOutputStream(tJarInternalStoragePath));
    byte[] buf = new byte[BUF_SIZE];
    int len;
    while ((len = aJarInputStream.read(buf, 0, BUF_SIZE)) > 0)
    {
      aJarOutputStream.write(buf, 0, len);
    }
    aJarOutputStream.close();
    aJarInputStream.close();
  } catch (IOException e) {
    if (aDexOutputStream != null) {
      try {
        aJarOutputStream.close();
      } catch (IOException ioe) {
        ioe.printStackTrace();
      }
    }

    if (aJarInputStream != null) {
      try {
        aJarInputStream.close();
      } catch (IOException ioe) {
        ioe.printStackTrace();
      }
    }
  }
}

/** Use DexClassLoader to load the classes from LibraryProvider */
private void loadLibraryProvider(File tFile) {
  // Internal storage where the DexClassLoader writes the optimized dex file to.
  final File aOptimizedDexOutputPath = getDir("outdex", Context.MODE_PRIVATE);

  // Initialize the class loader with the secondary dex file.
  DexClassLoader cl = new DexClassLoader(tFile.getAbsolutePath(),
          aOptimizedDexOutputPath.getAbsolutePath(),
          null,
          getClassLoader());
  Class<?> aLibProviderClazz = null;

  try {
    // Load the library class from the class loader.
    aLibProviderClazz = cl.loadClass(LIBRARY_PROVIDER_CLASS);      
    sLibraryProvider = (LibraryInterface) aLibProviderClazz.newInstance();
  } catch (Exception exception) {
    // Handle exception gracefully here.
    exception.printStackTrace();
  }
}

【问题讨论】:

  • 这可能是个愚蠢的问题,但为什么要将 Jar 打包在 assets/ 而不是 libs/ 中,因为它可以作为应用程序构建路径的一部分直接引用?
  • 这需要很多上下文来解释,但我能给出的最短答案是我提供了一个其他人会使用的框架。他们的贡献将被编译成 jars,这些 jars 将与框架一起打包以形成一个完整的应用程序。该框架将有一个接口在运行时从 jar 中加载模块。
  • 你试过file:///android_asset/...吗?另外,请考虑以下讨论:stackoverflow.com/questions/4789325/…stackoverflow.com/questions/4820816/…
  • @ArunWarrier 上面的解决方案是正确的方法。请参阅下面的答案。您必须先将 jar 文件提取到一个目录中,然后再将其交给类加载器。
  • @AndEngine 是的,完全正确。

标签: android classloader assets


【解决方案1】:

有没有办法为其提供指向 /assets 文件夹中文件的直接路径,这样我就不必用完外部存储来获取已经存在的额外副本?

答案是否定的。我想您遵循this blog posted by official source 实现您的代码。如果有更好的做事方式,博主应该在自己的博客中推荐。

您需要 optimizedDirectory 的原因已在 in the API 解释:

这个类加载器需要一个应用程序私有的、可写的目录来缓存优化的类。

还要注意,资产目录在apk中是不可写的,所以不能用纯资产目录来完成。

为什么需要复制jar文件的原因有点微妙,在博客中提到:

首先,它必须被复制到一个存储位置,其路径可以提供给类加载器。

嵌入在 apk 存档中的所有内容(文件夹/文件)在运行时都不能暴露(或解释)给底层文件系统。换句话说,DexClassLoader 和 PathClassLoader 的构造函数中都需要的 dexPath 需要一个像/data/data/com.example/dex/common-lib.jar 这样的实心路径字符串,它代表文件系统中的文件。

【讨论】:

  • 您对 API 文档的看法完全正确。我在阅读过程中错过了这一点。绝对需要将 .jar 从 .apk 文件中复制出来。我在其他地方读到过,将其复制到私有目录以防止代码注入攻击至关重要。
  • 另外,对于任何关注此主题的人。向我展示如何加载 jar android-developers.blogspot.co.nz/2011/07/… 的博客文章说您必须从命令行使用 ant 并不完全准确。通过添加自定义 build.xml 文件,我能够使用 Eclipse 重现他的所有 ant 技巧。 ibm.com/developerworks/opensource/tutorials/os-ecl-easyant/… 有一个关于在 Eclipse 中使用 ant 的优秀教程
猜你喜欢
  • 1970-01-01
  • 2011-06-02
  • 2020-06-08
  • 2012-09-11
  • 2023-03-12
  • 2019-08-04
  • 2012-03-13
  • 1970-01-01
  • 2012-04-15
相关资源
最近更新 更多