DexFile in 2.0 versions of Android Studio and Grad

2019-01-26 20:05发布

I am using the following code to instantiate all the classes included in a certain package.

DexFile df = new DexFile(getPackageCodePath());
for (Enumeration<String> iter = df.entries(); iter.hasMoreElements(); ) {
    String className = iter.nextElement();
    if (className.contains(packageName) && !className.contains("$")) {
        myClasses.add(Class.forName(className).newInstance());
    }
}

Unfortunately it is not working properly anymore. Since Android Studio 2 and Gradle 2.0.0, the DexFile entries no longer include all the classes within the app but only the classes belonging to the com.android.tools package.

Is this a known issue?

3条回答
做自己的国王
2楼-- · 2019-01-26 20:12

Looks like this issue is related to the new InstantRun feature in the Android Plugin for Gradle 2.0.0.

getPackageCodePath() gets a String pointing towards the base.apk file in the Android file system. If we unzip that apk we can find one or several .dex files inside its root folder. The entries obtained from the method df.entries() iterates over the .dex files found in that root folder in order to obtain all of its compiled classes.

However, if we are using the new Android Plugin for Gradle, we will only find the .dex related to the android runtime and instant run (packages com.tools.android.fd.runtime, com.tools.android.fd.common and com.tools.android.tools.ir.api). Every other class will be compiled in several .dex files, zipped into a file called instant-run.zip and placed into the root folder of the apk.

That's why the code posted in the question is not able to list all the classes within the app. Still, this will only affect Debug builds since the Release ones don't feature InstantRun.

查看更多
时光不老,我们不散
3楼-- · 2019-01-26 20:30

for get all dex files of an app use below method.

public static ArrayList<DexFile> getMultiDex()
{
    BaseDexClassLoader dexLoader = (BaseDexClassLoader) getClassLoader();
    Field f = getField("pathList", getClassByAddressName("dalvik.system.BaseDexClassLoader"));
    Object pathList = getObjectFromField(f, dexLoader);
    Field f2 = getField("dexElements", getClassByAddressName("dalvik.system.DexPathList"));
    Object[] list = getObjectFromField(f2, pathList);
    Field f3 = getField("dexFile", getClassByAddressName("dalvik.system.DexPathList$Element"));

    ArrayList<DexFile> res = new ArrayList<>();

    for(int i = 0; i < list.length; i++)
    {
        DexFile d = getObjectFromField(f3, list[i]);
        res.add(d);
    }

    return res;
}

//------------ other methods

public static ClassLoader getClassLoader()
{
    return Thread.currentThread().getContextClassLoader();
}


public static Class<?> getClassByAddressName(String classAddressName)
{
    Class mClass = null;
    try
    {
        mClass = Class.forName(classAddressName);
    } catch(Exception e)
    {
    }
    return mClass;
}


public static <T extends Object> T getObjectFromField(Field field, Object arg)
{
    try
    {
        field.setAccessible(true);
        return (T) field.get(arg);
    } catch(Exception e)
    {
        e.printStackTrace();
        return null;
    }
}
查看更多
Melony?
4楼-- · 2019-01-26 20:34

To access all DexFiles you can do this

internal fun getDexFiles(context: Context): List<DexFile> {
    // Here we do some reflection to access the dex files from the class loader. These implementation details vary by platform version,
    // so we have to be a little careful, but not a huge deal since this is just for testing. It should work on 21+.
    // The source for reference is at:
    // https://android.googlesource.com/platform/libcore/+/oreo-release/dalvik/src/main/java/dalvik/system/BaseDexClassLoader.java
    val classLoader = context.classLoader as BaseDexClassLoader

    val pathListField = field("dalvik.system.BaseDexClassLoader", "pathList")
    val pathList = pathListField.get(classLoader) // Type is DexPathList

    val dexElementsField = field("dalvik.system.DexPathList", "dexElements")
    @Suppress("UNCHECKED_CAST")
    val dexElements = dexElementsField.get(pathList) as Array<Any> // Type is Array<DexPathList.Element>

    val dexFileField = field("dalvik.system.DexPathList\$Element", "dexFile")
    return dexElements.map {
        dexFileField.get(it) as DexFile
    }
}

private fun field(className: String, fieldName: String): Field {
    val clazz = Class.forName(className)
    val field = clazz.getDeclaredField(fieldName)
    field.isAccessible = true
    return field
}
查看更多
登录 后发表回答