AndroidQualityEssentials [中文]
Improve Android code quality with static code analysis and runtime check:
- Naming Convention (Especially for resource files), with CheckStyle.
- Code Style, with CheckStyle.
- Potential Bugs, with FindBugs, PMD and Android Lint.
- Potential ANR (slow operations in the main thread), with StrictMode.
- Resource and Memory Leaks, with StrictMode and LeakCanary.
It is recommended that you add these checks when you create a new project and fix the problems with every check-in (as part of your continuous integration process). Otherwise it would need enormous courage and patience when you face and fix the huge amount of errors.
- Add the quality directory to your project.
- You can either copy it:
- Sync the code of this project and copy the quality directory to the root directory of your project.
- Add the following line in your
build.gradle
:
apply from: '../quality/static_analysis.gradle'
- Or add as a remote module. Just like in my project Android-App-Architecture-MVVM-Databinding:
- Add as a remote module:
git remote add analysis https://github.com/BrianSpace/Android-Quality-Essentials.git
- Then in your
build.gradle
:
apply from: '../analysis/quality/static_analysis.gradle'
- You can either copy it:
- Add Lint options
android {
...
lintOptions {
// Turn off analysis progress reporting by lint
quiet true
// Stop the gradle build if errors are found
abortOnError true
// Do not ignore lint warnings
ignoreWarnings false
// Treat warnings as errors
warningsAsErrors true
// Ignore rules list
ignore 'GoogleAppIndexingWarning' // Remove this if the app support App Indexing
}
...
}
- Add dependency for LeakCanary in your
build.gradle
:
dependencies {
...
debugCompile 'com.squareup.leakcanary:leakcanary-android:1.5.1'
releaseCompile 'com.squareup.leakcanary:leakcanary-android-no-op:1.5.1'
testCompile 'com.squareup.leakcanary:leakcanary-android-no-op:1.5.1'
...
}
- In your
Application
class (and not forget to add to the manifest):
public class AndroidQualityEssentialsApplication extends Application {
@Override
public void onCreate() {
super.onCreate();
if (BuildConfig.DEBUG) {
StrictMode.setThreadPolicy(new StrictMode.ThreadPolicy.Builder()
.detectAll()
.penaltyDeath()
.build());
StrictMode.setVmPolicy(new StrictMode.VmPolicy.Builder()
.detectAll()
.penaltyDeath() // If violations are in Android itself or 3rd-party libs, use penaltyLog.
.build());
// Avoid the process dedicated to LeakCanary for heap analysis.
if (!LeakCanary.isInAnalyzerProcess(this)) {
LeakCanary.install(this);
}
}
...
}
}
- Run
gradlew check
in the console to start the static analysis.
- The analysis report will be located in the
build/reports/
directory of the project in which you apply thestatic_analysis.gradle
.
- Run your debug version application for runtime check.
- Add the check step to your Continuous Integration process.
The naming convention defined in the style rules does not allow single letter prefix for member field names like "mMember". But if you like this style, you can change the format
property of the MemberName
module to "^[a-z][a-zA-Z0-9]$". Or change to "^m[A-Z][a-zA-Z0-9]$" to force the "m" prefix.
- In Android Studio, select "Analyze"->"Run Inspection by Name..." menu, search "be final", then run the following two rules under "Java"->"Code style issues":
- Field may be 'final'
- Local variable or parameter may be final
- After each run, in the result panel, click "Make final" button to add "final" automatically.
For PMD rule UseUtilityClass, it will prompt to create Utility Class if your class has only static fields and methods. You just need to:
- Define your utility class as final.
- Create a private constructor and throw exception in it to prevent instantiation. Sample:
public final class FileUtil {
private static Context appContext;
private FileUtil() throws InstantiationException {
throw new InstantiationException("Utility class FileUtil should not be instantiated!");
}
public static void init(final Context context) {
appContext = context.getApplicationContext();
}
/**
* Get available cache directory. Prefer external over internal.
*/
@NonNull
public static File getAvailableCacheDir() {
final File externalCacheDir = appContext.getExternalCacheDir();
return externalCacheDir == null ? appContext.getCacheDir() : externalCacheDir;
}
}
All the static analysis rules are best practices from people's past experience, but they are not always truth that cannot be broken. Some of the PMD rules need to be based on your own preferences:
- AccessorMethodGeneration This rule prefer performance and reducing method count over encapsulation, usually I care more about encapsulation so I exclude this rule. But if you care more about performance and method count (to avoid Multi-dex), you can include it.
- GenericsNaming
This rule prefer a single upper case letter for generic values. But I prefer some more meaningful names with "T" letter as the postfix, like
ItemTypeT
, for better readability. You can remove it from the "exclude" list if you like the single letter naming.
Naming conventions are defined in the quality/checkstyle/naming_convention.xml file. The following rules are defined:
- Java files:
- Should use Camel case.
- Test files should be named "Test.java" or "Base.java".
- Resource files:
- Resource files should use Snake case(lower case, concatenated by underscore).
- Drawables should begin with "bg_", "ic_" or "img_".
- Layouts should begin with "activity_", "fragment_", "view_", "dialog_", "item_" or "btn_".
- Values should begin with "attrs_", "colors_", "dimens_", "strings_" or "styles_".
You can modify the regular expressions as you need for your own project.
Run gradlew checkFileNames
if you would like to check only the naming convention.
CheckStyle is used to check the Java code style. Style rules are based on Google Java Style Guide with the following changes:
- Severity: changed to "error".
- Line length: changed to 120.
- Indentation: tab size changed to 4.
- MethodName: underscore is not allowed.
- ConstantName: all upper case, underscore allowed.
Run gradlew checkCodeStyle
if you would like to check only the code style.
If you would like to exclude some files like 3rd party code, you can add an exclude
item in the checkCodeStyle
task in the static_analysis.gradle file.
FindBugs scan your code for patterns that may result in bugs. The files to be excluded from the analysis is defined here.
Run gradlew findBugs
if you would like to run findbugs only.
PMD is a static code analyzer that can detect common programming flaws. Rules are defined in quality/pmd/pmd-ruleset.xml. Full list of the rules can be found here.
Run gradlew pmdCheck
if you would like to run PMD only.
Android Lint is a Android specific static code analysis tool. The full list of checks is here.
Run gradlew lint
if you would like to run Lint rules only.
StrictMode is very useful to detect slow operations in UI thread and resource leaks. Specifically, ThreadPolicy will detect disk/network I/O and slow operations in UI thead, whereas VmPolicy will detect resource leaks. For details, see the documents for ThreadPolicy.Builder and VmPolicy.Builder
LeakCanary can help to detect memory leaks. It has a very nice UI to report the leaks and show the whole reference chain so that you can easily locate where to fix the leaks.
- The static analysis config for findbugs and PMD is based on https://github.com/ribot/android-boilerplate.
The MIT License
Copyright (c) 2017-2017 AndroidQualityEssentials project contributors
https://github.com/BrianSpace/AndroidQualityEssentials/graphs/contributors
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.