Implementing Spring-like Package Scanning on Android

I am trying to implement a package scan function similar to Spring component-scan for the Android platform that I am developing. Basically, I would like to be able to specify the base package, for example. com.foo.bar and get all instances of Class that have a specific annotation. I don’t want to register every component with my infrastructure, as this will defeat the purpose of automatic scanning.

Based on my research, it seems that it is not possible for Java to retrieve resources given the package name using reflection. However, I briefly looked at the structure of Reflections , and I wonder if there is an Android compatible equivalent. If not, there may be a slightly less obvious way to accomplish what I want to do.

I looked a bit at Spring's source to see how they accomplished this, but I don't think they will work in the Dalvik runtime.

Update

Currently, the code below was the best I can do to get all classes containing a specific annotation, but frankly, this is a pretty bad solution. This makes some really unsafe assumptions about 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; } 
+4
source share
5 answers

I share the opinion of Joop Eggen and I think his approach is good. In Android, I try to avoid the usual features of a web application that lead to a long launch of the application. I do not use reflection or packet scanning.

But if you want ... if I understand him correctly, you want to have annotation for the class. Instead of using annotations, you can also use marker interfaces (to have more features).

1) Look

2) AndroidAnnotations

I would prefer to use AndroidAnnotations (perhaps integration into AndroidAnnotations is preferable): it automatically adds additional compilation that generates source code using the standard Java annotation processing tool. Therefore, instead of scanning at runtime, you execute code based on annotations generated at compile time.

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

Scan function not available, see this thread.

3) Writing your own annotation processor

+3
source

I wanted to find the whole subclass at runtime. So I was looking for an Android class scan. This is my last code from what I have collected on the Internet. You will get this 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(); } } } 
+4
source

EDIT:

I found this problem in the tray for the Android tracker. ClassLoader.getResource(String) seems to work as expected, as it returns null . This is expected because DalvikVM does not save resources after compilation. This article lists workarounds, but there may be another way to access the classes you want.

Use PackageManager to get an instance of ApplicationInfo . ApplicationInfo has a public field called sourceDir , which is the full path (a String ) for the location of the source directory for this expression. Create a File from this String , and you can navigate to your package in the source directory. After that, 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 can use ClassLoader.getResource(String) to get the URL for your specific package (the name of the package you are interested in is passed to String , separated by path delimiters, not periods). Using this URL you can call getFile() , from which you can create a Java File in the package folder. Call packageFile.listFiles() from there, and you have your classes / subpackages.

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

0
source

Take a look at the Vogar ClassPathScanner . He uses it to search for test cases on the way to the class.

0
source

In your Java build process, class path scanning, generated data / injection code, is included. Then it could be placed in Dalvik. Dynamic scanning is even more efficient.

0
source

All Articles