Supporting older devices while using newer versions of the Android SDK in App Inventor
Started by: José Dominguez (josmasflores@gmail.com) on July 1st, 2013
Introduction
App Inventor supports all devices from API Level 4, Platform version 1.5 (as of Feb 2016). Some functionality added in newer versions of the Android SDK can be made available to users of newer devices, but still needs to be gracefully handled in older ones. An example of this is the use of cookies in the Web component. Cookies support was added on API Level 9 (Gingerbread), and applications developed with App Inventor can make use of them in devices running at that (or a higher) level, while showing a short notice in devices lower than 9, stating that the particular functionality can not be used.
Current MinSDK
The android manifest file is generated in Compiler.java. Currently apps default to Api Level 4, and the Companion is built with this version too.
If you are compiling the server from sources, you will see an additional component in the ‘Experimental’ category called Firebase. When using the Firebase component, the minSDK has to be reset up to Level 10.
The problem and the Solution
Backwards compatibility is a desired quality in most system, although it generally comes coupled with some tradeoffs. The solution used in App Inventor is similar to the wrapper class explained in this (rather old) blogpost.
The problem
Classes added to newer versions of the SDK will not be available in devices running an older stack, so, if those classes are used at runtime, the app will simply crash when trying to execute them (more on linking and class initialisation in the blog post linked above). There are different ways around this problem, from not allowing installation of the app in devices by stating a minimum SDK, to allow installation and handle devices in a different way, depending on whether they can use certain parts of the SDK or not. This is the way things are done in App Inventor, by providing a number of wrapper classes that allow component developers to decide what to do when certain functionality is not available. A couple of examples of how to do that are provided in the next section.
The solution
The solution is to use helper classes. These have names based on the version of Android that they represent. For example: DonutUtil.java, EclairUtil.java, FroyoUitl.java, GingerbreadUtil.java and others (for newer devices) all located at components/runtime/util/. These classes encapsulate particular behaviours that only exists in a particular API level (and up). Making the component class use one of these utility classes, instead of the API class directly, allows for additional design around the constraint of a particular non available piece of functionality in certain devices. Let’s see that with a couple of examples:
GingerbreadUtil in the Web component: Cookies support was added in Gingerbread (API Level 9). In the Documentation we can see this (see screenshot): http://developer.android.com/reference/java/net/CookieManager.html
The second line at the top right reads: ‘Added in API Level 9’. Level 9 is Gingerbread (consult API levels).
The following code can be seen in the Web Component:
cookieHandler = (SdkLevel.getLevel() >= SdkLevel.LEVEL_GINGERBREAD)
? GingerbreadUtil.newCookieManager()
: null;
|
The idea being that, if the device supports the use of cookies, then we return a Cookie Manager to handle that (being that cookie manager provided by the Util class). If not, we initialise the cookieHandler to null. Later in the code, there is a check for nulls that will handle older devices. In this case, as the code below shows, an appropriate message about the functionality not being supported is displayed to the user.
if (allowCookies && cookieHandler == null) {
form.dispatchErrorOccurredEvent(this, "AllowCookies",
ErrorMessages.ERROR_FUNCTIONALITY_NOT_SUPPORTED_WEB_COOKIES);
}
|
The main difference in behaviour between a device running Gingerbread or higher, and a device running Froyo or lower is that users with the first device will be using cookies automatically, and users of the second type of devices will see a message in their screens explaining that their device does not support cookies (this is handled by the error message dispatched through the Form class).
A different example can be found in the App Inventor codebase through the use of FroyoUitl.java in the OrientationSensor component. The code reads:
private int getScreenRotation() {
Display display =
((WindowManager) form.getSystemService(Context.WINDOW_SERVICE)).
getDefaultDisplay();
if (SdkLevel.getLevel() >= SdkLevel.LEVEL_FROYO) {
return FroyoUtil.getRotation(display);
} else {
return display.getOrientation();
}
}
|
According to the documentation, the method getOrientation was deprecated with the introduction of API level 8 (Froyo):
The method getRotation() should be used instead, but this is only available from level 8 and higher:
FroyoUtil.java defines the method getRotation as:
public static int getRotation(Display display) {
return display.getRotation();
}
|
The main difference in behaviour between a device running Froyo or higher, and a device running Eclair or lower is that users with the first device will have a more accurate report of the orientation value of their device (according to the docs ‘Returns the rotation of the screen from its "natural" orientation’) than users of the second type of devices.
There are more examples in the code, for instance DonutUtil.java adds some handling for animations that were not available in previous versions of the Android stack.
Implementing a solution
Two steps are needed to implement a solution, the first being to choose how the app should behave in non-supported devices, and second, implementing the solution through one of the Uitl classes in the codebase.
Calling through one level of indirection adds a slight overhead, but it shouldn’t be a big problem.
Providing different experiences to the users of different devices is not ideal, but unfortunately, there’s no way around it, and it’s better than not allowing to install the app in some devices (unless the functionality is so central to the app that it wouldn’t make sense without it!). Changing the SDK levels can be done in Compiler.java, but it’s not something that should be done without consulting the team.
Note: there might be places in the sources where differences in SDK levels might be handled in different ways. The one described in this document is the preferred one.
댓글 없음:
댓글 쓰기