if、for、try 等嵌套深度规范,变量初始化规范等activity_ 开头等LogUtil 代替 Log 的使用,Message.Obtain() 代替 new Message(),Activity 部分文件命名,甚至 Activity、Fragment 的基类定义规则还是很容易发生错误,并没有被发现。随着编码规范的完善充实,多个开发的编码规范如何保证,就会成为一个显而易见的问题。util 工具类,但随着各个开发的补充,这套 util 工具类也越来越多,如 LogUtil、KeyboardUtil 等,而这些类一部分是为了统一入口,如统一使用 LogUtil,可以统一做到测试服打开本地日志,线上服关闭日志;KeyboardUtil 方便使用者控制键盘的弹出隐藏等操作。虽然定义了这些工具类,但终究存在应该使用而没有使用的情况。当然这些工具代码并不难,开发在自己的模块也能很容易的实现和使用,一般也不会出问题。然而上述讲的优点都会消失掉。而这些问题依赖 code review 也是件头疼的问题。RecycleView 的编码方式,单例模式的实现方式等等,各个开发可能写出各式代码,甚至实现的单例模式并不是线程安全的。for,出现下拉列表如下:
fori,出现编码片段:
i,跳入循环结果值的输入:
Live Template 一个实例。这个类似于 iOS 中的 Code Snippets,提供了代码片段的能力。Android Studio (Mac) 进入 Settings/Preferences -> Editor -> Live Templates,可以看到已定义的模板组:
fori 编码模板的实现:
Tabiterationsforigroup


$<variable_name>$,点击 Edit variables 可设置变量具体内容:
No application contexts yet. Define,设置为 java 环境:
android studio config\templates 目录下查看到 yanxuan.xml。C:\Users\\<user>\\.AndroidStudiox.x\config\templates(user 为你的计算机用户名)~/Library/Preferences/AndroidStudiox.x/templates
<templateSet group="test"> <template name="yxtest" value="testMethod($a$, $b$);" description="这是一个测试模板" toReformat="false" toShortenFQNames="true"> <variable name="a" expression="lineNumber()" defaultValue="2" alwaysStopAt="false" /> <variable name="b" expression="" defaultValue="" alwaysStopAt="true" /> <context> <option name="JAVA_CODE" value="true" /> <option name="JAVA_STATEMENT" value="true" /> <option name="JAVA_EXPRESSION" value="true" /> <option name="JAVA_DECLARATION" value="true" /> <option name="JAVA_COMMENT" value="true" /> <option name="JAVA_STRING" value="true" /> <option name="COMPLETION" value="true" /> </context> </template> </templateSet>

yxtest

singleton
Live Template 之外,工程项目中很多新建的类也有很多机械的代码,如我们定义的 Activity 要么继承自 BaseBlankActivity,要么继承自 BaseActionBarActivity,另外项目中采用 MVP 模式,因此一个 Activity 基本上会有一个对应的 presenter 类,一个 layout 文件,同时很多时候,一个页面中会有一个需要支持刷新的 RecycleView 等。除此之外,ViewHolder、HttpTask 等代码也是固定模式的代码。
Empty Activity,并填写类名,layout 名称等信息,之后就能出现对应的添加或修改:MainActivity.java、activity_main.xml、AndroidManifest.xml:
${Android Studio 的安装目录}/plugins/android/lib/templates/Mac: Android Studio.app/Contents/plugins/android/lib/templates/
globals.xml.ftl:定义当前模板的一些全局变量recipe.xml.ftl:定义模板拷贝的逻辑等template.xml:定义模板对话框的样式template_blank_activity.png:定义模板的图标root/src/app_package/SimpleActivity.java.ftl:具体的模板文件
图片来自:http://www.slideshare.net/murphonic/custom-android-code-templates-15537501


java 规范,checkstyle 帮助开发者实现常用的检查。这里 CheckStyle 能检查的内容有:
apply plugin: 'checkstyle'
checkstyle { toolVersion '6.1.1' showViolations true }
task checkstyle(type: Checkstyle) { configFile file("$configDir/checkstyle/checkstyle.xml") configProperties.checkstyleSuppressionsPath = file("$configDir/checkstyle/suppressions.xml").absolutePath source 'src' include '**/*.java' // 检查 java 代码 exclude '**/gen/**' // 排除生成的代码 classpath = files() ignoreFailures true // 忽略检查失败的情况,避免gradle命令执行中止 }
checkstyle.xml:
<!--单个文件方法数上限最多为 30--> <module name="MethodCount"> <property name="maxTotal" value="30"/> </module> <!--方法名的首字母是小写--> <module name="MethodName"> <property name="format" value="^[a-z][a-zA-Z0-9]*$"/> </module> <!--静态变量名的首字符是 s--> <module name="StaticVariableName"> <property name="format" value="^[a-z][a-zA-Z0-9]*$"/> <property name="applyToPublic" value="true"/> <property name="applyToProtected" value="true"/> <property name="applyToPackage" value="true"/> <property name="applyToPrivate" value="true"/> </module> <!--只有私有构造函数的类需要定义成 final 类型--> <module name="FinalClass"/> ...
具体其他的检查项配置可以查看 检查配置链接
./gradlew checkstyle
${project}/app/build/reports/checkstyle/checkstyle.html
CheckStyle 工具不同的是,FindBugs 不注重样式或者格式,而是试图寻找出真正的缺陷或者现在的性能问题。FindBugs 检查类和 Jar 文件,不是通过分析类文件的形式或结构来分析程序,而是使用 Visitor 模式,将字节码与一组缺陷模式进行对比以发现可能的问题。而这些问题比如如下:
a 的值也不会变成 dddbbbccc。因此,上述代码很可能是程序猿的 bug。为此 FindBugs 能找出这种问题
strMaps 是否确实有 aaa 这个 key,为此这里会检查出错误。
actions 并未初始化,因此当 actions.add("TEST") 被执行的时候会发生异常。
apply plugin: 'findbugs'
task findbugs(type: FindBugs, dependsOn: "assembleDebug") { ignoreFailures = false effort = "max" reportLevel = "high" excludeFilter = new File("$configDir/findbugs/findbugs-filter.xml") classes = files("${project.rootDir}/app/build/intermediates/classes") source 'src' include '**/*.java' exclude '**/gen/**' reports { xml.enabled = false html.enabled = true xml { destination "$reportsDir/findbugs/findbugs.xml" } html { destination "$reportsDir/findbugs/findbugs.html" } } classpath = files() ignoreFailures true // 避免检查失败 gradle 执行中止 }
./gradlew findbugs
${project}/app/build/reports/findbugs/findbugs.html
FindBugs 的检查实例(忽略返回值, 未初始化的成员变量使用),可以发现在 Android Studio IDE 上,已经出现了标黄提示,我们把光标放上去,就能看到具体的提示了:
cmd + F1 可以看到具体的错误提示:
Lint 给我们提供的错误提示功能。除了和 FindBugs 重复的纯 java 代码检查之外,Lint 能检查很多其他工具无法检查的内容,也更贴合 Android:
在 Activity 内定义非静态内部类 Handler 的报警

在 AndroidManifest.xml 中定义 export 为 true 的广播接受器,但没有定义权限,Lint 检查认为是不安全的

build.gradle 文件中引用的 support 包的版本低的提示Android Lint 是一个静态代码检查工具,能够对潜在的 bug,可能的安全性、性能、可用性、可访问性、国际化等优化内容做出监测:
来自官方文档 Improve Your Code with Lint
lint-result.htmlPreferences → Editor → Inspections 进入 Android Studio 的 Lint 配置界面
lint.xml 上配置 Lint
<?xml version="1.0" encoding="UTF-8"?> <lint> <!-- Disable the given check in this project --> <issue id="IconMissingDensityFolder" severity="ignore" /> <!-- Ignore the ObsoleteLayoutParam issue in the specified files --> <issue id="ObsoleteLayoutParam"> <ignore path="res/layout/activation.xml" /> <ignore path="res/layout-xlarge/activation.xml" /> </issue> <!-- Ignore the UselessLeaf issue in the specified file --> <issue id="UselessLeaf"> <ignore path="res/layout/main.xml" /> </issue> <!-- Change the severity of hardcoded strings to "error" --> <issue id="HardcodedText" severity="error" /> </lint>
来源 Android Develop 文档 Improve Your Code with Lint
android { lintOptions { abortOnError false // 配置 lint 过程中出错,不中止 gradle 任务 xmlReport false htmlReport true lintConfig file("$configDir/lint/lint.xml") // 配置 lint 检查规则 htmlOutput file("$reportsDir/lint/lint-result.html") // 配置 lint 输出文件 xmlOutput file("$reportsDir/lint/lint-result.xml") // 配置 lint 输出文件 } }
./gradlew lint
${项目工程}/app/build/reports/lint/lint-result.html
LogUtilactivity_XXXfragment_XXXBaseBlankActivity 或 BaseActionBarActivityCheckStyle,FindBugs)就已经无能为力了,我们必须编码支持自定义检查。以项目中集成的 Lint 检查为例,讲述流程:lint 库
dependencies { ... compile 'com.android.tools.lint:lint-api:24.5.0' compile 'com.android.tools.lint:lint-checks:24.5.0' }
IssueRegistry 类MyIssueRegistry 类,继承自 IssueRegistry。用来注册我们自定义的全部 issue
public class MyIssueRegistry extends IssueRegistry { @Override public List<Issue> getIssues() { System.out.println("********YXLint rules works!!!********"); return Arrays.asList( LogUsageDetector.ISSUE, ToastUsageDetector.ISSUE, ActivitySuperClassDetector.ACTIVITY_SUPER_CLASS_ISSUE, ... BuildGradleVersionDetector.ISSUE); } }
其中:LogUsageDetector.ISSUE:用于检查不允许直接使用 Log.* 方式输出本地日志的代码ToastUsageDetector.ISSUE:用于检查直接用 Toast 方式显示 toast 的代码ActivitySuperClassDetector.ACTIVITY_SUPER_CLASS_ISSUE:用于检查 Activity 的基类BuildGradleVersionDetector.ISSUE:用于检查 gradle 文件中不允许直接写数字版本号的代码IssueRegistry 类
jar { manifest { attributes('Lint-Registry': 'com.netease.htlint.lintrules.MyIssueRegistry') } }
DetectorMyIssueRegistry 类中声明注册了各个 Detector 的 Issue。Issue 由 Detector 发现并报告,是 Android 程序代码可能存在的风险。而这里就需要真正实现这些 Detector,以检查 Activity 的基类为例。
ACTIVITY_SUPER_CLASS_ISSUE 这个 Issue 的定义需要使用 Issue.create(...) 方式实现,同时需要传入 6 个参数分别如下:IssueFatal, Error, Warning, Informational, IgnoreIssue 和 Detector 提供映射关系,Detector 就是当前类。声明扫描检测的范围 Scope,描述 Detector 需要分析时需要考虑的文件集,包括:Resource 文件或目录、Java 文件、Class 文件ActivitySuperClassDetector 继承自 Detector,并实现 Detector.JavaScaner。这里主要自定义实现的方法如上图 H,IBaseBlankActivity 或 BaseActionBarActivity?如果都不是的话,则报告错误../../gradlew assemble 来执行编译任务,就可以输出我们需要的 jar 文件 (htlintrules_jar-0.0.1.jar) 了htlintrules_jar-0.0.1.jar 拷贝到 ~/.android/lint 中,但缺点是针对会影响一台机器其他的工程。很明显,我们的自定义 Lint 检查有很多是项目中特有的一些编码规范。LinkedIn 方案:将 jar 放到一个 aar 中。这样我们就可以针对工程进行自定义 Lint,lint.jar 只对当前工程有效。htlintrules_jar 工程的 build.gradle 中添加代码,整体看起来如下:
apply plugin: 'java' apply plugin: 'maven' dependencies { compile 'com.android.tools.lint:lint-api:24.5.0' compile 'com.android.tools.lint:lint-checks:24.5.0' } jar { manifest { attributes('Lint-Registry': 'com.netease.htlint.lintrules.MyIssueRegistry') } } configurations { lintJarOutput } dependencies { lintJarOutput files(jar) } defaultTasks 'assemble'
同时新建另一个工程 htlint,在其 build.gradle 文件中添加如下代码:
/* * rules for including "lint.jar" in aar */ configurations { lintJarImport } dependencies { lintJarImport project(path: ':htlintrules_jar', configuration: "lintJarOutput") } task copyLintJar(type: Copy) { from (configurations.lintJarImport) { rename { String fileName -> 'lint.jar' } } into 'build/intermediates/lint/' } project.afterEvaluate { def compileLintTask = project.tasks.find { it.name == 'compileLint' } compileLintTask.dependsOn(copyLintJar) }
最后在 app 工程的 build.gradle 中添加 htlint 引用,配置完成
dependencies { compile project(':htlint') // lint 检查库 ... }
${项目工程}/app/ 目录下执行 ../gradlew lint:
lint-result.html 文件,可以查看到前面编写的 ActivitySuperClassDetector.ACTIVITY_SUPER_CLASS_ISSUE 已经生效,并且检查出了相关的非规范代码。

FullScreenVideoActivity 确实是需要的错误检查结果,而 WXEntryActivity 却不是,这个类是有集成微信分享时需要的,并且按照微信开放平台的文档来编写,因此并不需要按照项目规范,继承 BaseBlankActivity 或 BaseActionBarActivity。为此,我们期望 WXEntryActivity 不应该被检查出 WrongActivitySuperClass 错误WXEntryActivity 类名签名添加 SuppressLint 注解:
@SuppressLint("WrongActivitySuperClass") public class WXEntryActivity extends Activity implements IWXAPIEventHandler{ ... }
@SuppressLint(${IssueId})。这里设置的就是具体某个 Issue 的 id 值all 关键字,比如:@SuppressLint("all")com.sina.weibo.sdk.net.DownloadService 这个 Service,而这个 Service 会被 Lint 检查为未定义,为此需要 xml 文件中也过滤部分代码的 Lint 的检查:
<manifest xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" package="com.netease.yanxuan"> ... <service android:name="com.sina.weibo.sdk.net.DownloadService" android:exported="false" tools:ignore="MissingRegistered" /> </manifest>
这里对于单个 Issue 过滤的规则为:tools:ignore=${IssueId}all 关键字:tools:ignore="all"
基本(rulesets/basic.xml),终结函数(finalizer),未使用的代码(rulesets/unusedcode.xml),设计(rulesets/design.xml) 等。FindBugs,pmd 的一些规则更具争议,但 pmd 支持我们构建自己的规则集
<?xml version="1.0"?> <ruleset name="customruleset"> <description> Sample ruleset for developerWorks article </description> <rule ref="rulesets/design.xml"/> <rule ref="rulesets/naming.xml"/> <rule ref="rulesets/basic.xml"/> </ruleset>
gradle 中自定义 check 命名,并依赖其他的 task。在执行检查的时候,可以通过 ./gradlew check 来执行全部的检查命令。
check.dependsOn 'checkstyle', 'findbugs', 'pmd', 'lint'
另一方面,这种代码检查,如果等到开发完成的时候再去执行,很可能问题积累了很多,甚至导致产品上线前,开发并不能来得及修正全部的问题。为此,可以将代码检查的命令集成 jenkins,保证开发每天都能看到当前的代码的缺陷,能及时的修改原文转自:https://www.jianshu.com/p/6a38e9dcc0d9