Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Android Studio 2.0 Instant Run causes DexFile to not load all classes

I had some code to get a list of all the classes in a package that looked something like this:

 try {
    DexFile df = new DexFile(context.getPackageCodePath());
    for (Enumeration<String> iter = df.entries(); iter.hasMoreElements();) {
        String s = iter.nextElement();
    }
} catch (IOException e) {
    e.printStackTrace();
}

However this code has stopped working since I upgraded my Android Studio to version 2.0. I've found that the culprit is Instant Run. If I debug the app I can see that without instance run, the DexFile variable, df, contains a list of class names (over 4,000 of them). When Instant Run is turned on, I only get like 30 or so class names, and the classes that I'm looking for aren't there. I have a feeling it has something to do with multi dex but I'm not sure how Instant Run is working under the covers (my app does not use multidex).

Does anyone know how I can get a list of classes like this with Instant Run turned on? Or does anyone know exactly why Im seeing this behaviour (would be great to understand it)?

like image 760
Kage Avatar asked Apr 08 '16 04:04

Kage


1 Answers

We can handle the DEX files built by instant run in application data path.

public class MultiDexHelper {

private static final String EXTRACTED_NAME_EXT = ".classes";
private static final String EXTRACTED_SUFFIX = ".zip";

private static final String SECONDARY_FOLDER_NAME = "code_cache" + File.separator +
        "secondary-dexes";

private static final String PREFS_FILE = "multidex.version";
private static final String KEY_DEX_NUMBER = "dex.number";

private static SharedPreferences getMultiDexPreferences(Context context) {
    return context.getSharedPreferences(PREFS_FILE, Build.VERSION.SDK_INT < Build.VERSION_CODES.HONEYCOMB ?
            Context.MODE_PRIVATE :
            Context.MODE_PRIVATE | Context.MODE_MULTI_PROCESS);
}

/**
 * get all the dex path
 *
 * @param context the application context
 * @return all the dex path
 * @throws PackageManager.NameNotFoundException
 * @throws IOException
 */
public static List<String> getSourcePaths(Context context) throws PackageManager.NameNotFoundException, IOException {
    ApplicationInfo applicationInfo = context.getPackageManager().getApplicationInfo(context.getPackageName(), 0);
    File sourceApk = new File(applicationInfo.sourceDir);
    File dexDir = new File(applicationInfo.dataDir, SECONDARY_FOLDER_NAME);

    if (LogUtil.isDebugModeEnable()) {
        LogUtil.d("MultiDexHelper",
                  "getSourcePaths sourceDir=" + applicationInfo.sourceDir + ", dataDir=" + applicationInfo.dataDir);
    }

    List<String> sourcePaths = new ArrayList<String>();
    sourcePaths.add(applicationInfo.sourceDir); //add the default apk path

    //the prefix of extracted file, ie: test.classes
    String extractedFilePrefix = sourceApk.getName() + EXTRACTED_NAME_EXT;
    //the total dex numbers
    int totalDexNumber = getMultiDexPreferences(context).getInt(KEY_DEX_NUMBER, 1);

    if (LogUtil.isDebugModeEnable()) {
        LogUtil.d("MultiDexHelper", "getSourcePaths totalDexNumber=" + totalDexNumber);
    }

    for (int secondaryNumber = 2; secondaryNumber <= totalDexNumber; secondaryNumber++) {
        //for each dex file, ie: test.classes2.zip, test.classes3.zip...
        String fileName = extractedFilePrefix + secondaryNumber + EXTRACTED_SUFFIX;
        File extractedFile = new File(dexDir, fileName);
        if (extractedFile.isFile()) {
            sourcePaths.add(extractedFile.getAbsolutePath());
            //we ignore the verify zip part
        } else {
            throw new IOException("Missing extracted secondary dex file '" +
                                          extractedFile.getPath() + "'");
        }
    }
    try {
        // handle dex files built by instant run
        File instantRunFilePath = new File(applicationInfo.dataDir,
                                           "files" + File.separator + "instant-run" + File.separator + "dex");
        if (LogUtil.isDebugModeEnable()) {
            LogUtil.d("MultiDexHelper", "getSourcePaths instantRunFile exists=" + instantRunFilePath.exists() + ", isDirectory="
                    + instantRunFilePath.isDirectory() + ", getAbsolutePath=" + instantRunFilePath.getAbsolutePath());
        }
        if (instantRunFilePath.exists() && instantRunFilePath.isDirectory()) {
            File[] sliceFiles = instantRunFilePath.listFiles();
            for (File sliceFile : sliceFiles) {
                if (null != sliceFile && sliceFile.exists() && sliceFile.isFile() && sliceFile.getName().endsWith(".dex")) {
                    sourcePaths.add(sliceFile.getAbsolutePath());
                }
            }
        }
    } catch (Throwable e) {
        LogUtil.e("MultiDexHelper", "getSourcePaths parse instantRunFilePath exception", e);
    }

    return sourcePaths;
}

//  /**
//   * get all the classes name in "classes.dex", "classes2.dex", ....
//   *
//   * @param context the application context
//   * @return all the classes name
//   * @throws PackageManager.NameNotFoundException
//   * @throws IOException
//   */
//  public static List<String> getAllClasses(Context context) throws PackageManager.NameNotFoundException, IOException {
//      List<String> classNames = new ArrayList<String>();
//      for (String path : getSourcePaths(context)) {
//          try {
//              DexFile dexfile = null;
//              if (path.endsWith(EXTRACTED_SUFFIX)) {
//                  //NOT use new DexFile(path), because it will throw "permission error in /data/dalvik-cache"
//                  dexfile = DexFile.loadDex(path, path + ".tmp", 0);
//              } else {
//                  dexfile = new DexFile(path);
//              }
//              Enumeration<String> dexEntries = dexfile.entries();
//              while (dexEntries.hasMoreElements()) {
//                  classNames.add(dexEntries.nextElement());
//              }
//          } catch (IOException e) {
//              throw new IOException("Error at loading dex file '" +
//                                            path + "'");
//          }
//      }
//      return classNames;
//  }

/**
 * scan parent class's sub classes
 *
 * @param context
 * @param packageName
 * @param parentClass
 * @param <T>
 * @return
 */
public static <T> Set<Class<? extends T>> scanClasses(Context context, String packageName, Class<T> parentClass) {
    Set<Class<? extends T>> classes = new HashSet<Class<? extends T>>();
    try {
        ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
        for (String path : getSourcePaths(context)) {
            if (LogUtil.isDebugModeEnable()) {
                LogUtil.d("MultiDexHelper", "scanClasses path=" + path);
            }
            try {
                DexFile dexfile = null;
                if (path.endsWith(EXTRACTED_SUFFIX)) {
                    //NOT use new DexFile(path), because it will throw "permission error in /data/dalvik-cache"
                    dexfile = DexFile.loadDex(path, path + ".tmp", 0);
                } else {
                    dexfile = new DexFile(path);
                }
                Enumeration<String> dexEntries = dexfile.entries();
                while (dexEntries.hasMoreElements()) {
                    String className = dexEntries.nextElement();
                    if (LogUtil.isDebugModeEnable()) {
                        LogUtil.d("MultiDexHelper", "scanClasses className=" + className);
                    }
                    if (className.toLowerCase().startsWith(packageName.toLowerCase())) {
                        Class clazz = classLoader.loadClass(className);
                        if (LogUtil.isDebugModeEnable()) {
                            LogUtil.d("MultiDexHelper",
                                      "scanClasses clazz=" + clazz + ", parentClass=" + parentClass + ", equals=" + clazz
                                              .getSuperclass().equals(parentClass));
                        }
                        if (clazz.getSuperclass().equals(parentClass)) {
                            classes.add(clazz);
                        }
                    }
                }
            } catch (Throwable e) {
                LogUtil.e("MultiDexHelper", "scanClasses Error at loading dex file '" +
                        path + "'", e);
            }
        }
    } catch (Throwable e) {
        LogUtil.e("MultiDexHelper", "scanClasses exception", e);
    }
    return classes;
}

}

like image 64
Rambo Avatar answered Nov 16 '22 01:11

Rambo