16

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

附加信息

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

反射的问题,我最初的解决方案是 android 中的 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 Lolipop 上测试

于 2015-03-17T10:59:41.097 回答
3

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

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

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

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();
于 2015-06-27T11:08:43.520 回答
2

这些方法通常已经过时,不适用于多 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
}

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

getDexFiles(context)
            .flatMap { it.entries().asSequence() }
            .filter { it.startsWith("my.package.name") }
            .map { context.classLoader.loadClass(it) }
于 2018-08-29T19:49:24.477 回答
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 回答