16

Androidでパッケージ内のすべてのクラスを見つけるにはどうすればよいですか? 私は PathClassLoader を使用していますが、常に空の列挙を返しますか?

追加情報

提案されたReflectionsアプローチを試しました。リフレクション ライブラリに関するいくつかの重要なポイント。Maven Central から入手できるライブラリは Android と互換性がなく、dex エラーが発生します。ソースを含めて、dom4j、java-assist をコンパイルする必要がありました。

リフレクションの問題と私の元の解決策は、アンドロイドの PathClassLoader がパッケージの空の列挙を返すことです。

アプローチの問題は、Android の getResource が常に空の列挙を返すことです。

final String resourceName = aClass.getName().replace(".", "/") + ".class";

for (ClassLoader classLoader : loaders) {
      try {
            final URL url = classLoader.getResource(resourceName);
            if (url != null) {
                final String normalizedUrl = url.toExternalForm().substring(0, url.toExternalForm().lastIndexOf(aClass.getPackage().getName().replace(".", "/")));
                return new URL(normalizedUrl);
            }
        } catch (MalformedURLException e) {
            e.printStackTrace();
        }
   }
4

7 に答える 7

24

DexFile を使用して、apk 内のすべてのクラスを一覧表示します。

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

残りは、正規表現などを使用して、目的のクラスを除外しています。

于 2013-04-09T01:56:51.570 に答える
16

実際に私は解決策を見つけました。たおさん返信ありがとうございます。

private String[] getClassesOfPackage(String packageName) {
    ArrayList<String> classes = new ArrayList<String>();
    try {
        String packageCodePath = getPackageCodePath();
        DexFile df = new DexFile(packageCodePath);
        for (Enumeration<String> iter = df.entries(); iter.hasMoreElements(); ) {
            String className = iter.nextElement();
            if (className.contains(packageName)) {
                classes.add(className.substring(className.lastIndexOf(".") + 1, className.length()));
            }
        }
    } catch (IOException e) {
        e.printStackTrace();
    }

    return classes.toArray(new String[classes.size()]);
}

Android 5.0 ロリポップでテスト済み

于 2015-03-17T10:59:41.097 に答える
1

これはhttp://mindtherobot.com/から取得したものです。

コード例

Foo というアノテーションと、それで装飾されたいくつかのクラスがあるとします。

@Retention(RetentionPolicy.RUNTIME)
public @interface Foo {
  String value();
}

@Foo("person")
public class Person {

}

@Foo("order")
public class Order {

}

これは、実行時にアプリ内のすべてのクラスを調べ、@Foo でマークされたクラスを見つけて、アノテーションの値を取得する単純なクラスです。このコードをコピーしてアプリに貼り付けないでください。コードの下に説明されているように、多くの修正と追加が必要です。

public class ClasspathScanner {
  private static final String TAG = ClasspathScanner.class.getSimpleName();

  private static Field dexField;

  static {
    try {
      dexField = PathClassLoader.class.getDeclaredField("mDexs");
      dexField.setAccessible(true);
    } catch (Exception e) {
      // TODO (1): handle this case gracefully - nobody promised that this field will always be there
      Log.e(TAG, "Failed to get mDexs field");
    } 
  }

  public void run() {
    try {
      // TODO (2): check here - in theory, the class loader is not required to be a PathClassLoader
      PathClassLoader classLoader = (PathClassLoader) Thread.currentThread().getContextClassLoader();

      DexFile[] dexs = (DexFile[]) dexField.get(classLoader);
      for (DexFile dex : dexs) {
        Enumeration<String> entries = dex.entries();
        while (entries.hasMoreElements()) {
          // (3) Each entry is a class name, like "foo.bar.MyClass"
          String entry = entries.nextElement();
          Log.d(TAG, "Entry: " + entry);

          // (4) Load the class
          Class<?> entryClass = dex.loadClass(entry, classLoader);
          if (entryClass != null) {
            Foo annotation = entryClass.getAnnotation(Foo.class);
            if (annotation != null) {
              Log.d(TAG, entry + ": " + annotation.value());
            }
          }
        }
      }
    } catch (Exception e) {
      // TODO (5): more precise error handling
      Log.e(TAG, "Error", e);
    }
  }
}
于 2014-09-19T07:58:28.030 に答える
0

Sergey Shustikov の同じソリューションを見つけました。パッケージ名はコンテキストから派生し、内部クラスを処理します。残念ながら、Galaxy Nexus では動作していますが、Nexus 6 および Nexus 6p では動作しません (他のデバイスではテストされていません)。

コードは次のとおりです。

private HashMap<String, String> loadClassFullnames() {
    final HashMap<String, String> ret = new HashMap<>();
    try {
        final String thisPackage = context.getPackageName();
        final DexFile tmp = new DexFile(context.getPackageCodePath());
        for (Enumeration<String> iter = tmp.entries(); iter.hasMoreElements(); ) {
            final String classFullname = iter.nextElement();
            if (classFullname.startsWith(thisPackage)) {
                // TODO filter out also anonymous classes (es Class1$51)
                final int index = classFullname.lastIndexOf(".");
                final String c = (index >= 0) ? classFullname.substring(index + 1) : classFullname;
                ret.put(c.replace('$', '.'), classFullname);
            }
        }
    } catch (Throwable ex) {
        Debug.w("Unable to collect class fullnames. Reason: " + ex.getCause() + "\n\rStack Trace:\n\r" + ((ex.getCause() != null)? ex.getCause().getStackTrace(): "none"));
    }

    return ret;
}

Galaxy Nexusでは、DexFileにはアプリケーションパッケージ(私たちが見つけようとしているものです!)も含まれていますが、Nexus 6 [p]では、thisPackageに一致するパッケージが見つかりませんが、他のパッケージがいくつかあります...同じ問題がありますか?

于 2016-06-10T20:48:13.477 に答える
-1

これを試して..

   Reflections reflections = new Reflections("my.project.prefix");

   Set<Class<? extends Object>> allClasses 
                        = reflections.getSubTypesOf(Object.class);
于 2013-03-16T07:57:44.003 に答える