Skip to content

Android dynamic load classes

galen edited this page Jan 4, 2016 · 6 revisions

Android classes are loaded by DexClassLoader.

Android类由DexClassLoader加载

public class DexClassLoader extends BaseDexClassLoader {
    public DexClassLoader(String dexPath, String optimizedDirectory,
            String libraryPath, ClassLoader parent) {
        super(dexPath, new File(optimizedDirectory), libraryPath, parent);
    }
}

It was simply redirect to class BaseDexClassLoader.
转向BaseDexClassLoader

public BaseDexClassLoader(String dexPath, File optimizedDirectory,
        String libraryPath, ClassLoader parent) {
    super(parent);
    this.originalPath = dexPath;
    this.pathList =
        new DexPathList(this, dexPath, libraryPath, optimizedDirectory);
}

Let's track the dexPath, as usual, it's "/data/../*.apk", now it was used to create the pathList (DexPathList).

public DexPathList(ClassLoader definingContext, String dexPath,
        String libraryPath, File optimizedDirectory) {
    ...
    this.dexElements =
        makeDexElements(splitDexPath(dexPath), optimizedDirectory);
    ...
}

And then dexElements ( DexPathList$Element[] ) was created.

private static final String DEX_SUFFIX = ".dex";
private static final String JAR_SUFFIX = ".jar";
private static final String ZIP_SUFFIX = ".zip";
private static final String APK_SUFFIX = ".apk";
...
private static Element[] makeDexElements(ArrayList<File> files,
        File optimizedDirectory) {
    ArrayList<Element> elements = new ArrayList<Element>();
    /*
     * Open all files and load the (direct or contained) dex files
     * up front.
     */
    for (File file : files) {
        ZipFile zip = null;
        DexFile dex = null;
        String name = file.getName();
        if (name.endsWith(DEX_SUFFIX)) {
            // Raw dex file (not inside a zip/jar).
            ...
        } else if (name.endsWith(APK_SUFFIX) || name.endsWith(JAR_SUFFIX)
                || name.endsWith(ZIP_SUFFIX)) {
            //---------------------------------------------------
            // Archive File Loading Block
            // if (name.endsWith(".so") doFollowingWithReflect();
            //---------------------------------------------------
            try {
                zip = new ZipFile(file);
            } catch (IOException ex) {
                /*
                 * Note: ZipException (a subclass of IOException)
                 * might get thrown by the ZipFile constructor
                 * (e.g. if the file isn't actually a zip/jar
                 * file).
                 */
                System.logE("Unable to open zip file: " + file, ex);
            }
            try {
                dex = loadDexFile(file, optimizedDirectory);
            } catch (IOException ignored) {
                /*
                 * IOException might get thrown "legitimately" by
                 * the DexFile constructor if the zip file turns
                 * out to be resource-only (that is, no
                 * classes.dex file in it). Safe to just ignore
                 * the exception here, and let dex == null.
                 */
            }
        } else {
            System.logW("Unknown file type for: " + file);
        }
        if ((zip != null) || (dex != null)) {
            elements.add(new Element(file, zip, dex));
        }
    }
    return elements.toArray(new Element[elements.size()]);
}

By now, we can see that DexClassLoader only support files with extensions ".dex", ".jar", ".zip" and ".apk".

For support ".so" file which can be automatic built in android application storage, we needs to do stuff as Archive File Loading Block to create a DexPathList$Element and then reflect on host class loader and expand it's dexPathList's dexElements.

Following is the fake code:

Context context = getApplicationContext();
File plugin = new File(context.getApplicationInfo().dataDir, "lib/**.so");
Element element = makeDexElement(plugin); // dalvik.system.DexPathList$Element
context.getClassLoader() // dalvik.system.DexClassLoader
        .@dexPathList // dalvik.system.DexPathList
        .@dexElements // dalvik.system.DexPathList$Element []
        .insert(element, 0);