如何在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();
}
}
答案 0 :(得分: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();
}
其余的使用正则表达式或其他东西来过滤掉你想要的课程。
答案 1 :(得分:14)
其实我找到了解决方案。感谢tao回复。
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上测试
答案 2 :(得分: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();
答案 3 :(得分:1)
试试这个..
Reflections reflections = new Reflections("my.project.prefix");
Set<Class<? extends Object>> allClasses
= reflections.getSubTypesOf(Object.class);
答案 4 :(得分:1)
代码示例
假设我们有一个名为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);
}
}
}
答案 5 :(得分:0)
我发现了谢尔盖舒斯蒂科夫的相同解决方案,包名称来自上下文并处理内部类。不幸的是,它正在使用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的包,而其他一些包就在那里。 。 有没有人有同样的问题?
答案 6 :(得分:0)
这些方法通常已经过时,不适用于多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) }