In today's tutorial, we'll learn about how to ensure high-quality Android code in our projects using some static code analysis tools for Java. We'll look at Checkstyle, FindBugs, PMD, and Android Studio Lint—all of them free and open source!
What Are Static Code Analysis Tools?
These are tools that parse and analyse your source code without actually executing it. The goal is to find potential vulnerabilities such as bugs and security flaws. A popular free static code analyser such as FindBugs checks your code against a set of rules which your code should adhere to—if the code doesn't follow these rules, it's a sign that something may be wrong. Think of static code analysis tools as an additional compiler that is run before the final compilation into the system language.
Many software companies are requiring projects to pass static code analysis tests, in addition to doing code reviews and unit testing in the build process. Even maintainers of open-source projects often include one or more static code analysis steps in the build process. So learning about static analysis is an important step in writing quality code. Be aware that static code analysis—also known as "white-box" testing—should not be seen as a replacement for unit testing of your source code.
In this tutorial, we're going to learn about some popular static analysis tools that are available for Android and Java. But first, let's see some of the benefits of using static analysis.
Benefits
- Helps detect potential bugs that even unit or manual testing might have missed.
- Defines project-specific rules. For example, static analysis as part of the build chain helps newcomers get up to speed with the code standards of their new team.
- Helps you improve your knowledge of a new language.
- Scans your whole project, including files that you might not have ever read.
Setup
All the code analysis tools we'll learn about in this tutorial are available as Gradle plugins, so we can create individual Gradle tasks for each of them. Let's use a single Gradle file that will include them all. But before that, let's create a folder that will contain all of our files for the static code analysis.
Open Android Studio and inside the app module (in Project view), create a new folder and name it code_quality_tools. This folder will contain the XML files for the code analysis tools, and it will also have a Gradle file, quality.gradle, which will run our static analysis tasks.
Finally, visit your build.gradle in the app module folder and include this line at the end of the file:
apply from: '/code_quality_tools/quality.gradle'
Here, our quality.gradle Gradle script is being applied with a reference to its local file location.
Checkstyle
Given rules you specify in an XML file to enforce a coding standard for your project, Checkstyle enforces those rules by analysing your source code and compares them against known coding standards or conventions.
Checkstyle is an open-source tool that is actively maintained by the community. This means you can create your own custom checks or modify existing ones to suit your needs. For example, Checkstyle can run a check on the constant names (final, static, or both) in your classes. If your constant names do not stick to a rule of being in uppercase with words separated by an underscore, the problem will be flagged in the final report.
// incorrect private final static String myConstant = "myConstant"; // correct private final static String MY_CONSTANT = "myConstant";
Integrating Checkstyle
I'll show you how to integrate Checkstyle into our Android Studio project and demonstrate a practical example.
First, we need to create our coding rules. Inside checkstyle.xml, we create some Checkstyle configuration rules that will be run against our code.
<?xml version="1.0"?><!DOCTYPE module PUBLIC "-//Puppy Crawl//DTD Check Configuration 1.2//EN" "http://www.puppycrawl.com/dtds/configuration_1_2.dtd"><module name="Checker"><module name="FileTabCharacter"/><module name="TreeWalker"><!-- Checks for Naming Conventions --><!-- See http://checkstyle.sourceforge.net/config_naming.html --><module name="MethodName"/><module name="ConstantName"/><!-- Checks for Imports --><!-- See http://checkstyle.sourceforge.net/config_imports.html--><module name="AvoidStarImport"/><module name="UnusedImports"/><!-- Checks for Size --><!-- See http://checkstyle.sourceforge.net/config_sizes --><module name="ParameterNumber"><property name="max" value="6"/></module><!-- other rules ignored for brevity --></module>
In the above code, we include the rules or checks we want Checkstyle to validate in our source code. One rule is AvoidStarImport which, as the name says, checks if your source code included an import statement like java.util.*
. (Instead, you should explicitly specify the package to import, e.g. java.util.Observable
.)
Some rules have properties, which we can set just like we did for ParameterNumber—this limits the number of parameters of a method or constructor. By default, the property max
is 7, but we changed it to 6 instead. Take a look at some of the other checks on the Checkstyle website.
To run this check, we need to create a Gradle task. So visit the quality.gradle file and create a task called checkstyle:
apply plugin: 'checkstyle' task checkstyle(type: Checkstyle) { description 'Check code standard' group 'verification' configFile file('./code_quality_tools/checkstyle.xml') source 'src' include '**/*.java' exclude '**/gen/**' classpath = files() ignoreFailures = false }
Notice that in the code above, we first applied the Checkstyle Gradle plugin. We gave it a description and added it to an already predefined Gradle group called verification.
The key properties of the Checkstyle Gradle task we are concerned with are:
- configFile: the Checkstyle configuration file to use.
- IgnoreFailures: whether or not to allow the build to continue if there are warnings.
- include: the set of include patterns.
- exclude: the set of exclude patterns. In this case, we don't scan generated classes.
Finally, you can run the Gradle script by visiting the Gradle tool window on Android Studio, opening the verification group, and then clicking on checkstyle to run the task.
Another way is to use the command line:
gradle checkstyle
After the task has finished running, a report will be generated, which is available at app module > build > reports > checkstyle. You can open checkstyle.html to view the report.
A Checkstyle plugin is freely available for Android Studio or IntelliJ IDEA. It offers real-time scanning of your Java files.
PMD
PMD is another open-source code analysis tool that analyzes your source code. It finds common flaws like unused variables, empty catch blocks, unnecessary object creation and so on. PMD has many rule sets you can choose from. An example of a rule which is part of the Design Rules set is:
SimplifyBooleanExpressions
: avoid unnecessary comparisons in boolean expressions which complicate simple code. An example:
public class Bar { // can be simplified to // bar = isFoo(); private boolean bar = (isFoo() == true); public isFoo() { return false;} }
PMD is configured with the pmd.xml file. Inside it, we'll include some configuration rules such as the ones for Android, Naming, and Design.
<?xml version="1.0"?><ruleset xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" name="Android Application Rules" xmlns="http://pmd.sf.net/ruleset/1.0.0" xsi:noNamespaceSchemaLocation="http://pmd.sf.net/ruleset_xml_schema.xsd" xsi:schemaLocation="http://pmd.sf.net/ruleset/1.0.0 http://pmd.sf.net/ruleset_xml_schema.xsd"><description>Custom ruleset for Android application</description><exclude-pattern>.*/R.java</exclude-pattern><exclude-pattern>.*/gen/.*</exclude-pattern><!-- Android ---><!-- http://pmd.sourceforge.net/pmd-4.3.0/rules/android.html --><rule ref="rulesets/java/android.xml"/><!-- Design --><!-- http://pmd.sourceforge.net/pmd-4.3.0/rules/design.html --><rule ref="rulesets/java/design.xml"><exclude name="UncommentedEmptyMethod"/></rule><!-- Naming --><!-- http://pmd.sourceforge.net/pmd-4.3.0/rules/naming.html --><rule ref="rulesets/java/naming.xml/ShortClassName"><properties><property name="minimum" value="3"/></properties></rule><!-- other rules ignored for brevity --></ruleset>
As we did for Checkstyle, we also need to create a PMD Gradle task for the check to be executed inside the quality.gradle file.
apply plugin: 'pmd' task pmd(type: Pmd) { description 'Run PMD' group 'verification' ruleSetFiles = files("./code_quality_tools/pmd.xml") source 'src' include '**/*.java' exclude '**/gen/**' reports { xml.enabled = false html.enabled = true } ignoreFailures = false }
PMD is also available as a Gradle plugin.
The key properties of the task we've created are:
- ruleSetFiles: The custom rule set files to be used.
- source: The source for this task.
- reports: The reports to be generated by this task.
Finally, you can run the Gradle script by visiting the Gradle tool window, opening the verification group folder, and then clicking on pmd to run the task. Or you can run it via the command line:
gradle pmd
A report will also be generated after the execution of the task which is available at app module > build > reports > pmd. There is also a PMD plugin available for IntelliJ or Android Studio for you to download and integrate if you want.
FindBugs
FindBugs is another free static analysis tool which analyses your class looking for potential problems by checking your bytecodes against a known list of bug patterns. Some of them are:
- Class defines hashCode() but not equals(): A class implements the hashCode() method but not equals()—therefore two instances might be equal but not have the same hash codes. This falls under the bad practice category.
- Bad comparison of int value with long constant: The code is comparing an int value with a long constant that is outside the range of values that can be represented as an int value. This comparison is vacuous and possibly will yield an unexpected result. This falls under the correctness category.
- TestCase has no tests: class is a JUnit
TestCase
but has not implemented any test methods. This pattern is also under the correctness category.
FindBugs is an open-source project, so you can view, contribute or monitor the progress of the source code on GitHub.
In the findbugs-exclude.xml file, we want to prevent FindBugs from scanning some classes (using regular expressions) in our projects, such as auto-generated resource classes and auto-generated manifest classes. Also, if you use Dagger, we want FindBugs not to check the generated Dagger classes. We can also tell FindBugs to ignore some rules if we want.
<FindBugsFilter><!-- Do not check auto-generated resources classes --><Match><Class name="~.*R\$.*"/></Match><!-- Do not check auto-generated manifest classes --><Match><Class name="~.*Manifest\$.*"/></Match><!-- Do not check auto-generated classes (Dagger puts $ into class names) --><Match><Class name="~.*Dagger*.*"/></Match><!-- http://findbugs.sourceforge.net/bugDescriptions.html#ST_WRITE_TO_STATIC_FROM_INSTANCE_METHOD--><Match><Bug pattern="ST_WRITE_TO_STATIC_FROM_INSTANCE_METHOD" /></Match></FindBugsFilter>
And finally, we'll include the findbugs task in quality.gradle:
apply plugin: 'findbugs' task findbugs(type: FindBugs) { description 'Run findbugs' group 'verification' classes = files("$project.buildDir/intermediates/classes") source 'src' classpath = files() effort 'max' reportLevel = "high" excludeFilter file('./code_quality_tools/findbugs-exclude.xml') reports { xml.enabled = false html.enabled = true } ignoreFailures = false }
In the first line above, we applied FindBugs as a Gradle Plugin and then created a task called findbugs
. The key properties of the findbugs
task we are really concerned with are:
classes
: the classes to be analyzed.effort
: the analysis effort level. The value specified should be one ofmin
,default
, ormax
. Be aware that higher levels increase precision and find more bugs at the cost of running time and memory consumption.reportLevel
: the priority threshold for reporting bugs. If set to low, all bugs are reported. If set to medium (the default), medium and high priority bugs are reported. If set to high, only high priority bugs are reported.excludeFilter
: the filename of a filter specifying bugs to exclude from being reported, which we have created already.
You can then run the Gradle script by visiting the Gradle tool window, opening the verification group folder, and then clicking on findbugs to run the task. Or launch it from the command line:
gradle findbugs
A report will also be generated when the task has finished executing. This will be available at app module > build > reports > findbugs. The FindBugs plugin is another freely available plugin for download and integration with either IntelliJ IDEA or Android Studio.
Android Lint
Lint is another code analysis tool, but this one comes with Android Studio by default. It checks your Android project source files for potential bugs and optimizations for correctness, security, performance, usability, accessibility, and internationalization.
To configure Lint, you have to include the lintOptions {}
block in your module-level build.gradle file:
lintOptions { abortOnError false quiet true lintConfig file('./code_quality_tools/lint.xml') }
The key Lint options we are concerned with are:
abortOnError
: whether lint should set the exit code of the process if errors are found.quiet
: whether to turn off analysis progress reporting.lintConfig
: the default configuration file to use.
Your lint.xml file can include issues you want Lint to ignore or modify, such as the example below:
<?xml version="1.0" encoding="UTF-8"?><lint><!-- Disable the given check in this project --><issue id="IconMissingDensityFolder" severity="ignore" /><!-- Change the severity of hardcoded strings to "error" --><issue id="HardcodedText" severity="error" /></lint>
You can run Lint manually from Android Studio by clicking on the Analyze menu, choosing Inspect Code... (the inspection scope is the whole project), and then clicking on the OK button to proceed.
You can also run Lint by visiting the Gradle tool window, opening the verification group, and then clicking on lint. Finally, you can run it via the command line.
On Windows:
gradlew lint
On Linux or Mac:
./gradlew lint
A report will also be generated when the task has finished executing, which is available at app module > build > outputs > lint-results.html.
Bonus: StrictMode
StrictMode is a developer tool that helps prevent developers of your project doing any accidental flash I/O or network I/O on the main thread, because this can lead to the app being sluggish or unresponsive. It also helps in preventing ANR (App Not Responding) dialogs from showing up. With StrictMode issues corrected, your app will become more responsive and the user will enjoy a smoother experience. StrictMode uses two sets of policies to enforce its rules:
- VM Policies: guards against bad coding practices such as not closing
SQLiteCursor
objects or anyCloseable
object that was created. - Thread Policies: looks out for operations such as flash I/O and network I/O being performed on the main application thread instead of on a background thread.
if (BuildConfig.DEBUG) { StrictMode.setThreadPolicy(new StrictMode.ThreadPolicy.Builder() .detectDiskReads() .detectDiskWrites() .detectNetwork() // or .detectAll() for all detectable problems .penaltyLog() // Log detected violations to the system log. .build()); StrictMode.setVmPolicy(new StrictMode.VmPolicy.Builder() .detectLeakedSqlLiteObjects() .detectLeakedClosableObjects() .penaltyLog() .penaltyDeath() // Crashes the whole process on violation. .build()); }
The code above can be either in your Application, Activity, or other application component's onCreate()
method.
You can learn more about StrictMode
here on Envato Tuts+.
A sample Android project implementing all of the above including rule sets of the tools for a typical Android project can be found in this post's GitHub repo.
Conclusion
In this tutorial, you learned about how to ensure high-quality Android code using static code analysis tools: what they are, benefits of using them, and how to use Checkstyle, FindBugs, Lint, PMD, and StrictMode in your application. Go ahead and give these tools a try—you might discover some problems in your code that you never expected.
In the meantime, check out some of our other courses and tutorials on Android app development!
- Android SDKRxJava 2 for Android Apps: RxBinding and RxLifecycle
- Android StudioCoding Functional Android Apps in Kotlin: Getting Started
- Android SDKAndroid From Scratch: Using REST APIs
- Android SDKHow to Create an Android Chat App Using Firebase