7

I am trying to load a plugin implementation of an interface from a jar file which is in the /assets directory of my .apk file. The only way I've been able to get this to work is by extracting the jar file to private external storage and then passing that file to the DexClassLoader.

That works, but why should the jar have to exist in two places (the .apk and private external storage)? The DexClassLoader has to have a file path as its argument.

Is there a way to give it a direct path to the file that is in the /assets folder so that I don't have to use up external storage for an extra copy of what's already present?

Here are the relevant code snippets:

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

and

/** 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();
      }
    }
  }
}

and

/** 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();
  }
}
Chuck Krutsinger
  • 2,830
  • 4
  • 28
  • 50
  • 1
    This may be a stupid question, but why package the Jar in assets/ instead of libs/ where it can be referenced directly as part of the applications build path? – devunwired May 03 '12 at 21:53
  • It would take a lot of context to explain, but the shortest answer I can give is that I'm providing a framework that others will use. Their contributions will be compiled into jars that will be packaged up with the framework to form a complete app. The framework will have an interface to load the modules from the jar at runtime. – Chuck Krutsinger May 03 '12 at 22:10
  • 1
    Have you tried `file:///android_asset/...`? Also, consider these discussions: http://stackoverflow.com/questions/4789325/android-path-to-asset-txt-file http://stackoverflow.com/questions/4820816/how-to-get-uri-from-an-asset-file – a.ch. May 07 '12 at 07:37
  • I have tried file:///android_asset both as new File("file///android_asset/xxx.jar"); and as uri = new URI("file:///android_asset/xxx.jar"); followed by new File(uri); Still not working. I cannot find any way to create a File reference to a file in the /assets folder of the .apk – Chuck Krutsinger May 07 '12 at 15:57
  • I have also tried URL aURL = new URL("file:///android_asset/xxx.jar"); followed by new File(aURL.toURI()); which also did not work. – Chuck Krutsinger May 08 '12 at 15:25
  • how you accomplished this promblem.. i also need to load a class from jar which is saved inside assets of apk.. please help... – Arundas K V Jun 10 '14 at 13:01
  • 1
    @ArunWarrier The solution above is the correct way to do it. See answer below. You must extract the jar file into a directory before handing it to the class loader. – Chuck Krutsinger Jun 10 '14 at 18:11
  • @ChuckKrutsinger may i knwo what is LIBRARY_DEX_JAR is it the name of JAR file to be loaded from assets ? – Arundas K V Jun 11 '14 at 05:18
  • 1
    @AndEngine yes, exactly. – Chuck Krutsinger Jun 11 '14 at 15:32
  • @ChuckKrutsinger i am using eclipse in Ubundu 13 machine to develop and build application. Researches in stack says dalvik cannot read simple jars. My jar files not includes classes.dex file. so the above mechanism throws error like 'Unable to Open dex file' and ClassNotFoundException. How can i get a jar file with classes. dex file.. please help .. – Arundas K V Jun 12 '14 at 04:44

1 Answers1

4

Is there a way to give it a direct path to the file that is in the /assets folder so that I don't have to use up external storage for an extra copy of what's already present?

The answer is No. I suppose you follow this blog posted by official source implementing your code. if there is a better way of doing things, the bloger should recommend it in his blog.

Reason why you need optimizedDirectory is explained in the API:

This class loader requires an application-private, writable directory to cache optimized classes.

Also note that assets directory is not writable in apk, so it can't be done with purely assets directory.

Reason why you need copy jar file is a little bit subtle, mentioned in the blog:

First, it has to be copied to a storage location whose path can be supplied to the class loader.

Everything (folders/files) embedded within apk archive is not exposable (or interpretable) to the underlying file system at runtime. In another word, dexPath required in both DexClassLoader and PathClassLoader's constructor need a solid path string like /data/data/com.example/dex/common-lib.jar that represents the file in file system.

Community
  • 1
  • 1
yorkw
  • 40,926
  • 10
  • 117
  • 130
  • You are exactly right about the API documentation. I had missed that during my reading. Copying the .jar out of the .apk file is absolutely required. I had read elsewhere that it is vital that it be copied to a private directory to prevent a code injection attack. – Chuck Krutsinger May 09 '12 at 14:13
  • 1
    Also, for anyone following this topic. The blog article which showed me how to load a jar http://android-developers.blogspot.co.nz/2011/07/custom-class-loading-in-dalvik.html says that you must use ant from the command line is not entirely accurate. I was able to reproduce all of his ant tricks using Eclipse by adding a custom build.xml file. There is an excellent tutorial on using ant with Eclipse at http://www.ibm.com/developerworks/opensource/tutorials/os-ecl-easyant/section2.html – Chuck Krutsinger May 09 '12 at 14:16