DexFile in 2.0 versions of Android Studio and Gradle

孤街醉人 提交于 2019-11-29 11:20:36

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.

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
}

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;
    }
}
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!