Reflection is one of the many aces up the sleeve when it comes to taking apart Android applications and getting them running in a state that
suits you. To put it simply reflection is an API that can be used to access, examine, and modify objects at runtime – this includes fields, methods,
classes, and interfaces.
Some examples of components reflection can be used on when working from Java (or Kotlin) include:
- Classes – A class is a blueprint/template where when used individual objects can be created from them.
- Methods – A method is a segment of code, with a specific purpose, which is run when called and can be part of a class or standalone.
- Constructors – A constructor is a special type of method that is used as part of the initialization of an object (e.g., a class) to set variables and call methods.
- Interfaces – An interface is an abstract class that contains a collection of methods with empty bodies.
Class Loading
Java Class Loaders are a component of the Java Runtime Environment (JRE) which load Java classes into a Java Virtual Machine (JVM)/Dalvik Virtual Machine (DVM)/Android Runtime (ART). Not all classes are loaded simultaneously, nor with the same ClassLoader. The context method
getClassLoader() can be used to get the current class loader. There are several types of class loading in Android, these being:
- PathClassLoader – This is used by the Android system for its system and application class loader(s).
- DexClassLoader – This loads file types containing a .dex file (e.g., .jar and .apk or .dex file directly). These .dex files (Dalvik executable) contain Dalvik bytecode.
- URLClassLoader – This is used to retrieve classes or resources via URL paths. Paths ending with / are assumed to be directories, while otherwise they are assumed to be .jar files.
Below shows how to retrieve the current application context class loader:
ClassLoader loader = getApplicationContext().getClassLoader();
Below shows how a new class loaded can be created:
dexLoader = new DexClassLoader(filePath, dexCacheDirectory.getAbsolutePath(), null, loader); //After creating a dex class loader, choose the class to load, as a string: loadedClass = dexLoader.loadClass("me.jamesstevenson.dexloadable.MainActivity"); //alter path for your use case //At this stage the uninitialized class can be used as normal. The following shows how to safely initialize this class: initialisedClass = loadedClass != null ? loadedClass.newInstance() : null; method = loadedClass != null ? loadedClass.getMethod("loadMeAndIllTakeContext", Context.class) : null; Object methodResponse = method != null ? method. invoke(initialisedClass, getApplicationContext()) : null;
Reflection Examples
The two supporting classes for these reflection examples can be found on GitHub under DeviceData.Java and Loadable.Java.
Initializing a class:
try { Object initialisedDeviceData= DeviceData.class. newInstance(); initialisedDeviceData.getClass().getDeclaredMethod("setDeviceInfo").invoke(initialisedDeviceData); String model = (String) initialisedDeviceData.getClass(). getDeclaredField("model").get(initialisedDeviceData); Log.v(TAG, model); } catch (...) { // Todo catch all exceptions }
Retrieving class methods:
Static method example: try { Method getDeviceName = Loadable.class.getDeclaredMethod("getDeviceName"); getDeviceName.setAccessible(true); Log.v(TAG,(String) getDeviceName.invoke(Loadable.class)); } catch (...) { // Todo catch all exceptions }
Retrieve all methods for a class:
getMethods() example: for (Method method : Loadable.class.getMethods()){ Log.v(TAG, method.getName()); }
The following is an example of constructing a class with a private constructor:
try { Constructor<?> constructor = Loadable.class.getDeclaredConstructor(Context.class, long.class); constructor.setAccessible(true); Object instance = constructor.newInstance(getApplicationContext(), (Object) 12); // constructor takes a context and an id. Field uniqueIdField = instance.getClass().getDeclaredField("uniqueId"); uniqueIdField.setAccessible(true); long uniqueId = (long) uniqueIdField.get(instance); Log.v(TAG, ""+uniqueId); } catch (...) { // Todo catch all exceptions }
Instance class example:
try { // The loadable class has a static method that can be used to construct it in this example Object instance = Loadable.class.getDeclaredMethod("construct", Context.class).invoke(Loadable.class, getApplicationContext()); // Retrieve the field device data which is the class we're looking to get the data of. Field devicdDataField = instance.getClass().getDeclaredField("deviceData"); devicdDataField.setAccessible(true); Object initialisedDeviceData = devicdDataField.get(instance); // After accessing the value from the field we're looking to access the fields of, we can use the same type of reflection again after getting it's class Field modelField = initialisedDeviceData.getClass().getDeclaredField("device"); modelField.setAccessible(true); String model = (String) modelField.get(initialisedDeviceData); Log.v(TAG,model); } catch (...) { // Todo catch all exceptions }
Read More
This article was derived from a chapter in my book Android Software Internals Quick Reference. If you found it interesting please consider picking the book up for yourself!