4

I'm attempting to implement a package-scanning feature, similar to Spring's component-scan, for the Android framework I'm developing. Basically, I would like to be able to specify a base package, e.g. com.foo.bar and retrieve all Class instances that have a particular annotation. I don't want to have to register every component with my framework as that would defeat the purpose of the auto scanning.

Based on my research, it seems that it's not possible with Java to retrieve resources given a package name using reflection. However, I briefly looked into the Reflections framework, and I'm wondering if there is an Android-compatible equivalent. If not, perhaps there is a slightly less obvious way to accomplish what I want to do.

I looked into the Spring source a bit to see how they achieved this, but I don't think what they are doing would work within the Dalvik runtime.

Update

Currently, the below code has been the best I can do to retrieve all classes that contain a specific annotation, but frankly it's a pretty poor solution. It makes some really unsafe assumptions about the ClassLoader plus it scans (and loads) all application classes.

public Set<Class<?>> getClassesWithAnnotation(Class<? extends Annotation> annotation) {
    Set<Class<?>> classes = new HashSet<Class<?>>();
    Field dexField = PathClassLoader.class.getDeclaredField("mDexs");
    dexField.setAccessible(true);
    PathClassLoader classLoader = (PathClassLoader) Thread.currentThread().getContextClassLoader();
    DexFile[] dexs = (DexFile[]) dexField.get(classLoader);
    for (DexFile dex : dexs) {
        Enumeration<String> entries = dex.entries();
        while (entries.hasMoreElements()) {
            String entry = entries.nextElement();
            Class<?> entryClass = dex.loadClass(entry, classLoader);
            if (entryClass != null && entryClass.isAnnotationPresent(annotation)) {
                classes.add(entryClass);
            }
        }
    }
    return classes;
}
Tyler Treat
  • 14,640
  • 15
  • 80
  • 115

5 Answers5

4

I wanted to find all the subclass at runtime. So I've been looking for android class scanning. This is my final code from what I gathered in web. You will get the idea.

public static void findSubClasses(Context context, Class parent) {
    ApplicationInfo ai = context.getApplicationInfo();
    String classPath = ai.sourceDir;
    DexFile dex = null;
    try {
        dex = new DexFile(classPath);
        Enumeration<String> apkClassNames = dex.entries();
        while (apkClassNames.hasMoreElements()) {
            String className = apkClassNames.nextElement();
            try {
                Class c = context.getClassLoader().loadClass(className);
                if (parent.isAssignableFrom(c)) {
                    android.util.Log.i("nora", className);
                }
            } catch (ClassNotFoundException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            }
            //              android.util.Log.i("nora", className);
        }
    } catch (IOException e) {
        // TODO Auto-generated catch block
        e.printStackTrace();
    } finally {
        try {
            dex.close();
        } catch (IOException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
    }
}
NoraBora
  • 41
  • 2
3

I share the opinion of Joop Eggen and find his approach a good one. In Android I try to avoid the usual web app features which lead to a long lasting application start. I do not use reflection or package scanning.

But if you want to .... if I understand it correctly you want to have an annotation for a class. Instead of using annotations you could also use marker interfaces (to just have more possibilites).

1) Look at

2) AndroidAnnotations

I would prefer the way AndroidAnnotations work (maybe an integration in AndroidAnnotations is the preferable way): It automatically adds an extra compilation step that generates source code, using the standard Java Annotation Processing Tool. So instead of runtime scanning you execute code based on the annotations generated during compile time.

I think the Bean/EBean annotation could work for you (only single class): https://github.com/excilys/androidannotations/wiki/Enhance%20custom%20classes

A scan-feature is not available, see this thread

3) Writing your own annotation processor

Community
  • 1
  • 1
ChrLipp
  • 15,526
  • 10
  • 75
  • 107
  • Admittedly I'm not very familiar with how compilation-time source code generation works. I will have to take a look at how AndroidAnnotations does it, but I fear switching my framework to compile-time code generation would be a lot of work, although the reduction of performance overhead would be nice. – Tyler Treat Jul 13 '12 at 17:30
  • And did the first link help you? – ChrLipp Jul 16 '12 at 09:16
  • [ClassIndex library](https://github.com/atteo/classindex) implements 3rd point and I can confirm it works with Android. – Sławek Aug 20 '15 at 22:25
0

EDIT:

I found this issue in the Android issue tracker. It appears that ClassLoader.getResource(String) is 'working as expected', in that it returns null. This is expected because the DalvikVM does not keep the resources around after compiling. There are workarounds listed in the issue, but there may be another way to access the classes you desire.

Use the PackageManager to get a hold of an instance of ApplicationInfo. ApplicationInfo has a public field called sourceDir which is the full path (a String) to the location of the source directory for that application. Create a File from this String, and you should be able to navigate to your package within the source directory. Once there, you can use the method from my original answer to find the classes you are looking for.

String applicationSourceDir = 
    getPackageManager().getApplicationInfo(androidPackageName, 0).sourceDir;

/EDIT

You should be able to use the ClassLoader.getResource(String) to get a URL to your specific package (the passed in String being the package name you are interested in delimited by path separators rather than periods). With this URL you can then call getFile(), from which you can create a Java File to the package folder. Call packageFile.listFiles() from there, and you have your classes/subpackages.

Be recursive with the subpackages, and with the classes find the Class object using the static Class.forName(String) method.

nicholas.hauschild
  • 42,483
  • 9
  • 127
  • 120
  • Are you sure you can access the dex files like this? `ClassLoader.getResource('com/foo')` is returning `null` and based on some quick searching this seems like something that isn't actually possible. Otherwise it would be an excellent solution. – Tyler Treat Jul 11 '12 at 04:00
  • This is an exclusively native approach I have used to do this in a Java application. I assumed that it would work in Android, due to there being no dependency on external libraries. – nicholas.hauschild Jul 11 '12 at 04:07
  • I see. Unfortunately in Android the classes are compiled into a `classes.dex` file, so it looks like this isn't an option. I had high hope for it too! – Tyler Treat Jul 11 '12 at 04:09
  • I'm afraid that doesn't work either since the "source directory" is actually just the APK. It still seems like my best option is to load the APK as a `DexFile` and iterate over all of its class entries like I described in the updated question. It works, but I don't like it and it's rather slow. – Tyler Treat Jul 13 '12 at 17:22
0

Take a look at Vogar's ClassPathScanner. It uses it to find test cases on the class path.

Jesse Wilson
  • 39,078
  • 8
  • 121
  • 128
  • Thanks, Jesse. This appears to be a really good solution, but it seems that the `java.class.path` system property just returns `.`, so I'm unable to create a dex file from that. What am I not getting? – Tyler Treat Jul 11 '12 at 14:32
0

In your java build process incorporate the class path scanning, generating injection data/code. This could then be ported too to Dalvik. It is even more efficient that dynamic scanning.

Joop Eggen
  • 107,315
  • 7
  • 83
  • 138