Android Studio 2.0 Instant 运行 导致 DexFile 无法全部加载 类

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

我有一些代码来获取包中所有 classes 的列表,看起来像这样:

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

但是,自从我将 Android Studio 升级到 2.0 版后,此代码已停止工作。我发现罪魁祸首是 Instant 运行。如果我调试应用程序,我可以看到没有实例 运行,DexFile 变量 df 包含 class 名称的列表(超过 4,000 个)。当启用 Instant 运行 时,我只能得到大约 30 个 class 个名字,而我正在寻找的 classes 不存在。我感觉它与 multidex 有关,但我不确定 Instant 运行 如何在幕后工作(我的应用程序不使用 multidex)。

有谁知道我如何在即时 运行 开启的情况下获得像这样的 classes 列表?或者有人确切地知道为什么我会看到这种行为(理解它会很棒)吗?

我们可以在应用程序数据路径中处理即时构建的DEX文件运行。

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;
}

}