Posted by Tim Bray on 28 July 2011 at 8:35 AM
[This post is by Fred Chung, who's an android developer advocate-Tim Bray]
The Dalvik VM provides facilities for developers to perform custom class loading. instead of loading Dalvik executable ("Dex") files from the default location, an application can load them from alternative locations such as internal storage or over the network.
This technique is not for every application; in fact, most do just fine without it. However, there are situations where custom class loading can come in handy. Here are a couple of scenarios:
Big apps can contain in more than 64 K method references, which is the maximum number of supported in a DEX file. to get around this limitation, developers can partition part of the program into multiple secondary Dex files, and load them at runtime. (a large application can contain up to 64 K method references, which is the maximum limit supported by Dex files. To break through this restriction, developers can divide the program into multiple Dex files and then load these classes at runtime)
Frameworks can be designed to make their execution logic extensible by dynamic code loading at runtime)
We have created a sample app to demonstrate the partitioning of DEX files and runtime class
Loading. (Note that for reasons discussed below, the app cannot be built with the ADT Eclipse plug-in. Instead, use the existing ded ant build script. See readme.txt for detail .)
The app has a simple activity that invokes a library component to display a toast. the activity and its resources are kept in the default Dex, whereas the library code is stored in a secondary Dex bundled in the APK. this requires a modified build process,
Which is shown below in detail.
Before the library method can be invoked, the APP has to first explicitly load the secondary Dex file. Let's take a look at the relevant moving parts.
Code Organization
The application consists of 3 classes.
Com. example. Dex. mainactivity: Ui component from which the library is invoked
Com. example. Dex. libraryinterface: interface definition for the library
Com. example. Dex. Lib. libraryprovider: Implementation of the Library
The library is packaged in a secondary Dex, while the rest of the classes are stored in the default (primary) DEX file. the "Build Process" section below has strates how to accomplish this. of course, the packaging demo-is dependent on the Particle
Scenario A developer is dealing.
Class loading and Method Invocation
The secondary Dex file, containing libraryprovider, is stored as an application asset. first, it has to be copied to a storage location whose path can be supplied to the class loader. the sample app uses the app's private internal storage area for this purpose.
(Technically, external storage wowould also work, but one has to consider the security implications of keeping application binaries there .)
Below is a snippet from mainactivity where standard file I/O is used to accomplish the copying.
// Before the secondary dex file can be processed by the DexClassLoader, // it has to be first copied from asset resource to a storage location. File dexInternalStoragePath= newFile(getDir("dex",Context.MODE_PRIVATE), SECONDARY_DEX_NAME); ... BufferedInputStream bis= null; OutputStream dexWriter= null; static final int BUF_SIZE= 8* 1024; try { bis = new BufferedInputStream(getAssets().open(SECONDARY_DEX_NAME)); dexWriter =new BufferedOutputStream( newFileOutputStream(dexInternalStoragePath)); byte[] buf= newbyte[BUF_SIZE]; int len; while((len= bis.read(buf,0, BUF_SIZE))> 0){ dexWriter.write(buf,0, len); } dexWriter.flush(); dexWriter.close(); bis.close(); } catch(. ..) {...}
Next, a dexclassloader is instantiated to load the library from the extracted
Secondary Dex file. There are a couple of ways to invoke methods on classes loaded in this manner. In this sample, the class instance is cast to an interface through which the method is called directly.
Another approach is to invoke methods using the reflection API. the advantage of using reflection is that It doesn't require the secondary Dex file to implement any participant interfaces. however, one shocould be aware that reflection is verbose and slow.
// Internal storage where the DexClassLoader writes the optimized dex file to final File optimizedDexOutputPath= getDir("outdex",Context.MODE_PRIVATE); DexClassLoader cl= newDexClassLoader(dexInternalStoragePath.getAbsolutePath(), optimizedDexOutputPath.getAbsolutePath(), null, getClassLoader()); Class libProviderClazz= null; try { // Load the library. libProviderClazz = cl.loadClass("com.example.dex.lib.LibraryProvider"); // Cast the return object to the library interface so that the // caller can directly invoke methods in the interface. // Alternatively, the caller can invoke methods through reflection, // which is more verbose. LibraryInterface lib= (LibraryInterface) libProviderClazz.newInstance(); lib.showAwesomeToast(this,"hello"); } catch(Exception e){ ...}Build Process
In order to church n out two separate Dex files, we need to tweak the Standard build process. to do the trick, we simply modify the "-Dex" target in the project's ant build. XML.
The modified "-Dex" target performs the following operations:
Create two staging directories to store. class files to be converted to the default DEX and the secondary Dex.
Selectively copy. class files from project_root/bin/classes to the two staging directories.
<!-- Primary dex to include everything but the concrete library implementation. --> <copytodir="${out.classes.absolute.dir}.1"> <filesetdir="${out.classes.absolute.dir}"> <excludename="com/example/dex/lib/**"/> </fileset> </copy> <!-- Secondary dex to include the concrete library implementation. --> <copytodir="${out.classes.absolute.dir}.2"> <filesetdir="${out.classes.absolute.dir}"> <includename="com/example/dex/lib/**"/> </fileset> </copy>
Convert. class files from the two staging directories into two separate Dex files.
Add the secondary Dex file to a jar file, which is the expected input format for the dexclassloader. Lastly, store the JAR file in the "assets" directory of the project.
<!-- Package the output in the assets directory of the apk. --> <jardestfile="${asset.absolute.dir}/secondary_dex.jar" basedir="${out.absolute.dir}/secondary_dex_dir" includes="classes.dex"/>
To kick-off the build, you execute ant debug (or release) from the project root directory.
That's it! In the right situations, dynamic class loading can be quite useful.