查找指定类的所有子类(Android)

5
我曾使用 Reflections 库在 Java 中查找给定类的所有子类。以下是我在一个简单的 Java 项目中使用的代码片段:

我以前用 Reflections 库在 Java 中找到给定类的所有子类。

这是我在一个 简单的 Java 项目中使用的代码片段:

    Reflections reflections = new Reflections(PACKAGE_NAME);

    Set<Class<? extends SuperClass>> subTypes =
            reflections.getSubTypesOf(SuperClass.class);

    for (Class<? extends SuperClass> subType : subTypes) {

        log("Subclass = " + subType.getSimpleName());
    }

当我在 Android 项目中运行相同的代码时,“subType”列表返回为空。
有人可以帮我让这个在Android上工作吗? 编辑 我添加的Jars以使整个过程工作是:
  • reflections-0.9.9-RC1-uberjar.jar
  • javassist-3.12.1.GA.jar
  • guava-14.0.1.jar

你看过这个了吗? - t0mm13b
@t0mm13b。这是关于超类,而不是子类的问题。 - Rohit Jain
@RohitJain那么为什么原帖中的代码引用了SuperClass.Class - t0mm13b
@t0mm13b。也许他想在通配符边界中获取SuperClass的所有子类。 - Rohit Jain
@RohitJain 可能吧...是的,这可能是原帖作者的意图。 - t0mm13b
我需要所有的SUB类型,而不是SUPER类型... - Ivelius
5个回答

2
也许你可以尝试这个:
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();

这个工作得相当不错,我认为它应该被接受为答案。 - Alok

1
由于Android使用的编译过程,这可能不起作用。源代码会被Java编译器转换为.class文件,然后将其转换为Android .dex文件(Dalvik字节码),它们不太可能保留所有元数据。

http://developer.android.com/tools/building/index.html


是的,我也这么想...你认为有办法使它工作吗?也许编写一些钩子在编译时会起作用? - Ivelius
抱歉,没有编译器专家。你可以查看这个链接:http://android-developers.blogspot.co.il/2011/07/custom-class-loading-in-dalvik.html - mach
有趣的帖子。谢谢。我期待着找到一个解决方案。 - Ivelius
看这个:https://github.com/ronmamo/reflections/issues/127 - olivmir

0
这是基于fantouch答案的一种方法解决方案。
结果也将包含间接后代。
public class Utils
{
    // ...

    public static ArrayList<Class> GetSubClasses( Context context, String packageName, Class targetSuperClass )
    {
        ArrayList<Class> subClasses = new ArrayList<>();

        try
        {
            DexFile dex = new DexFile( context.getPackageCodePath() );
            for ( Enumeration<String> iterator = dex.entries(); iterator.hasMoreElements(); )
            {
                String className = iterator.nextElement();

                if ( !className.startsWith( packageName ) || className.contains("$") )
                {
                    continue;
                }

                Class classObj = Class.forName( className );

                Class superClass = classObj;
                while ( true )
                {
                    superClass = superClass.getSuperclass();

                    if ( superClass == null || superClass == Object.class )
                    {
                        break;
                    }

                    if ( superClass == targetSuperClass )
                    {
                        subClasses.add( classObj );
                        break;
                    }
                }
            }
        }
        catch ( Exception e )
        {
            e.printStackTrace();
        }

        return subClasses;
    }
}

0
这是一个Kotlin解决方案,也适用于接口。您需要一个真正的Context实例,因此请注意,您无法在单元测试中使用此方法。 请注意,由于API脆弱性且没有有用的替代方案,DexFile过时
private inline fun <reified T> getAllSubclasses(
    startsWithPackage: String,
    context: Context,
): Set<Class<out T>> {
    val classNames = DexFile(context.packageCodePath).entries()
    val classLoader = context.classLoader

    val superclass = T::class.java

    val result = mutableSetOf<Class<out T>>()

    classNames.iterator().forEach { className ->
        if (className.startsWith(startsWithPackage) && className.contains("$").not()) {
            val loadedClass = classLoader.loadClass(className)
            if (loadedClass.superclass == superclass || loadedClass.interfaces.contains(superclass)) {
                @Suppress("UNCHECKED_CAST")
                result.add(loadedClass as Class<out T>)
            }
        }
    }

    return result
}

上述方法可以像这样使用。

val subclasses = getAllSubclasses<SuperClass>("com.example", context)

0

处理dex文件有一个官方API。详情请参见此处。基本上,给定.dex文件的路径,您可以枚举其中的所有文件。要获取classes.dex的URL,您可以使用以下方法:

  Thread.currentThread().getContextClassLoader().getResource("classes.dex")

(你可能需要切掉末尾的"!/classes.dex",我现在无法检查)。然后,您可以迭代遍历classes.dex内容,并尝试以这种方式加载所有找到的类(Class.forName),最后检查您需要的任何类属性。请记住,classes.dex包含所有类,而“所有”可能是“相当多”。处理.dex文件时还有其他性能问题需要记住。但只要不做不必要的低效操作,就应该没问题。特别是Reflections以类似的方式完成其魔术,据我所记。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接