Tag Archives: Android Studio

Creating a Google Fit style circular animated view

Google’s health tracking platform, Google Fit, provides data visualization using a circular view to display feedback to users. This circular view is smoothly animated and presents a variety of data about the current status of the users fitness goals.

This article takes you through the steps required to build the chart shown in the clip below and add it into your Android App.

DecoView

DecoView was designed as a fully configurable library for animating arc based charts. Continue reading this article to find out how to reproduce the Google Fit user experience in your app with DecoView. DecoView is open source and available on GitHub.

DecoView is subclassed from the Android View class. Just like other View subclasses, such as TextView and ImageView, it can be added and configured from your layout XML then controlled in your Activity code.

Faux Fit: DecoView sample project

The source code is available from the Faux Fit project on GitHub.

Step 1: Include the DecoView library in your project

DecoView is hosted on the JitPack.io repository. The first step is to add the required repository into your build.gradle file

repositories {
    // ...
    maven { url "https://jitpack.io" }
}

Next, add the gradle dependency for DecoView

dependencies {
    compile 'com.github.bmarrdev:android-DecoView-charting:v0.9.3'
}

Step 2: Add a DecoView to your XML layout

Open your Activity layout file and add a DecoView by inserting the following declaration.

<com.hookedonplay.decoviewlib.DecoView
    xmlns:custom="http://schemas.android.com/apk/res-auto"
    custom:dv_lineWidth="26dp"
    android:id="@+id/dynamicArcView"
    android:layout_width="match_parent"
    android:layout_height="match_parent" />

Note the use of the custom:dv_lineWidth attribute to set the default width of the arcs. You can always override this value for any of the data series you add later.

Step 3: Configure the DecoView by adding a data series

After adding a DecoView to your XML layout you will not see anything in your app unless you add one or more data series.

The first data series we are adding is going to be used as a background track to show the path where the other data series will follow as they are animated. To add this background arc add the following code to your Activity source file. Note how we set the color to a very light gray (hex code #FFE2E2E2).

DecoView decoView = (DecoView) findViewById(R.id.dynamicArcView);

SeriesItem seriesItem = new SeriesItem.Builder(Color.parseColor("#FFE2E2E2"))
    .setRange(0, 50, 0)
    .build();

int backIndex = decoView.addSeries(seriesItem);

Tip: While we are setting the color to be fully opaque here, DecoView supports alpha transparency in all functions that take a Color value

Next you need to add an actual data series in your Activity code. This may be used to visualize a data value such as the number of calories the user has burnt or number of minutes spent walking.

final SeriesItem seriesItem = new SeriesItem.Builder(Color.parseColor("#FFFF8800"))
        .setRange(0, 50, 0)
        .build();

int series1Index = decoView.addSeries(seriesItem);

Step 4: Add a listener to monitor value changes

First you need to add a TextView to your XML layout. Then you can attach a new SeriesItem.SeriesItemListener() that will be called whenever the current position of the data series is changed. If you issue an event to move the series this callback will be called multiple times for the one event.

We will calculate the percentage the data series is filled and update our TextView with this percentage value.

final TextView textPercentage = (TextView) findViewById(R.id.textPercentage);
seriesItem.addArcSeriesItemListener(new SeriesItem.SeriesItemListener() {
    @Override
    public void onSeriesItemAnimationProgress(float percentComplete, float currentPosition) {
        float percentFilled = ((currentPosition - seriesItem.getMinValue()) / (seriesItem.getMaxValue() - seriesItem.getMinValue()));
        textPercentage.setText(String.format("%.0f%%", percentFilled * 100f));
    }

    @Override
    public void onSeriesItemDisplayProgress(float percentComplete) {

    }
});

Tip: When implementing the DecoView progress listeners be aware that they will be called many times for each event. Avoid heavy processing in the listener to prevent jank, and avoid new allocations to prevent memory churn.

Step 5: Create timed events to animate the DecoView data series

We need to set the background track to be at the maximum value, this enables it to be drawn as a complete circle. We could have set this as the initial position for the track when it was constructed, however we want to see an animation filling the background track so we use an event to reveal the background track. While it is possible to fully customize this animation we will keep it simple and just set the minimum information required; that is the position to move the value and the index of the data series to execute the event.

decoView.addEvent(new DecoEvent.Builder(50)
        .setIndex(backIndex)
        .build());

It is possible to animate the DecoView at any time in response to a user action, such as pressing a button or using a swipe gesture. This is what we have just created with an event to be executed immediately.

It is also possible to delay events to happen at a future time. We will construct then event using setDelay(5000) to delay the animation of the main data series. We need to set the current value between the minimum (0) and maximum (50) values specified on creation of the data series.

Here is how to move the current position to 16.3f (out of 50) and delay the event to start after 5 seconds. After 10 seconds we will then move the position to 30.

decoView.addEvent(new DecoEvent.Builder(16.3f)
        .setIndex(series1Index)
        .setDelay(5000)
        .build());

decoView.addEvent(new DecoEvent.Builder(30f)
        .setIndex(series1Index)
        .setDelay(10000)
        .build());

Note: All time values in DecoView are specified in Milliseconds, 5000ms == 5 seconds

Step 6. Run your app to test your DecoView

You should now have the DecoView animating and looking similar to the image below when you run your app.

DecoView basic sample

Step 7. Add more data series, custom reveal animations and event listeners

To finish off building the Faux Fit sample you need to:

  • Add two more data series. This can be done using the same method used to create the existing series
  • Add custom reveal and hide effects
  • Create a animation loop by adding a listener to recreate all the events when the last one has completed

The code required to complete these final tasks can be viewed in the Faux Fit Activity found within the GitHub project. You should end up with an Activity something like the snapshot below.

Faux Fit DecoView Sample

License

Copyright 2015 Brent Marriott

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

   http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.

References

DecoView on GitHub: https://github.com/bmarrdev/android-DecoView-charting

Faux Fit sample on GitHub: https://github.com/bmarrdev/fauxfit-decoview-sample

Advertisements

3 Comments

Filed under Android Libraries, Android Studio, Animation, Charting

Android NDK: Version code scheme for publishing APKs per architecture

Distributing native libraries with your APK adds complexity to your build process and can bloat the size of your APK. You have to include a specific build of native libraries for every architecture you want to target, or to be more technically correct every ABI (Application Binary Interface) you are targeting. Different Android devices use different CPU’s architecture, which have support for different instruction sets. An ABI is different for each combination of CPU and the instruction set supported.

The recommended distribution method for your APK is to distribute one APK that contains all the static native libraries for all the ABIs being targeted. This article does not follow that path and discusses how to build, upload and manage separate APKs for your app. Each APK will include only the native libraries for a specific ABI. The following assumptions are made:

  • You are using Android Studio 1.2+ and using the Android gradle plugin build system
  • You are distributing your app through the Google Play store
  • You already have static native libraries (.so) built with the Android NDK
  • You are concerned about APK bloat caused by these native libraries

Universal APK or Multiple APKs

You need to make an educated and considered decision about if you distribute one APK that includes all native libraries or multiple APKs. The advantage of one universal APK is that it is simple to deploy and maintain your app, the disadvantage is that you potentially could have a substantial increase in APK size. I have previously described how to use SQLCipher in an Android app which discussed this tradeoff. Figure 1, below, shows the impact on size when shipping SQLCipher native libraries in a basic APK.

Figure 1: Size increase when including SQLCipher

Figure 1: Size increase when including SQLCipher

Play Store requirement for unique versionCode per ABI

The only way for a developer to distribute an app, with different APKs, to users on different devices is to have a unique versionCode and to ensure each APK is targeting different devices. The versionCode needs to be unique as it is possible that a users device is compatible with more than one APK, when this happens they will receive the version with the highest versionCode.

When I mention the version I am not referring to the version that is visible to the user, which is specified as the versionName, I am talking about the internal version which is a numerical versionCode that indicates an ascending order of releases. The snippet from a build.grade script below shows the default versionCode and versionName that Android Studio will apply to a new project.

defaultConfig {
 applicationId "com.hookedonplay.androidbycode.mynewapp"
 minSdkVersion 15
 targetSdkVersion 21
 versionCode 1
 versionName "1.0"
}

It is possible that you are maintaining existing apps which only specify the version in the AndroidManifest.xml. While this works well for static version information we need to use the new Android Gradle build system to be able to dynamically generate version information. Here is an example of version information added directly into the AndroidManifest.xml.

<manifest xmlns:android="http://schemas.android.com/apk/res/android"
      package="com.hookedonplay.androidbycode.mynewapp"
      android:versionCode="1"
      android:versionName="1.0">
      ...
</manifest>

Format for individual versionCodes per ABI

Each ABI needs to have an unique versionCode before it can be uploaded to the play store. We are going alter the build.gradle file to automatically generate a unique version number for each ABI. Assuming we want to build our app with versionCode 101, Our aim is to produce the following results:

ABI versionCode table

Figure 2: Example versionCode values for each ABI

The order of the ABI numbers are important as is the length of the 8 digit versionCode.

The order is based on way the Play store works with multiple APKs. Remember, if a users device is compatible with more than one APK for an application the device will receive the version with the highest version code. This is why an armeabi build will be given a prefix of 1 as this build will also be able to run a armeabi-v7a device (2) and in some cases a x86 device (8) due to a proprietary ARM translation layer called libhoudini.

The versionCode we are going to generate is 8 digits long to cater for multiple APKs that differ not only by target ABI, but also different Android SDK versions and screen sizes. An example of how to incorporate all these details into the 8 digit versionCode is shown in Figure 3 below. This is an extension of a numbering scheme suggested in the Android developer documentation where we prefix the number with the ABI id value that was listed in Figure 2.

versionCode format

Figure 3: Android versionCode suggested format reference

You should be aware that this is only a suggested method for generating version information. It is not mandatory and it can be tailored to me your needs. For example, if you know that you are not going to distribute different APKs based on Android SDK API level then you could drop those two digits from the version.

Automating unique versionCode generation

Before continuing and changing your versionCode naming convention you need to understand the following:

If you are distributing one APK containing all flavors of native libraries then you do not need to worry about implementing the following version scheme.

To automate the version process we need to customize the build process. The build.gradle script for your app is where you can configure the build to produce a separate APK for each ABI type and also set the versionCode to conform with the desired 8 digit format discussed earlier.

Here is an example of a complete build.gradle that will achieve this:

apply plugin: 'com.android.application'
 
 android {
    compileSdkVersion 22
    buildToolsVersion "22.0.1"
 
    defaultConfig {
        applicationId "com.hookedonplay.androidbycode.androiddatabaseencryption"
        minSdkVersion 15
        targetSdkVersion 22
        versionCode 101
        versionName "1.0"
    }
 
    splits {
        abi {
            enable true
            reset()
            include 'x86', 'armeabi', 'armeabi-v7a'
            universalApk true
        }
    }
     
    project.ext.versionCodes = ['armeabi': 1, 'armeabi-v7a': 2, 'arm64-v8a': 3, 'mips': 5, 'mips64': 6, 'x86': 8, 'x86_64': 9]
 
    android.applicationVariants.all { variant ->
        variant.outputs.each { output ->
            output.versionCodeOverride =
                project.ext.versionCodes.get(output.getFilter(
                    com.android.build.OutputFile.ABI), 0) * 10000000 + android.defaultConfig.versionCode
        }
    }
 
    buildTypes {
        release {
            minifyEnabled false
            proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
        }
    }
 }
 
 dependencies {
    compile fileTree(dir: 'libs', include: ['*.jar'])
    compile 'com.android.support:appcompat-v7:22.2.0'
 }

Building separate APK files

The splits mechanism for the Android Gradle Plugin allows you to generate multiple files as shown by the example below. You can include only the ABI types you want to build and it is also possible to build an APK that includes all possible native libraries through the command universalApk true.

splits {
        abi {
            enable true
            reset()
            include 'x86', 'armeabi', 'armeabi-v7a'
            universalApk true
        }
}

Generate versionCode per APK

The section of the build script that sets the versionCode of each APK to a unique number is shown in the code snippet below. Note the project.ext.versionCodes which sets the first digit of the versionCode when multiplied by 10000000 we generate the 8 digit number we require. Finally the initial versionCode of the app, accessed with android.defaultConfig.versionCode, is added to complete the full numerical version for each APK.

   
project.ext.versionCodes = ['armeabi': 1, 'armeabi-v7a': 2, 'arm64-v8a': 3, 'mips': 5, 'mips64': 6, 'x86': 8, 'x86_64': 9]
 
android.applicationVariants.all { variant ->
        variant.outputs.each { output ->
            output.versionCodeOverride =
                project.ext.versionCodes.get(output.getFilter(
                    com.android.build.OutputFile.ABI), 0) * 10000000 + android.defaultConfig.versionCode
    }
}

Verify the versionCode included in each APK

The Android Asset Packaging Tool (aapt) is part of the Android build tools and is required to build APK files. You can also use it to verify the contents of the APK files you have generated.

You will find the aapt executable in your latest version of the build tools, for example ..\sdk\build-tools\22.0.1

Execute the following command on each of the APKs you have produced:

 aapt dump badging AndroidApp.apk

You will find a wealth of information about the APK but the following items in the output will be of most interest:

versionCode='20000101'
...
native-code: 'armeabi-v7a'

Below is a more detailed, but still trimmed sample output from the aapt badging request:

package: name='com.hookedonplay.androidbycode.androiddatabaseencryption' 
versionCode='2000101' versionName='1.0' platformBuildVersionName='5.0.1-1624448'
sdkVersion:'15'
targetSdkVersion:'21'
application-label:'Android Database Encryption'
application-icon-160:'res/mipmap-mdpi-v4/ic_launcher.png'
application-icon-240:'res/mipmap-hdpi-v4/ic_launcher.png'
application-icon-320:'res/mipmap-xhdpi-v4/ic_launcher.png'
application-icon-480:'res/mipmap-xxhdpi-v4/ic_launcher.png'
application-icon-640:'res/mipmap-xxxhdpi-v4/ic_launcher.png'
application: label='Android Database Encryption' icon='res/mipmap-mdpi-v4/ic_launcher.png'
application-debuggable
uses-feature: name='android.hardware.touchscreen'
uses-implied-feature: name='android.hardware.touchscreen' reason='default feature for all apps'
main
supports-screens: 'small' 'normal' 'large' 'xlarge'
supports-any-density: 'true'
locales: '--_--' 'ca' 'da' 'fa' 'ja' 'nb' 'de' 'af' 'bg' 'th' 'fi' 'hi' 'vi' 'sk' 'uk' 'el' 'nl' 'pl' 'sl' 'tl' 'am' 'in' 'ko' 'ro' 'ar' 'fr' 'hr' 'sr' 'tr' 'cs' 'es' 'it' 'lt' 'pt' 'hu' 'ru' 'zu' 'lv' 'sv' 'iw' 'sw' 'fr-CA' 'lo-LA' 'en-GB' 'bn-BD' 'et-EE' 'ka-GE' 'ky-KG' 'km-KH' 'zh-HK' 'si-LK' 'mk-MK' 'ur-PK' 'hy-AM' 'my-MM' 'zh-CN' 'ta-IN' 'te-IN' 'ml-IN' 'en-IN' 'kn-IN' 'mr-IN' 'mn-MN' 'ne-NP' 'gl-ES' 'eu-ES' 'is-IS' 'es-US' 'pt-PT' 'zh-TW' 'ms-MY' 'kk-KZ' 'uz-UZ'
densities: '160' '240' '320' '480' '640'
native-code: 'armeabi-v7a'

This same dump can be also used to verify other attributes of your APK, such as the build SDK version, locales and supported screen densities.

You will also be able to verify the targeted ABI and versionCode once you upload to the Play Store via the developer console.

Distribution of multiple APKs via alternative app stores

This article has assumed that you are distributing your app through the Google Play Store. It may be possible that you are using multiple distribution methods, such as promoting your app on the Amazon Appstore. According to the documentation the same version methodology will work for the multiple APK support within the Amazon Appstore. There are also a number of smaller app marketplaces that may automatically add your app. Each of these app store may or may not handle multiple APKs correctly which may result in some users downloading an APK that is incompatible with their device.

References

Multiple APK support in Android

http://developer.android.com/google/play/publishing/multiple-apks.html

Android apk-splits user-guide

http://tools.android.com/tech-docs/new-build-system/user-guide/apk-splits

Amazon Device Targeting

https://developer.amazon.com/public/solutions/devices/fire-tv/docs/using-device-targeting

4 Comments

Filed under Android NDK, Android Studio, Automation, Google Play, Gradle

Android Studio 1.1: Unit Testing on the local JVM

The Android Studio 1.1 update is still filtering onto developers machines. One important improvement is the addition of support for unit testing using the local JVM on your development machine. This feature is still experimental and must be enabled in the IDE settings.  You can read all about it on the official site:

http://tools.android.com/tech-docs/unit-testing-support

This new feature means the most popular open source Android Studio plugin for gradle unit tests will now no longer be developed or maintained:

https://github.com/evant/android-studio-unit-test-plugin

Leave a comment

Filed under Android Studio, Testing

Android Database Encryption using SQLCipher

Clone or fork this project from GitHub: https://github.com/bmarrdev/AndroidDatabaseSQLCipher

Data security is a hot topic that will only intensify under the weight of the petabytes of information accumulating in data centres around the world. Securing your users data is no longer a bolt on afterthought, but a non-negotiable in any system that deals with sensitive data. It is not just the data centres that hold this sensitive data, those ever increasingly powerful computers we carry in our pockets or strap to our wrists are a treasure trove of personal information that others would love to get their hands on. As an app developer it is your responsibility to do everything in your power to protect the users of your product. In doing so you are not only protecting those users but the reputation of your company. Wherever possible you must avoid storing sensitive data within your app. This article discusses the pros and cons of encrypting your Android database with SQLCipher for the cases where you have no choice other than to store sensitive data.

SECTION 1: SQLCipher

SQLCipher is often the go to choice for Android apps for the following reasons:

  • Strong encryption (256-bit AES)
  • Mature technology
  • Maintained and supported by its developers and the open source community
  • Supports virtually the same API as standard Android database functions

Unfortunately there is no such thing as a free lunch, it is important to understand not only the benefits but also the consequences of switching to an encrypted database. Here are a few to consider:

  • APK size will be increased substantially
  • Database performance will decrease
  • Secure management of the encryption key (or password) is required
  • Use of strong encryption has legal considerations in some jurisdictions

APK Size

Firstly there is APK size to consider. Using the sample project that accompanies this article as an example, a release build (without proguard enabled) of the APK expands from 1MB to over 9MB. This equates to 2-3MB for each architecture you want to support.

It is possible to build different APK files to support each architecture. When including SQLCipher this can lead to a significant reduction in the size of your app. For example, it reduces the size of the sample app included with this project by more than half if you build a separate app for each of armeabi, armeabi-v7a and x86. You can read more about distributing your app with multiple APK’s.

Performance

Any layer of encryption is going to come with a performance hit. SQLCipher is no different. If a 10-20% performance hit is going to impact the user experience of your app then you may want to look at different options. For most purposes the database in your app in unlikely to be big enough for this to cause an issue.

Key Management

To encrypt and decrypt you need a key. SQLCipher does some of the hard work for you, encrypting a password into a key using PBKDF2. This password however still needs to be managed and should not be stored within your source code.

Legal issues

Apps that use strong cryptography, such as the algorithms used in SQLCipher, have obligations for compliance to the strict United States export requirements. You should not add any encryption without understanding what these export requirements are and what your obligations are. This will not be discussed further in the article, you should seek your own clarification.

Before you can publish your app on the Google Play Store you must acknowledge that your software application may be subject to United States export laws regardless of your location or nationality and you certify that your app is authorized for export under these laws.

Partial database encryption

SQLCipher is applied to a complete database. It does not support encryption of specific columns in your database.

SECTION 2: Add SQLCipher library to your app

Update August 2015: Commonsguy has released an unofficial GitHub repository that packages the SQLCipher for Android distribution in a standard Android library module. You can use this as an alternative to following the steps in this section to unpack the official version from Zetetic.

If, after careful consideration, you have decided that you just have to store that sensitive data, this is how to use SQLCipher to do it. These instructions are for Android Studio 1.0+, if you are an Eclipse user you should visit the SQLCipher project home for details about how to add the SQLCipher library to your Eclipse project. When developing in Android Studio your first port of call for including libraries should be through the Maven repository via your Gradle script. Unfortunately at this time SQLCipher is not available officially through Maven so we will have to get the libraries and import them manually.

Step 1: Download the SQLCipher for Android binaries from here: https://www.zetetic.net/sqlcipher/open-source

What are these libraries? SQLCipher is written in C code. These libraries (.so) have been build using the Android NDK, which allows native languages such as C and C++ to be compiled and used by your Android app. Unlike the Java code in your app which is compiled to bytecode to run in Androids VM (Dalvik or ART), this library contains native code that is architecture dependent. This means there is a library for will support each possible architecture that the target device may use.

Step 2: In your app\src\main directory create two new child folders named jniLibs and assets.

Step 3: From the binaries you downloaded in step 1, copy the contents of the assets folder into your newly created assets folder and copy the sub-folders from the libs folder into your newly created jniLibs folder. Be sure that you have named the folder correctly and copied all the files as well as the sub-folder for each architecture type you wish to support.

Step 4: From the binaries archive you downloaded in step 1 you now need to copy the jar files from the libs folder into app/libs. In addition to these jars you also need to copy guava-r09.jar and commons-codec.jar into this folder. Currently these are not included in the binaries archive above. If you download the sample project attached to this article you can find the jar files there.

Step 5: To ensure your project can find the libraries add an include in your gradle script for your app module, this should go in the dependencies section. (This may already be present in your gradle script)

compile fileTree(dir: 'libs', include: ['*.jar'])

SECTION 3: Replacing your Sqlite database with SQLCipher database

Now the SQLCipher library is added for your project you need to do a clean and rebuild. Follow the steps below to switch your sqlite database across to a SQLCipher database. This will work even if your sqlite database is within a content provider.

Step 1: Replace imports that reference standard Android sqlite database classes with SQLCipher classes. To do this look for any import from the standard database classes, for example:

import android.database.Cursor; 
import android.database.DatabaseUtils; 
import android.database.SQLException; 
import android.database.sqlite.SQLiteDatabase;

Replace these with calls to the equivalent SQLCipher classes:

import net.sqlcipher.Cursor; 
import net.sqlcipher.DatabaseUtils; 
import net.sqlcipher.SQLException; 
import net.sqlcipher.database.SQLiteDatabase;

Step 2: Replace calls to open or create the database passing the password as an argument. For example:

getWritableDatabase()

Needs to be replaced with:

getWritableDatabase('my_secure_key')

Step 3: Add the initialization call to SQLCipher. This must be called before any call the SQLCipher classes and you should ensure that this is only called once.

import net.sqlcipher.database.SQLiteDatabase;
...
SQLiteDatabase.loadLibs(context);

Step 4: That’s it, build your app and run!

Remember to head over to GitHub to clone or fork the sample project: https://github.com/bmarrdev/AndroidDatabaseSQLCipher

SECTION 4: Distributing an App with native libraries

Since this article was initially release I have described in detail the considerations of distributing apps that include native libraries, such as those required by SQLCipher.

To learn more I recommend reviewing these articles:

Android NDK: A guide to deploying apps with native libraries

Android NDK: Version code scheme for publishing APKs per architecture

 

4 Comments

Filed under Android NDK, Android SDK, Database, Security

Static Code Analysis using FindBugs (Android Studio)

There are many techniques, procedures and tools that can contribute to the quality of the source code your team produces.  One of those techniques is static code analysis and if you are using Android Studio you are probably using at least one tool for this already, Android Lint.  Lint is an very high quality tool that will not only give your Java source code the once over, but also your XML files, including layouts. It has deep knowledge about Android development patterns and it evolves with each SDK release, it should be the first stop for static code analysis in Android.  Every developer should pay attention when Android Lint waves that little yellow flag at you.

No matter how good Android Lint is, it is always worthwhile having a second and third opinion from different tools.  FindBugs is not an alternative to Android Lint, but it can be used as a companion. It is an open source tool that performs static analysis of your Java bytecode to warn of potential bugs, defects, security and performance issues.  It knows nothing about Android and is not a panacea for poorly designed apps, but it can provide a little nudge in the direction of potential issues in your software. As with all tools in this space it will never have a 100% strike rate, but it does allow the developer to take another look and think twice about a piece of code.

When an issue arises you will get a warning that is placed into one of nine different buckets. They are:

  • Dodgy code
  • Malicious code vulnerability
  • Bad practice
  • Correctness
  • Internationalization
  • Security
  • Performance
  • Multi-threaded correctness
  • Experimental

Each of these can be disabled if not required and you can also configure the confidence level at which issues will be reported.

Using FindBugs in the Android Studio IDE

Android Studio is built from the IntelliJ Java IDE which allows it to use IntelliJ plugins.  This is how you can integrate FindBugs into your Android Studio environment.

To open the IDE plugins go to the settings page (File > Settings…), and find the Plugins menu.  From here you can browse plugin repositories and do a search for FindBugs, or you can download the FindBugs plugin and install it manually from file.  When searching for the plugin you may find references to the QA Plug plugin.  This is another plugin that includes FindBugs and another way to get access to FindBugs from within your IDE and as a bonus it also includes other code analysis tools such as CheckStyle and PMD.

Executing FindBugs Analysis

Your code must have been built before analysis as FindBugs operates on the generated bytecode.  You can start the FindBugs analysis from within Android Studio by going to the Analyze menu, selecting FindBugs and then selecting the scope of the analysis to perform.  If you are using FindBugs through the QA Plug, you can access it from the Tools menu.

Exclude filters for Android

On first run of FindBugs you may be quite disappointed with the results.  With the default confidence level and no filters set FindBugs will report many issues that are part of the automatically generated android code to support functionality such as accessing resources.  For example, you may get many warnings similar to the these:

The class name R$dimen doesn't start with an upper case letter
The class name R$drawable doesn't start with an upper case letter

Take a look at the FindBugs manual for the specification of the filter format and details how you can create an exclude filter. For a filter that stops the warnings from Android auto generated code use the following XML:

<?xml version="1.0" encoding="UTF-8"?>
<FindBugsFilter>
    <Match>
        <!-- ignore all issues in resource generation -->
        <Class name="~.*\.R\$.*"/>
    </Match>
    <Match>
        <Class name="~.*\.Manifest\$.*"/>
    </Match>
</FindBugsFilter>

Another issue you may hit are FindBugs warnings for JUnit tests. This can also be handled in the exclude filter file by adding the following:

<!-- ignore all bugs in test classes, except for those bugs specifically relating to JUnit tests -->
<Match>
    <Class name="~.*\.*Test" />
    <!-- test classes are suffixed by 'Test' -->
    <Not>
        <Bug code="IJU" /> <!-- 'IJU' is the code for bugs related to JUnit test code -->
    </Not>
  </Match>

This filter can be added to your Gradle build file for the automated builds discussed later, but for manually executed analysis you can add the filter in Android Studio. Go to settings dialog (File > Settings…) and select FindBugs-IDEA. Here you can configure the plugin settings and add the exclude filter list from an XML file.

findbugs_options

Run FindBugs again and you will have no one to blame except your team for the issues that are reported.

Automating FindBugs

Like other parts of the development process you can automate this static analysis. Gradle has support for the FindBugs plugin. To create a Gradle task you can add the following to the gradle.build in your project:

apply plugin: 'findbugs'

task findbugs(type: FindBugs) {
    ignoreFailures = false
    effort = "default"
    reportLevel = "medium"
    excludeFilter = new File("${project.rootDir}/findbugs/findbugs-filter.xml")
    classes = files("${project.rootDir}/app/build/intermediates/classes")
    source = fileTree('src/main/java/')
    classpath = files()
    reports {
        xml.enabled = true
        html.enabled = true
        xml {
            destination "$project.buildDir/findbugs/findbugs-output.xml"
        }
        html {
            destination "$project.buildDir/findbugs/findbugs-output.html"
        }
    }
}

Visit https://gradle.org/docs/current/dsl/org.gradle.api.plugins.quality.FindBugs.html for more information about configuring FindBugs in Gradle.

Conclusion

If your team doesn’t enforce clean Android Lint builds for your projects then stop looking at other tools now.  It should be your first line of defence for code quality standards.  If your lint pane is boringly empty then maybe it is time for another tool to give its opinion.  This is not a must have tool for every Android developer, but it does have its value.  It may only need to pick up a lazy security flaw once to make the configuration and automation time worthwhile.

References

FindBugs home http://findbugs.sourceforge.net/

FindBugs Intellij Plugin https://plugins.jetbrains.com/plugin/3847?pr=idea

Gradle FindBugs Plugin https://gradle.org/docs/current/userguide/findbugs_plugin.html

5 Comments

Filed under Android SDK, Android Studio, Automation, Gradle, Performance, Security, Tools