java 在 Android 中查找包中的所有类

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/15446036/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-10-31 19:37:15  来源:igfitidea点击:

Find all classes in a package in Android

javaandroid

提问by user210504

How can i find all classes inside a package on Android? I use PathClassLoader, but it always returns an empty enumeration?

如何在 Android 上的包中找到所有类?我使用 PathClassLoader,但它总是返回一个空的枚举?

Additional info

附加信息

Tried the suggested Reflections approach. Couple of important points about reflections library. The library available through maven central is not compatible with Android and gives dex errors. I had to include source and compile dom4j, java-assist.

尝试了建议的 Reflections 方法。关于反射库的几个要点。可通过 maven central 获得的库与 Android 不兼容并出现 dex 错误。我必须包含源代码并编译 dom4j、java-assist。

The problem with reflections, and my original solution is that PathClassLoader in android returns an empty enumeration for package.

反射的问题,我最初的解决方案是 android 中的 PathClassLoader 返回包​​的空枚举。

The issue with approach is that getResource in Android is always returning empty enumeration.

方法的问题是 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();
        }
   }

回答by tao

Using DexFile to list all classes in your apk:

使用 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();
    }

the rest is using regexp or something else to filter out your desired class.

剩下的就是使用正则表达式或其他东西来过滤掉你想要的类。

回答by Sergey Shustikov

Actually i found solution. Thanks for taoreply.

其实我找到了解决方案。感谢答复。

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()]);
}

Tested on Android 5.0 Lolipop

在 Android 5.0 Lolipop 上测试

回答by fantouch

Here's a solution that bring you not only the class name,
but also the Class<?>object.

这是一个解决方案,不仅可以为您带来类名,
还可以为您带来Class<?>对象。

And while PathClassLoader.class.getDeclaredField("mDexs")fails frequently,
new DexFile(getContext().getPackageCodePath())seems much more stable.

虽然PathClassLoader.class.getDeclaredField("mDexs")经常失败,但
new DexFile(getContext().getPackageCodePath())似乎更稳定。

public abstract class ClassScanner {

    private static final String TAG = "ClassScanner"; 
    private Context mContext;

    public ClassScanner(Context context) {
        mContext = context;
    }

    public Context getContext() {
        return mContext;
    }

    void scan() throws IOException, ClassNotFoundException, NoSuchMethodException {
        long timeBegin = System.currentTimeMillis();

        PathClassLoader classLoader = (PathClassLoader) getContext().getClassLoader();
        //PathClassLoader classLoader = (PathClassLoader) Thread.currentThread().getContextClassLoader();//This also works good
        DexFile dexFile = new DexFile(getContext().getPackageCodePath());
        Enumeration<String> classNames = dexFile.entries();
        while (classNames.hasMoreElements()) {
            String className = classNames.nextElement();
            if (isTargetClassName(className)) {
                //Class<?> aClass = Class.forName(className);//java.lang.ExceptionInInitializerError
                //Class<?> aClass = Class.forName(className, false, classLoader);//tested on 魅蓝Note(M463C)_Android4.4.4 and Mi2s_Android5.1.1
                Class<?> aClass = classLoader.loadClass(className);//tested on 魅蓝Note(M463C)_Android4.4.4 and Mi2s_Android5.1.1
                if (isTargetClass(aClass)) {
                    onScanResult(aClass);
                }
            }
        }

        long timeEnd = System.currentTimeMillis();
        long timeElapsed = timeEnd - timeBegin;
        Log.d(TAG, "scan() cost " + timeElapsed + "ms");
    }

    protected abstract boolean isTargetClassName(String className);

    protected abstract boolean isTargetClass(Class clazz);

    protected abstract void onScanResult(Class clazz);
}

and this is a example how to use:

这是一个如何使用的示例:

new ClassScanner(context) {

    @Override
    protected boolean isTargetClassName(String className) {
        return className.startsWith(getContext().getPackageName())//I want classes under my package
                && !className.contains("$");//I don't need none-static inner classes
    }

    @Override
    protected boolean isTargetClass(Class clazz) {
        return AbsFactory.class.isAssignableFrom(clazz)//I want subclasses of AbsFactory
                && !Modifier.isAbstract(clazz.getModifiers());//I don't want abstract classes
    }

    @Override
    protected void onScanResult(Class clazz) {
        Constructor constructor = null;
        try {
            constructor = clazz.getDeclaredConstructor();
            constructor.setAccessible(true);
            constructor.newInstance();
        } catch (NoSuchMethodException e) {
            e.printStackTrace();
        } catch (InstantiationException e) {
            e.printStackTrace();
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        } catch (InvocationTargetException e) {
            e.printStackTrace();
        }
    }

}.scan();

回答by armansimonyan13

This is taken from http://mindtherobot.com/

这是取自http://mindtherobot.com/

Code example

代码示例

Say we have an annotation called Foo and few classes decorated with it:

假设我们有一个名为 Foo 的注释和几个用它装饰的类:

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

@Foo("person")
public class Person {

}

@Foo("order")
public class Order {

}

Here's a simple class that goes over all classes in your app in runtime, finds those marked with @Foo and get the value of the annotation. Do not copy and paste this code into your app – it has a lot to be fixed and added, as described below the code.

这是一个简单的类,它在运行时遍历应用程序中的所有类,找到那些标有 @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);
    }
  }
}

回答by Mahi

try this..

试试这个..

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

   Set<Class<? extends Object>> allClasses 
                        = reflections.getSubTypesOf(Object.class);

回答by Stefano Liboni

I found the same solution of Sergey Shustikov, with the package name derived form the context and handles inner classes. Unfortunally while it's working on Galaxy Nexus, it's not working on Nexus 6 and Nexus 6p (not tested on other devices).

我找到了与 Sergey Shustikov 相同的解决方案,包名从上下文派生而来并处理内部类。不幸的是,当它在 Galaxy Nexus 上运行时,它不适用于 Nexus 6 和 Nexus 6p(未在其他设备上测试)。

Here's the code:

这是代码:

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

It seams that on Galaxy Nexus, the DexFile contains also the application packages (the one we are trying to find!), while on Nexus 6[p] no package from matching thisPackage are found while some other packages are there... Does anybody have the same problem?

它接缝在 Galaxy Nexus 上,DexFile 还包含应用程序包(我们试图找到的那个!),而在 Nexus 6[p] 上没有找到匹配 thisPackage 的包,而其他一些包在那里......有人吗?有同样的问题吗?

回答by konakid

These approaches are generally outdated and don't work with multi dex apps. To support multidex you can get all of the DexFiles like this

这些方法通常已经过时并且不适用于多 dex 应用程序。要支持 multidex,您可以像这样获取所有的 DexFiles

internal fun getDexFiles(context: Context): Sequence<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
    }.asSequence()
}

private fun field(className: String, fieldName: String): Field {
    val clazz = Class.forName(className)
    val field = clazz.getDeclaredField(fieldName)
    field.isAccessible = true
    return field
}

From there you can use it like this to get all classes in a package

从那里你可以像这样使用它来获取包中的所有类

getDexFiles(context)
            .flatMap { it.entries().asSequence() }
            .filter { it.startsWith("my.package.name") }
            .map { context.classLoader.loadClass(it) }