4

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?

anavarroma
  • 1,382
  • 1
  • 12
  • 29

3 Answers3

8

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.

anavarroma
  • 1,382
  • 1
  • 12
  • 29
  • My app ideally relies on this for connivence and means debugging currently breaks my app totally. Is there no way to disable InstantRun or by pass this issue do you think? – Peter Fox Jun 28 '16 at 21:10
  • 2
    I found out there's a way to disable it in Android studio though it's a shame if can't write it into the gradle configs themselves that the app can't support it http://stackoverflow.com/questions/35168753/instant-run-in-android-studio-2-0-how-to-turn-off – Peter Fox Jun 28 '16 at 21:24
  • 1
    Thanks, man! Help me a lot. To disable Instant Run, go to File > Settings > Build, Execution, Deployment > Instant Run – Vinícius Carvalho May 09 '19 at 21:27
1

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
}
konakid
  • 656
  • 5
  • 7
0

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;
    }
}
Ali Bagheri
  • 3,068
  • 27
  • 28