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
編碼模板的實現:Tab
iterations
fori
group
$<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.html
Preferences
→ 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
LogUtil
activity_XXX
fragment_XXX
BaseBlankActivity
或 BaseActionBarActivity
CheckStyle
,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') } }
Detector
MyIssueRegistry
類中聲明注冊了各個 Detector
的 Issue
。Issue
由 Detector
發現并報告,是 Android 程序代碼可能存在的風險。而這里就需要真正實現這些 Detector
,以檢查 Activity 的基類為例。ACTIVITY_SUPER_CLASS_ISSUE
這個 Issue
的定義需要使用 Issue.create(...)
方式實現,同時需要傳入 6 個參數分別如下:Issue
Fatal
, Error
, Warning
, Informational
, Ignore
Issue
和 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