How to detect the presence of an optional API (such as the Location API) at run-time, and use it only if it is available. For example, to develop an application that works on device without the Location API, but can become location-aware on devices that do have the Location API, without requiring multiple builds.
This example will refer to the Location API, but applies equally to other APIs.
Contents |
This technique should work on any Java virtual machine that complies with the VM specification including the CLDC specification). However, there may be some VMs that perform some kind of install-time verification, optimization or re-compilation of the Java bytecode, which prevent this technique from working. It is known not to work on the Motorola T720 (an old, MIDP-1 device).
In order to execute the code in a Java class, five steps must be completed.
Resolution is the key step. If we use a class that references a missing API, this is the point where the code will break. We need to make sure that this process only takes place if the API is present.
I didn't say when resolution takes place. That's because it varies between different JVM implementations. Different VMs perform this step at different times. Here are the two extremes:
And there are variations in between these two.
On VMs that use late resolution, it may be enough not to execute any code that refers to the missing API. However, such code may break on VMs that use early resolution.
To be sure our code will work, we need to make sure that no class is ever loaded that refers to the optional API, unless that API is present.
This is optional, but makes the technique "safer". Let's say our application is in the package:
package com.mycompany.myapplication;
No classes in this package will have any reference to the location API. We'll put all the location services code in a separate package:
package com.mycompany.locationservices;
By doing this, we can stop code in the application from access location code directly, using package-privacy. This will make more sense later.
We need to create one abstract class in the location services package. This will be the only public class in that package, and is the only one that must not use any part of the location API. It will define an interface through which we will use location services.
package com.mycompany.locationservices;
public abstract class LocationProvider {
// define the interface through which we'll access location information
public abstract double getLatitude();
public abstract double getLongitude();
/**
* this method will be used to get access to a LocationProvider class
*/
public static LocationProvider getProvider() throws ClassNotFoundException {
LocationProvider provider;
try {
// this will throw an exception if JSR-179 is missing
Class.forName("javax.microedition.location.Location");
// more about this class later
Class c = Class.forName("com.mycompany.locationservices.LocationImplementation");
provider = (LocationProvider)(c.newInstance());
} catch (Exception e) {
throw new ClassNotFoundExcepion("No Location API");
}
return provider;
}
}
This class makees no reference to JSR-179 classes, nor to any other class from the locationservices package, except for references in Strings in calls to Class.forName(). These will not cause classes to load until they are executed, and then they will throw exceptions (which can be caught and handled) if there is a problem.
Since this will be the only public class in the package, it will be the only class we can refer to from the main application. Because it doesn't reference any location classes, it's safe to reference it.
The LocationImplementation class will not be public, and so must be in the same package as LocationProvider to avoid an IllegalAccessError.
Now, we need the afore-mentioned LocationImplementation class.
package com.mycompany.locationservices;
import javax.microedition.location.*;
class LocationImplementation extends LocationProvider {
LocationImplementation() {
/*
We must have a no-parameter constructor, or Class.newInstance() cannot
create an instance of this class
*/
}
// implement the abstract LocationProvider methods
public double getLatitude() {
// do whatever is needed
}
public double getLongitude() {
// do whatever is needed
}
}
Note that this class is not public. This prevents classes outside this package from using it, and is a safe guard against accidentally using it elsewhere (and ruining our cunning plan).
Any other classes needed to support this can be created in this package, but make them package-private (not public) too.
Using this in the application is now easy.
try {
LocationProvider provider = LocationProvider.getProvider();
double latitude = provider.getLatitude();
double longitude = provider.getLongitude();
} catch (ClassNotFoundException cnfe) {
// no location API on this device
}
No related wiki articles found