9

次のようなパッケージ内のすべてのクラスのリストを取得するコードがいくつかありました。

 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 Run であることがわかりました。アプリをデバッグすると、インスタンスが実行されていない場合、DexFile 変数 df にクラス名のリスト (4,000 以上) が含まれていることがわかります。Instant Run をオンにすると、30 ほどのクラス名しか取得できず、探しているクラスがそこにありません。multi dex と関係があるような気がしますが、Instant Run が内部でどのように機能しているかはわかりません (私のアプリは multidex を使用していません)。

Instant Run をオンにして、このようなクラスのリストを取得する方法を知っている人はいますか? または、私がこの動作を見ている理由を正確に知っている人はいますか (それを理解するのは素晴らしいことです)?

4

1 に答える 1

4

アプリのデータパスでインスタントランでビルドした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;
}

}

于 2016-06-07T05:03:54.597 に答える