Learn Java for Android Development: Reflection Basics 5/13

In this tutorial, you’ll become familiar with the concept of Java reflection: the ability of a class or object to examine details about its own implementation programmatically.
Android applications are written in the Java, a programming language that supports reflection—the ability of an object to examine itself. In this tutorial, you’ll learn the basics of Java reflection, including how to inspect the methods and fields of a given class, check for the availability of specific methods, and other practical tasks you may need to use when developing for different versions of the Android SDK.

What You’ll Need

Technically, you don’t need any tools to complete this tutorial but you will certainly need them to develop Android applications.
To develop Android applications (or any Java applications, for that matter), you need a development environment to write and build applications. Eclipse is a very popular development environment (IDE) for Java and the preferred IDE for Android development. It’s freely available for Windows, Mac, and Linux operating systems.
For complete instructions on how to install Eclipse (including which versions are supported) and the Android SDK, see the Android developer website.

Why Use Reflection?

Reflection gives developers the flexibility to inspect and determine API characteristics at runtime, instead of compile time. Within the security constraints imposed by Java (e.g. use of public, protected, private), you can then construct objects, access fields, and invoke methods dynamically. The Java Reflection APIs are available as part of the java.lang.reflect package, which is included within the Android SDK for developers to use.
So what does this have to do with Android development? Well, with each new version of the Android SDK, classes, interfaces, methods, etc. are added, updated, and (less frequently) removed. However, Android developers often want to target devices running different versions of Android with a simple application package. To do this, Android developers may use reflection techniques to determine, at runtime, if a specific class or method is available before trying to use it. This allows the developer to leverage new APIs where available while still supporting the older devices—all in the same application.

Inspecting Classes

Java classes are represented at runtime using the Class (java.lang.Class) class. This class provides the starting point for all reflection APIs. Within this class, you’ll find many methods for inspecting different aspects of a class, such as its fields, constructors, methods, permissions, and more. You can also use the Class method called forName() to load a non-primitive class (e.g. not int, but Integer) by name dynamically at runtime, instead of at compile time:
String sClassName = "android.app.NotificationManager";
try {
    Class classToInvestigate = Class.forName(sClassName); 

    // Dynamically do stuff with this class
    // List constructors, fields, methods, etc.

} catch (ClassNotFoundException e) {
    // Class not found!
} catch (Exception e) {
    // Unknown exception
}
The class (in this case, NotificationManager) need not have the corresponding import statement in your code; you are not compiling in this class into your application. Instead, the class loader will load the class dynamically at runtime, if possible. You can then inspect this Class object and use the reflection techniques described in the rest of this tutorial.

Inspecting the Constructors Available Within a Class

You can inspect the constructors available within a given Class. To get just the constructors that are publicly available, use getConstructors(). However, if you want to inspect those methods specifically declared within the class, whether they are public or not, use getDeclaredConstructors() instead. Both methods return an array of Constructor (java.lang.reflect.Constructor) objects.
For example, the following code iterates through the declared constructors of a class:
Constructor[] aClassConstructors = classToInvestigate.getDeclaredConstructors();
for(Constructor c : aClassConstructors){
    // Found a constructor c  
}
Once you have a valid Constructor object, you can inspect its parameters and even declare a new instance of the class using that constructor with the newInstance() method.

Inspecting the Fields Available Within a Class

You can inspect the fields (or attributes) available within a given Class. To get just the methods that are publicly available, including inherited fields, use getFields(). However, if you want to inspect those fields specifically declared within the class (and not inherited ones), whether they are public or not, use getDeclaredFields() instead. Both methods return an array of Field (java.lang.reflect.Field) objects.
For example, the following code iterates through the declared fields of a class:
Field[] aClassFields = classToInvestigate.getDeclaredFields();
for(Field f : aClassFields){
    // Found a field f
}
You can also check for a specific public field by name using the getField() method. For example, to check for the EXTRA_CHANGED_PACKAGE_LIST field of the Intent class (which was added in API Level 8, or Android 2.2), you could use:
String sClassName = "android.content.Intent";
try {
    Class classToInvestigate = Class.forName(sClassName);
    String strNewFieldName = "EXTRA_CHANGED_PACKAGE_LIST";
    Field newIn22 = classToInvestigate.getField(strNewFieldName);

} catch (ClassNotFoundException e) {
    // Class not found
} catch (NoSuchFieldException e) {
    // Field does not exist, likely we are on Android 2.1 or older
    // provide alternative functionality to support older devices
} catch (SecurityException e) {
    // Access denied!
} catch (Exception e) {
    // Unknown exception
}
Once you have a valid Field object, you can get its name using the toGenericString() method. If you have the appropriate permissions, you can also access the value of that class field using the appropriate get() and set() methods.

Inspecting the Methods Available Within a Class

You can inspect the methods available within a given Class. To get just the methods that are publicly available, including inherited methods, use getMethods(). However, if you want to inspect those methods specifically declared within the class (without inherited ones), whether they are public or not, use getDeclaredMethods() instead. Both methods return an array of Method (java.lang.reflect.Method) objects.
For example, the following code iterates through the declared methods of a class:
Method[] aClassMethods = classToInvestigate.getDeclaredMethods();
for(Method m : aClassMethods)
{
    // Found a method m
}
Once you have a valid Method object, you can get its name using the toGenericString() method. You can also examine the parameters used by the method and the exceptions it can throw. Finally, if you have the appropriate permissions, you can also call the method using the invoke() method.

Inspecting Inner Classes

You can inspect the inner classes defined within a Class using getDeclaredClasses() method. This method will return an array of Class (java.lang.class) objects declared within the parent class. These classes can then be inspected like any other.

Inspecting Member Modifiers

You can also inspect the flags and security settings—called modifiers—associated with a given Class, Field, or Method using the getModifiers() method. Interesting modifiers include whether the component is public, private, protected, abstract, final, or static (amongst others).
For example, the following code checks the security modifiers of a class:
int permissions = classToInvestigate.getModifiers();

if(Modifier.isPublic(permissions)) { 
    // Class is Public 
}
if(Modifier.isProtected(permissions)) {
    // Class is Protected 
}
if(Modifier.isPrivate(permissions)) { 
    // Class is Private 
}
Keep in mind that you cannot dynamically access or invoke any class, method, or field using reflection that you would not normally be able to access at compile-time. In other words, regular class security is still applied at runtime.

Inspecting Class Metadata

You can also inspect the metadata—called annotations—associated with a given class, field or method using the getAnnotations() method. Interesting metadata associated with a class might include information about deprecation, warnings, and overrides, among other things.
For example, the following code checks the metadata available for the AbsoluteLayout class. Since this class was deprecated in Android 1.5, one of the annotations returned is @java.lang.Deprecated() when this code is run on Android 2.2:
String sClassName = "android.widget.AbsoluteLayout";
try {
    Class classToInvestigate = Class.forName(sClassName);

    Annotation[] aAnnotations = classToInvestigate.getDeclaredAnnotations();
    for(Annotation a : aAnnotations)
    {
        // Found an annotation, use a.toString() to print it out
    }

} catch (ClassNotFoundException e) {
    // Class not found!
} catch (Exception e) {
    // Handle unknown exception!
}
Similarly, you could simply check for the existence of a specific annotation, such as deprecation, by its type:
if(classToInvestigate.isAnnotationPresent(java.lang.Deprecated.class) == true)
{
    // Class is deprecated!
}

Reflection: Handy for Debugging

You can also use reflection to assist with debugging. For example, you might want to use the class keyword to access the underlying class data for a given type:
import android.app.Activity;
…
String strClassName = Activity.class.getName();    // android.app.Activity
You can also get class information from a variable instance using the getClass() method of the Object class (which is therefore inherited by all classes in Java):
String silly = "Silly String!";
Class someKindOfClass = silly.getClass();
String strSillyClassName = someKindOfClass.getName();    // java.lang.String
If you want to check the class of a variable, using instanceof is more appropriate. See the previous tutorial on instanceof for more details.
Similarly, you might want to use the getClass() method with the this keyword to check the name of the class you’re currently in and include this information as part of your debug logging to LogCat:
String strCurrentClass = this.getClass().getName();    // e.g. the current Activity
Log.v(strCurrentClass, "Debug tag is current class.");

Why Not To Use Reflection

As you’ve seen, reflection can be used to great effect, especially when you are unsure if a specific class or method is available at compile time. Reflection does, however, have some drawbacks, including reduced performance and the loss of the strong typing and safe coding practices enforced at compile time. It’s best to use reflection sparingly, but do use it when needed.

Wrapping Up

Reflection is a powerful tool that Java developers can use to explore packages and APIs programmatically at runtime. While reflection operations come at a cost, they give the developer the flexibility that is sometimes essential for getting the job done. Android developers frequently use these simple reflection techniques to test for the availability of specific classes, interfaces, methods, and fields at runtime, enabling them to support different versions.

No comments:

Post a Comment

UA-50246500-1