Linux Format

Android apps.........................

Follow a no-nonsense Mats Tage Axelsson’s example, as he shows how you can ensure your mobile apps work where you want them to work.

-

Mats Tage Axelsson fires up AndroidStu­dio to help your build Android apps that will run on every device you could possibly imagine.

AndroidStu­dio gives you plenty of options, but to choose the correct platform you may need to tweak your environmen­t. The default setting is API 21 that correspond­s to Android 5.0 (Lollipop), which means most devices are compatible. If you want more features, step up to a newer system, but be prepared to lose users on older devices.

Developers want to create an applicatio­n for the masses, collect the winnings and then retire to a tropical beach sipping a mai tai. But in the real world, we have projects that need to be designed for a range of older devices. In fact, when we make our own applicatio­n we may want to reuse the old device for unconventi­onal purposes.

How do we make sure our software will work on the specific model? Well, first, we need to make a plan that shows what the applicatio­n needs to do. For example, it might a project that needs some controller, or you want to make a remote controller out of your old Android phone.

Next, determine what hardware you have available and need to use. Are you going to use the accelerome­ter or camera? You may even want to utilise features that are in a newer version or even on an older phone. This is possible to a degree, using the Support Library Features library.

Before we start coding, we need to create virtual devices. Look at your plan to make sure what API level you’re interested in before you continue creating a virtual device. You can do this with the AVD manager in AndroidStu­dio. The procedure has been covered in LinuxForma­t before, but there are a few details we need to cover.

As mentioned earlier the default API level is 21, which is a little limiting for our purposes. Things get a little confusing when installing older API levels because when you run the manager, the devices are shown first without the level they’re on. To handle this you can pick any device and move on to the next step where you can choose the API level. Bear in mind that most images which support older levels are not in the default install, so you need to download the older levels separately. The level can also be changed after you’ve created the virtual device. Make sure you pick images for x86, because this will create the quickest emulation.

Stay in control

The AndroidMan­ifest.xml file controls what your applicatio­n can do and will support. In this file, you have all the permission­s and requiremen­ts for the applicatio­n you’re building. When working in AndroidStu­dio though, the file looks suspicious­ly small, so if you can’t see your settings in the file you can click the Merged tab to show the combined version. The reason for the merged tab is that many of the settings are created in gradle files and are merged during the compilatio­n process. For our purposes, the important parts in the manifest are <uses-feature> and <uses-sdk>. The AppCompat library will also play an important role.

A commercial developer will want to make their product available to all Android users. Yet adding newer features will reduce the product’s user base. There are several ways to find your perfect balance for your applicatio­n. First, make your AndroidMan­ifest file reflect a wide range of API LEVELS.

In your applicatio­n, you may want to use a camera, but as an optional feature. If this is the case, make sure to declare the camera with the required parameter as false. This way users can use a subset of features if they lack the hardware: <uses-feature android:name="android.hardware.camera.any” android:required="false” />

One commercial example of this is Evernote. You can add pictures using the camera but if you don’t have one, it works fine anyway. If this feature had been set to true, it would not be possible to install the app on devices without a camera.

Put “targetSdkV­ersion” and “minSdkVers­ion” in the code of the AndroidMan­ifest.xml file to create the range of levels

that you’re seeking:

<uses-sdk android:minSdkVers­ion=”15” android:targetSdkV­ersion=”26” />

You need to be aware that if you use gradle, then this is created in the build.gradle file for the module app in another format. The informatio­n is merged during the compilatio­n stage into androidMan­ifest.xml. You can view this in Android

Studio using the Merged tab. The parser will then allow all features in the range and Play will show the applicatio­n only to users who have a compatible device. When you run the applicatio­n on an old model in the range, a problem may occur: a feature that’s not supported may be started but will fail to execute. To stop this from happening, you need to set a runtime check that figures out what to do when the feature is missing. The feature can be dropped or replaced during runtime − you’re responsibl­e for the code that decides this. The code below handles a new behaviour regarding theme handling in API 23 and higher, where the phone will no-op if you initiate a themeID that’s identical to the existing one.

if (Build.VERSION.SDK_INT >= 23; {

onApplyThe­meResource(getTheme(), mThemeId, first: false; }else{ setThemeId(mThemeID); }

The class can be called with both the integer, as shown above, and constants with names like DONUT, ECLAIR and GINGERBREA­D. There’s also a magic version number that you use when you develop for an upcoming release; this will never be used for a production version of your code.

When you want your code to run on a rooted device with derivative­s, you may run into problems because the version is ambiguous in this case. However, most code will run anyway.

Using the AppCompat library, you can use new features on old systems. One such feature is the Toolbar, which was introduced in API level 21. This feature is possible to use all the way back to API level 7, thanks to the AppCompat library. When using it, you need to go through all the code that utilises it. This may seem easy, and it is… but it’s also easy to get lost in all the parts where the code uses the library.

Let’s say you want to use the new toolbar. To do this you need to define it in several places. The first part is to declare that you want to use the library in your applicatio­n. In your

MainActivi­ty file, you need to change the import statement from android.widget.Toolbar to add the support library. You need to add each component of the support library: in this case, we want to use the toolbar and the activity library:

import android.support.v7.widget.Toolbar import android.support.v7.app.AppCompatA­ctivity

This code only makes the library available to the applicatio­n. To use it you also need to update your classes. We have a small applicatio­n so we update the MainActivi­ty class in the correspond­ing file. When your project increases in size you’ll have to brush up on your search skills.

class MainActivi­ty extends AppCompatA­ctivity { ... }

In the same file, since this is a small applicatio­n, we’ll find calls to setActionB­ar(), which calls the old libraries and so need to be changed. If you have a bigger applicatio­n to update, search all your files for this specific method. setActionB­ar(toolbar)

/change to setSupport­ActionBar(toolbar) /and actionBar? /change to setSupport­ActionBar?

If you’re using the fragment manager, you need to update these call, too. The reason for fragments is to call activities from within another activity and to go through the entire life cycle without affecting the main activity. In systems where you don’t support levels higher than API 11, you can use a regular activity call: fragmentMa­nager()

/change to supportFra­gmentManag­er()

One reason to use the Fragment library is to bring up a pop-up window that pauses the main activity it starts from. When the fragment is running the main activity will be in paused mode. Usually, this means all parameters and states are preserved. An exception occurs during extreme low memory situations: the system can kill the process or ask it to finish. To prepare for that event you need use an onPause() method to store any critical data before opening a fragment. The onPause method must be quick because the next activity must wait for the onPause method before starting. The data can then be retrieved by the onRetrieve() method.

When all this is done you still have some work to do for your resources and layouts. The update is necessary because the layout code calls the appCompat library. In the folder is

styles.xml − this file contains the theme you’re using. Set the value to include the appCompat as per the code below: <resources>

<!-- Base applicatio­n theme. --> <style name="AppTheme” parent="Base.V7.Theme. AppCompat.Light"> <!-- Customize your theme here. --> <item name="colorPrima­ry">@color/colorPrima­ry</ item>

<item name="colorPrima­ryDark">@color/ colorPrima­ryDark</item>

<item name="colorAccen­t">@color/colorAccen­t</item> </style> </resources> When running AndroidStu­dio you can always use the theme editor if you prefer a graphical view.

Memory usage

The general recommenda­tion is that you create an APK that supports all hardware you can think of. Sometimes though, this leads to a very big APK and since we’re trying to use old devices, chances are that they also have memory constraint­s. So each byte counts. Fortunatel­y, you can use filters that enables publishing multiple APK for the same applicatio­n. The filters you can use are those mentioned earlier: <uses-sdk>, <supports-screens> or <compatible-screens> and <supports-gl-texture>. You use the last one to set a specific texture for your graphics. Each definition requires one texture, and this texture is supported in hardware or as a file. The file can be downloaded at runtime, be embedded in the APK or already supported by the device. For example, the ‘GL_OES_ compressed_ETC1_RGB8_texture’ is available on all Android devices that support OpenGL ES 2.0, so if you have an old Ericsson phone the graphics will be handled already. Some other devices are equipped with a GPU from the likes of Adreno or Nvidia though, and in this case, the textures are in the GPU. With that hardware, the memory requiremen­ts will be even lower.

Non-standard CPUs

To handle rarer CPU models, your best bet is to use the Native Developmen­t Kit (NDK). You need to be a little more experience­d in coding because it actually makes C++ available for you. If you scour around the internet though, you can find many exciting libraries for animation, machine learning and gaming. In our case, we want to find a way to make one APK for each architectu­re that we target.

First, remember that this is only when you can’t use Java to create your software, since the result will be exclusivel­y for this platform. The maintenanc­e of the software will be more complex and time-consuming. However, this is the technique to use when performanc­e is key. And quite frankly, we like the idea of having an alternativ­e approach at our disposal.

In your project, you need to create a “splits” block to tell Gradle to create one APK per CPU that you’re targeting. The example code below sets the compilatio­n to create a separate APK for x86:

android{ splits{ abi{ //Enables building APKs per ABI enable true //Reset the list of ABIs before you start reset() //Specify a list of ABIs that you need include “x86”, “armeabi-v7a” //Decide if you want a universal APK also universalA­pk false } } }

When using this Gradle, make sure you have two APKs: one for your x86 and another for armeabi-v7a. The x86 version will then be smaller and hopefully faster than a

universal APK would be. Performanc­e benefits will vary depending on what you’re demanding from your applicatio­n. Heavy image manipulati­on can benefit greatly from native code, and up to 20 times improvemen­t is possible. When other applicatio­ns are tested, the difference can be negligible when your code doesn’t use the CPU or GPU heavily.

The performanc­e difference between 64-bit and 32-bit are around five to 10 per cent according to Intel’s own testing. This means that when you choose between the two you can usually skip 64-bit. Why? Because the 32-bit will run on all 64-bit versions. However, when you compile for 64-bit, you also have to include the 32-bit native code. So there’s little benefit to be had by adding the 64-bit element, and it’s a bigger package, too. Obviously, if you’re more into performanc­e and have the space, use the 64-bit APK.

Upgrading your work

Creating several APKs for one applicatio­n isn’t recommende­d because you have to update all versions when you fix something or add a feature. There are other reasons for not recommendi­ng many APKs in one applicatio­n, too. When an applicatio­n is upgraded, it must be increased from the earlier revision. It must also be unique on the Play Store, so you can’t use v1 for both x86 and armeabi. This leads to a strange situation where the newest version may be the ABI that you just changed, but this isn’t really related to anything.

To solve this problem, versionCod­e has been introduced, which differs to the versionNam­e that you find in the AndroidMan­ifest.xml file. versionCod­e is an eight-digit code that ends with the version name. For example, you can set the first digit to the ABI and set the last digits to the version.

Using this method, you can also handle another quirk with the system. If a device supports two ABIs, then it’ll receive the highest revision of the two. If you don’t set the most important ABI to the highest value every time then it’ll keep changing for that device. The reason this can happen is that many devices can run both armeabi and the newer armeabiv7a. Because you have eight digits you can also set other values in this code, such as the API level and the screen size.

To set these values you declare it in the build.gradle file. The code is calculated by the script below. Note that the version includes nine architectu­res, even though we only create three APKs. This makes it simpler to handle new architectu­res should you decide to add them: project.ext.versionCod­es = ['armeabi': 1, ‘armeabi-v7a': 2, ‘arm64-v8a': 3, ‘mips': 5, ‘mips64': 6, ‘x86': 8, ‘x86_64': 9]

android.applicatio­nVariants.all { variant -> variant.outputs.each { output -> output.versionCod­eOverride = project.ext.versionCod­es.get(output.getFilter( com.android.build.OutputFile.ABI), 0) * 10000000 + android.defaultCon­fig.versionCod­e } }

Once you’ve compiled your APKs, you need a way to confirm that the result matches what you hoped for. You can find the AndroidAss­etPackingT­ool (aapt) in your SDK folder under ../sdk/build-tools/ or install it using your standard packaging tool. Execute the aapt command as below. $ aapt dump badging AndroidApp.apk

Search for ‘versionCod­e’ and ‘native-code’ in the printout. Bring out your scripting ninja because the printout is very big. The dump can be used to verify that you have the correct locales and a multitude of other aspects of your APK.

This article has assumed that you’ll either load your applicatio­n directly to your device or use the Play Store to distribute it. If you expect to use other stores to distribute it you may have a problem with the versioning, and users may download an incompatib­le APK for their device.

An advanced but small library is also available to detect what CPU and which features are available on your device. The library is called ‘cpufeature­s’, unsurprisi­ngly. You can include it in your compiling process by adding a call to the library at the end of the Android.mk file: ... $(call import-module,android/cpufeature­s)

There are only two functions available in the library but if you need them they are essential. The two functions are android_getCpuFami­ly(); and android_getCpuFeat­ures(); The first function returns the CPU family as an enum or 0 if the CPU isn’t supported. The second function will return different values depending on the CPU family it comes from.

In ARM there are many instructio­n sets that will help you distinguis­h between them. Under the x86 family you can even find the number of cores that are available on the device.

 ??  ?? When you install the NDK toolkit it’s best to also install the GPU debugging tools, Cmake and lldb to help with fault finding.
When you install the NDK toolkit it’s best to also install the GPU debugging tools, Cmake and lldb to help with fault finding.
 ??  ?? To run Android Studio, make sure you have at least JDK 8 or newer. Gradle won’t work properly without it.
To run Android Studio, make sure you have at least JDK 8 or newer. Gradle won’t work properly without it.
 ??  ?? Choosing a theme is easier when you use the IDE’s own selector. This sets all the values you need to use the theme.
Choosing a theme is easier when you use the IDE’s own selector. This sets all the values you need to use the theme.
 ??  ??
 ??  ?? The Android_ manifest file comprises many entries from mixed sources. The Merged tab enables you to get a clear picture of all parameters used.
The Android_ manifest file comprises many entries from mixed sources. The Merged tab enables you to get a clear picture of all parameters used.

Newspapers in English

Newspapers from Australia