Why are annotations under Android such a performance issue (slow)?

Solution 1:

Google has acknowledged the issue and fixed it "post-Honeycomb"

https://code.google.com/p/android/issues/detail?id=7811

So at least they know about it and have supposedly fixed it for some future version.

Solution 2:

Here's a generic version of Gray's & user931366's idea:

public class AnnotationElementsReader {

    private static Field elementsField;
    private static Field nameField;
    private static Method validateValueMethod;

    public static HashMap<String, Object> getElements(Annotation annotation)
            throws Exception {
        HashMap<String, Object> map = new HashMap<String, Object>();
        InvocationHandler handler = Proxy.getInvocationHandler(annotation);
        if (elementsField == null) {
            elementsField = handler.getClass().getDeclaredField("elements");
            elementsField.setAccessible(true);
        }
        Object[] annotationMembers = (Object[]) elementsField.get(handler);
        for (Object annotationMember : annotationMembers) {
            if (nameField == null) {
                Class<?> cl = annotationMember.getClass();
                nameField = cl.getDeclaredField("name");
                nameField.setAccessible(true);
                validateValueMethod = cl.getDeclaredMethod("validateValue");
                validateValueMethod.setAccessible(true);
            }
            String name = (String) nameField.get(annotationMember);
            Object val = validateValueMethod.invoke(annotationMember);
            map.put(name, val);
        }
        return map;
    }

}

I've benchmarked an annotation with 4 elements.
Millisecond times for 10000 iterations of either getting values of all of them or calling the method above:

     Device        Default  Hack
HTC Desire 2.3.7    11094   730
Emulator 4.0.4      3157    528
Galaxy Nexus 4.3    1248    392

Here's how I've integrated it into DroidParts: https://github.com/yanchenko/droidparts/commit/93fd1a1d6c76c2f4abf185f92c5c59e285f8bc69.

Solution 3:

To follow up on this, there's still a problem here when calling methods on annotations. The bug listed above by candrews fixes the getAnnotation() slowness, but calling a method on the annotation is still a problem due to the Method.equals() issues.

Couldn't find a bug report for Method.equals() so I created one here: https://code.google.com/p/android/issues/detail?id=37380

Edit: So my work around for this (thanks for the ideas @Gray), is actually pretty simple. (this is trunkcated code, some caching and such is omitted)

annotationFactory = Class.forName("org.apache.harmony.lang.annotation.AnnotationFactory");
getElementDesc = annotationFactory.getMethod("getElementsDescription", Class.class);
Object[] members = (Object[])getElementDesc.invoke(annotationFactory, clz); // these are AnnotationMember[]

Object element = null;
for (Object e:members){ // AnnotationMembers
    Field f = e.getClass().getDeclaredField("name");
    f.setAccessible(true);
    String fname = (String) f.get(e);
    if (methodName.equals(fname)){
        element = e;
    break;
    }
}

if (element == null) throw new Exception("Element was not found");
Method m = element.getClass().getMethod("validateValue");
return m.invoke(element, args);

You mileage will vary based on use, but in may case this was about 15-20 times faster then doing it the "right way"

Solution 4:

I think if you manage to change the RUNTIME retention policy, it should not be that slow.

EDIT: I know, for your project that may not be an option. Perhaps it is more a problem of what you are doing with that annotation rather than bad performance in general.