1 背景
一個(gè)項(xiàng)目開發(fā)必然會(huì)涉及團(tuán)隊(duì)協(xié)作,而工程質(zhì)量就需要團(tuán)隊(duì)去保證竖哩。一般我們期望的代碼:無潛在風(fēng)險(xiǎn)别瞭、無重復(fù)邏輯、風(fēng)格無差異顶燕、可閱讀性好、新人上手速度快等冈爹。為了達(dá)到上述目標(biāo)涌攻,一般團(tuán)隊(duì)都會(huì)制定一套自己認(rèn)可的編碼規(guī)范,并且周期性進(jìn)行 code review频伤。然而編碼規(guī)范的制定恳谎,那么一套編碼規(guī)范需要包含哪些內(nèi)容,另外編碼規(guī)范僅僅是一套軟規(guī)范憋肖,實(shí)際程序員同學(xué)能遵守到什么程序還是一個(gè)未知數(shù)因痛,所以需要我們進(jìn)行人肉 code review,而這種人肉排查方式岸更,很容易遺漏部分問題鸵膏,保障性還是有些不足。
為此坐慰,我們從編碼前期较性、編碼中期和編碼后期保證進(jìn)行了初步嘗試。
2 編碼前期 - 編碼規(guī)范
對(duì)于一個(gè) Android 項(xiàng)目结胀,一般需要涉及的編碼規(guī)范有:
-
普通 java 編碼風(fēng)格規(guī)范
如每個(gè)方法最大行數(shù)赞咙,每個(gè)類文件的最大行數(shù),每個(gè)方法最大參數(shù)數(shù)等
-
普通 java 編碼最佳實(shí)踐
如
if
糟港、for
攀操、try
等嵌套深度規(guī)范,變量初始化規(guī)范等 -
通用 Android 編碼規(guī)范(java 部分和 xml 部分)秸抚,
包含 Android java 部分和 Android xml 部分速和,如避免使用普通內(nèi)部類定義handler歹垫,避免 layout xml 中存在無用結(jié)點(diǎn)等
-
Gradle 編碼規(guī)范
如盡量避免 lib 使用 module,獨(dú)立工程可以通過 aar 或 mvn 方式導(dǎo)入
-
具體項(xiàng)目相關(guān)的編碼規(guī)范等
如項(xiàng)目團(tuán)隊(duì)規(guī)定使用自定義 LogUtil 打日志颠放,Activity排惨、Fragment 等重要類的繼承關(guān)系,Activity 對(duì)應(yīng)的 xml 文件必須以
activity_
開頭等
制定了這些內(nèi)容可以一定程度上規(guī)范程序猿的編碼碰凶,配合團(tuán)隊(duì)進(jìn)行了周期性的 code review (一般是一個(gè)版本一次暮芭,大概 4 個(gè)星期一次),會(huì)有比較好的效果欲低。然而即使這么做辕宏,還是存在一定的問題,距離我們期望的目標(biāo)還是比較遠(yuǎn)砾莱。比如各個(gè)單例類的定義五花八門瑞筐,使用 LogUtil
代替 Log
的使用,Message.Obtain()
代替 new Message()
腊瑟,Activity
部分文件命名聚假,甚至 Activity
、Fragment
的基類定義規(guī)則還是很容易發(fā)生錯(cuò)誤闰非,并沒有被發(fā)現(xiàn)魔策。隨著編碼規(guī)范的完善充實(shí),多個(gè)開發(fā)的編碼規(guī)范如何保證河胎,就會(huì)成為一個(gè)顯而易見的問題。
3 編碼中期 - 編碼模板
為了實(shí)現(xiàn)公用代碼復(fù)用虎敦,我們定義了一些 util
工具類游岳,但隨著各個(gè)開發(fā)的補(bǔ)充,這套 util
工具類也越來越多其徙,如 LogUtil
胚迫、KeyboardUtil
等,而這些類一部分是為了統(tǒng)一入口唾那,如統(tǒng)一使用 LogUtil
访锻,可以統(tǒng)一做到測(cè)試服打開本地日志,線上服關(guān)閉日志闹获;KeyboardUtil
方便使用者控制鍵盤的彈出隱藏等操作期犬。雖然定義了這些工具類,但終究存在應(yīng)該使用而沒有使用的情況避诽。當(dāng)然這些工具代碼并不難龟虎,開發(fā)在自己的模塊也能很容易的實(shí)現(xiàn)和使用,一般也不會(huì)出問題沙庐。然而上述講的優(yōu)點(diǎn)都會(huì)消失掉鲤妥。而這些問題依賴 code review 也是件頭疼的問題佳吞。
此外,RecycleView
的編碼方式棉安,單例模式的實(shí)現(xiàn)方式等等底扳,各個(gè)開發(fā)可能寫出各式代碼,甚至實(shí)現(xiàn)的單例模式并不是線程安全的贡耽。
提了這么多衷模,另一方面,要求開發(fā)在繁忙的業(yè)務(wù)中嚴(yán)格遵守這些規(guī)范菇爪,也有些強(qiáng)人所難算芯。所幸,Android Studio 為我們提供了編碼模板來解放開發(fā)的工作凳宙,并一定程度上統(tǒng)一編碼風(fēng)格熙揍。
3.1 Live Template
3.1.1 系統(tǒng)模板
查看一個(gè)使用 Android Studio 中很常見的例子,輸入 for
氏涩,出現(xiàn)下拉列表如下:
當(dāng)選擇 fori
届囚,出現(xiàn)編碼片段:
確認(rèn)循環(huán)變量 i
,跳入循環(huán)結(jié)果值的輸入:
上例是尖,就是 Android Studio 中系統(tǒng)提供的 Live Template
一個(gè)實(shí)例意系。這個(gè)類似于 iOS 中的 Code Snippets
,提供了代碼片段的能力饺汹。
Android Studio
(Mac) 進(jìn)入 Settings/Preferences
-> Editor
-> Live Templates
蛔添,可以看到已定義的模板組:
查看 fori
編碼模板的實(shí)現(xiàn):
- A: 完成模板的快捷鍵
Tab
- B: 位置分類
iterations
- C: 編碼模板對(duì)應(yīng)的縮寫
fori
- D: 模板的內(nèi)容
- E: 模板應(yīng)用環(huán)境
3.1.2 自定義模板
-
構(gòu)建項(xiàng)目
group
輸入 group 的名稱
-
構(gòu)建具體編碼模板
- Abbreviation:觸發(fā)編碼模板的縮寫
- Description:模板的具體描述
- Template Text:具體的模板內(nèi)容
-
變量定義
變量形式為
$<variable_name>$
,點(diǎn)擊Edit variables
可設(shè)置變量具體內(nèi)容:- Name:變量名
- Expression:變量表現(xiàn)
- Default value:默認(rèn)值
- Skip if defined:是否跳過編輯已經(jīng)定義的值
-
模板應(yīng)用環(huán)境
點(diǎn)擊
No application contexts yet. Define
兜辞,設(shè)置為 java 環(huán)境: -
模板文件
構(gòu)建了模板 group 后迎瞧,在
android studio config\templates
目錄下查看到yanxuan.xml
。windows:
C:\Users\\<user>\\.AndroidStudiox.x\config\templates
(user 為你的計(jì)算機(jī)用戶名)mac:
~/Library/Preferences/AndroidStudiox.x/templates
<templateSet group="test"> <template name="yxtest" value="testMethod($a$, $b$);" description="這是一個(gè)測(cè)試模板" 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>
-
設(shè)置完畢逸吵,實(shí)踐查看:
yxtest
singleton
3.2 Android Studio Template
-
背景
除了
Live Template
之外凶硅,工程項(xiàng)目中很多新建的類也有很多機(jī)械的代碼,如我們定義的 Activity 要么繼承自BaseBlankActivity
扫皱,要么繼承自BaseActionBarActivity
足绅,另外項(xiàng)目中采用MVP
模式,因此一個(gè) Activity 基本上會(huì)有一個(gè)對(duì)應(yīng)的presenter
類韩脑,一個(gè)layout
文件氢妈,同時(shí)很多時(shí)候,一個(gè)頁(yè)面中會(huì)有一個(gè)需要支持刷新的RecycleView
等扰才。除此之外允懂,ViewHolder
、HttpTask
等代碼也是固定模式的代碼衩匣。這些都是固定機(jī)械的代碼蕾总,而如果是人肉去寫的話粥航,難免會(huì)出現(xiàn)代碼風(fēng)格不一致、不規(guī)范的情況生百,同時(shí)也浪費(fèi)了一部分的時(shí)間递雀。所幸,Android Studio 提供了工程類模板蚀浆,方便我們實(shí)現(xiàn)這樣的功能缀程。
-
系統(tǒng)模板
查看 Android Studio 系統(tǒng)類模板,我們能發(fā)現(xiàn)有很多定義好的類模板:
如需要?jiǎng)?chuàng)建一個(gè)空的 Activity 頁(yè)面市俊,可以選擇
Empty Activity
杨凑,并填寫類名,layout 名稱等信息摆昧,之后就能出現(xiàn)對(duì)應(yīng)的添加或修改:MainActivity.java
撩满、activity_main.xml
、AndroidManifest.xml
:而這些模板定義绅你,可以在相關(guān)路徑文件中找到:
- Windows:
${Android Studio 的安裝目錄}/plugins/android/lib/templates/
- Mac:
Android Studio.app/Contents/plugins/android/lib/templates/
針對(duì) EmpytActivity 這里需要定義的文件有:
-
globals.xml.ftl
:定義當(dāng)前模板的一些全局變量 -
recipe.xml.ftl
:定義模板拷貝的邏輯等 -
template.xml
:定義模板對(duì)話框的樣式 -
template_blank_activity.png
:定義模板的圖標(biāo) -
root/src/app_package/SimpleActivity.java.ftl
:具體的模板文件
圖片來自:http://www.slideshare.net/murphonic/custom-android-code-templates-15537501
- Windows:
-
自定義模板
而針對(duì)我們需要自定義的模板伺帘,可以在模板定義路徑下新建文件夾和文件即可,細(xì)節(jié)內(nèi)容可查看 Tutorial How To Create Custom Android Code Templates
項(xiàng)目的模板文件內(nèi)容:
設(shè)置完模板文件之后忌锯,重啟 Android Studio伪嫁,可以生效模板文件,使用模板文件如下:
4 編碼后期 - 靜態(tài)代碼檢查
由上偶垮,我們定義了編碼規(guī)范张咳,定義了 Live Template 和 Android Studio Template 方便程序猿更好的準(zhǔn)守我們的項(xiàng)目編碼規(guī)范。然后編碼規(guī)范畢竟只是軟規(guī)范似舵,而提供編碼模板更多的解決大量 util 的使用問題和便利小伙伴完成機(jī)械編碼晶伦,并不能完全保證程序猿嚴(yán)格按照全部的規(guī)范來編碼。
為此啄枕,我們需要一套靜態(tài)代碼檢查機(jī)制能檢查已有的代碼是否遵守規(guī)范∽逦郑總結(jié)已有的規(guī)范频祝,可以將規(guī)范類型歸納為普通 Java 規(guī)范、普通 Android 規(guī)范脆淹、具體項(xiàng)目規(guī)范等常空。而這些檢查點(diǎn),可以配合不同的檢查工具進(jìn)行檢查盖溺。
4.1 檢查 java 代碼風(fēng)格 - CheckStyle
對(duì)于 java
規(guī)范漓糙,checkstyle 幫助開發(fā)者實(shí)現(xiàn)常用的檢查。這里 CheckStyle 能檢查的內(nèi)容有:
- Javadoc 注釋
- 命名約定
- 標(biāo)題
- Import 語(yǔ)句
- 體積大小
- 空白
- 修飾符
- 塊
- 代碼問題
- 類設(shè)計(jì)
- 混合檢查(包活一些有用的比如非必須的System.out和printstackTrace)
檢查內(nèi)容很多烘嘱,而檢查項(xiàng)需要和具體的項(xiàng)目規(guī)范做結(jié)合昆禽。如蝗蛙,每行代碼字符數(shù)控制在 80,單頁(yè)代碼行數(shù)控制在 800 等醉鳖。因此需要結(jié)合配置文件捡硅,來檢查項(xiàng)目中的 java 代碼。在 Android Studio 上配置 CheckStyle 流程如下:
-
在 Android Studio 中添加 gradle Plugin
apply plugin: 'checkstyle'
-
設(shè)置 CheckStyle 版本
checkstyle { toolVersion '6.1.1' showViolations true }
-
配置 CheckStyle 檢查項(xiàng)
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命令執(zhí)行中止 }
-
配置自定義的檢查項(xiàng):
checkstyle.xml
:<!--單個(gè)文件方法數(shù)上限最多為 30--> <module name="MethodCount"> <property name="maxTotal" value="30"/> </module> <!--方法名的首字母是小寫--> <module name="MethodName"> <property name="format" value="^[a-z][a-zA-Z0-9]*$"/> </module> <!--靜態(tài)變量名的首字符是 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> <!--只有私有構(gòu)造函數(shù)的類需要定義成 final 類型--> <module name="FinalClass"/> ...
具體其他的檢查項(xiàng)配置可以查看 檢查配置鏈接
-
執(zhí)行 checkstyle 檢查
./gradlew checkstyle
-
查看檢查結(jié)果
命令執(zhí)行結(jié)束壮韭,查看檢查結(jié)果文件:
${project}/app/build/reports/checkstyle/checkstyle.html
4.2 檢查 java 代碼質(zhì)量 - FindBugs
與 CheckStyle
工具不同的是,FindBugs
不注重樣式或者格式纹因,而是試圖尋找出真正的缺陷或者現(xiàn)在的性能問題喷屋。FindBugs
檢查類和 Jar
文件,不是通過分析類文件的形式或結(jié)構(gòu)來分析程序瞭恰,而是使用 Visitor
模式屯曹,將字節(jié)碼與一組缺陷模式進(jìn)行對(duì)比以發(fā)現(xiàn)可能的問題。而這些問題比如如下:
-
忽略返回值
上述代碼執(zhí)行結(jié)束之后寄疏,并沒有什么意義是牢,變量
a
的值也不會(huì)變成dddbbbccc
。因此陕截,上述代碼很可能是程序猿的 bug驳棱。為此 FindBugs 能找出這種問題 -
空指針示例
上述最后一行代碼,很明顯在執(zhí)行的時(shí)候會(huì)發(fā)生空指針異常农曲,這里因?yàn)?FindBugs 無法知道變量
strMaps
是否確實(shí)有aaa
這個(gè) key社搅,為此這里會(huì)檢查出錯(cuò)誤。 -
未初始化的成員變量使用
這里由于類成員變量
actions
并未初始化乳规,因此當(dāng)actions.add("TEST")
被執(zhí)行的時(shí)候會(huì)發(fā)生異常形葬。
Android Studio 上 FindBugs 的集成如下:
-
在 gradle 中引入插件
apply plugin: 'findbugs'
-
在 gradle 中配置 findbugs task
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 執(zhí)行中止 }
-
執(zhí)行 findbugs 檢查
./gradlew findbugs
-
查看檢查結(jié)果
查看檢查結(jié)果文件:
${project}/app/build/reports/findbugs/findbugs.html
4.3 檢查 Android 代碼質(zhì)量 - Lint
4.3.1 基本介紹
前面 FindBugs
的檢查實(shí)例(忽略返回值
, 未初始化的成員變量使用
),可以發(fā)現(xiàn)在 Android Studio IDE 上暮的,已經(jīng)出現(xiàn)了標(biāo)黃提示笙以,我們把光標(biāo)放上去,就能看到具體的提示了:
按 cmd + F1
可以看到具體的錯(cuò)誤提示:
這就原生 Lint
給我們提供的錯(cuò)誤提示功能冻辩。除了和 FindBugs
重復(fù)的純 java
代碼檢查之外猖腕,Lint 能檢查很多其他工具無法檢查的內(nèi)容,也更貼合 Android:
在 Activity 內(nèi)定義非靜態(tài)內(nèi)部類 Handler 的報(bào)警
在
AndroidManifest.xml
中定義 export 為 true 的廣播接受器恨闪,但沒有定義權(quán)限倘感,Lint 檢查認(rèn)為是不安全的
build.gradle 文件中引用的 support 包的版本低的提示
Android Lint 是一個(gè)靜態(tài)代碼檢查工具,能夠?qū)撛诘?bug咙咽,可能的安全性老玛、性能、可用性、可訪問性蜡豹、國(guó)際化等優(yōu)化內(nèi)容做出監(jiān)測(cè):
來自官方文檔 Improve Your Code with Lint
- App Source Files:工程中的源文件麸粮,包括 java 代碼、資源 xml 代碼余素、gradle 文件豹休,圖片資源文件以及 progroud 等文件
- lint.xml:配置文件,配置哪些 lint 檢查可以排除桨吊,自定義問題的嚴(yán)重級(jí)別
- lint Tool:靜態(tài)代碼檢查工具威根,可以是命令行或者集成在 Android Studio 上
- lint Output:代碼檢查結(jié)果,可以直接顯示在 Console 上视乐,也可以是
lint-result.html
4.3.2 原生 Lint 檢查
在 Android SDK Tools 16 及更高的版本中洛搀,Lint 工具會(huì)自動(dòng)安裝。原生 Lint 的檢查項(xiàng)已經(jīng)有 200 多項(xiàng) (包括前面示例的 5 項(xiàng)內(nèi)容)佑淀,因此使用原生的功能點(diǎn)留美,就能檢查開發(fā)中的大部分通用問題。
-
Android Studio IDE 上配置 Lint 檢查偏好設(shè)置
(Mac 下)
Preferences
→Editor
→Inspections
進(jìn)入Android Studio
的Lint
配置界面- A:配置 Profile伸刃,方便不同項(xiàng)目或者不同情況下使用不同的 Profile 進(jìn)行檢查
- B:Lint 檢查點(diǎn) Group
- C:Lint 檢查點(diǎn)具體描述
- D:Lint 檢查點(diǎn)警告級(jí)別設(shè)置
- E:Lint 檢查點(diǎn)開關(guān)
-
lint.xml
上配置 Lint除了可以通過 IDE 配置 Lint谎砾,還可以通過直接 lint.xml 為單個(gè)項(xiàng)目配置檢查規(guī)則
<?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
-
gradle 中配置 Lint task
android { lintOptions { abortOnError false // 配置 lint 過程中出錯(cuò),不中止 gradle 任務(wù) xmlReport false htmlReport true lintConfig file("$configDir/lint/lint.xml") // 配置 lint 檢查規(guī)則 htmlOutput file("$reportsDir/lint/lint-result.html") // 配置 lint 輸出文件 xmlOutput file("$reportsDir/lint/lint-result.xml") // 配置 lint 輸出文件 } }
-
執(zhí)行檢查
在工程根目錄執(zhí)行以下命令 (Mac)捧颅,以執(zhí)行檢查任務(wù)
./gradlew lint
-
檢查結(jié)果
生成的檢查結(jié)果在
${項(xiàng)目工程}/app/build/reports/lint/lint-result.html
4.3.3 自定義 Lint 檢查
雖然原生的 Lint 檢查已經(jīng)很強(qiáng)大了景图,檢查項(xiàng)也已經(jīng)很多,然而還是無法滿足項(xiàng)目中的特有需求:
- log 統(tǒng)一使用
LogUtil
- 對(duì)應(yīng) Activity 的 layout 命名為
activity_XXX
- 對(duì)應(yīng) Fragment 的 layout 命名為
fragment_XXX
- Activity 必須派生自
BaseBlankActivity
或BaseActionBarActivity
對(duì)于以上這些需求碉哑,原生 Lint 檢查(包括 CheckStyle
挚币,FindBugs
)就已經(jīng)無能為力了,我們必須編碼支持自定義檢查扣典。以項(xiàng)目中集成的 Lint 檢查為例妆毕,講述流程:
4.3.3.1 配置 Gradle,引入 lint
庫(kù)
dependencies {
...
compile 'com.android.tools.lint:lint-api:24.5.0'
compile 'com.android.tools.lint:lint-checks:24.5.0'
}
- lint-api: 官方給出的API贮尖,API并不是最終版笛粘,官方提醒隨時(shí)有可能會(huì)更改API接口。
- lint-checks:已有的檢查湿硝。
4.3.3.2 定義 IssueRegistry
類
新建一個(gè) MyIssueRegistry
類闰蛔,繼承自 IssueRegistry
。用來注冊(cè)我們自定義的全部 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 文件中不允許直接寫數(shù)字版本號(hào)的代碼
4.3.3.3 gradle 清單項(xiàng)中注冊(cè)前面定義的 IssueRegistry
類
jar {
manifest {
attributes('Lint-Registry': 'com.netease.htlint.lintrules.MyIssueRegistry')
}
}
4.3.3.4 定義 Detector
MyIssueRegistry
類中聲明注冊(cè)了各個(gè) Detector
的 Issue
图柏。Issue
由 Detector
發(fā)現(xiàn)并報(bào)告,是 Android 程序代碼可能存在的風(fēng)險(xiǎn)任连。而這里就需要真正實(shí)現(xiàn)這些 Detector
蚤吹,以檢查 Activity 的基類為例。
ACTIVITY_SUPER_CLASS_ISSUE
這個(gè) Issue
的定義需要使用 Issue.create(...)
方式實(shí)現(xiàn),同時(shí)需要傳入 6 個(gè)參數(shù)分別如下:
- A:一個(gè)固定的唯一的 id 代表這個(gè)
Issue
- B:對(duì)于問題的簡(jiǎn)短總結(jié)裁着,描述問題而不是修復(fù)措施
- C:完整的問題解釋和修復(fù)建議
- D:?jiǎn)栴}類別繁涂,現(xiàn)在已有的問題類別有如下
- Lint
- Correctness (incl. Messages)
- Security
- Performance
- Usability (incl. Icons, Typography)
- Accessibility
- Internationalization
- Bi-directional text
- E:優(yōu)先級(jí),必須在1到10之間二驰,10為最重要/最嚴(yán)重扔罪。
- F:嚴(yán)重級(jí)別,可選值有
Fatal
,Error
,Warning
,Informational
,Ignore
- G:為
Issue
和Detector
提供映射關(guān)系桶雀,Detector
就是當(dāng)前類矿酵。聲明掃描檢測(cè)的范圍Scope
,描述Detector
需要分析時(shí)需要考慮的文件集矗积,包括:Resource
文件或目錄全肮、Java
文件、Class
文件
ActivitySuperClassDetector
繼承自 Detector
棘捣,并實(shí)現(xiàn) Detector.JavaScaner
辜腺。這里主要自定義實(shí)現(xiàn)的方法如上圖 H,I
- H:檢查類的基類是 "android.support.v4.app.Activity" 或 "android.app.Activity"
- I:具體檢查類的方法乍恐,這里需要排除非 yanxuan 包名下的代碼评疗,判斷當(dāng)前類的基類是否是
BaseBlankActivity
或BaseActionBarActivity
?如果都不是的話茵烈,則報(bào)告錯(cuò)誤 - J:報(bào)出問題的方法:該方法中指定參數(shù)有:
- 需要報(bào)錯(cuò)的 Issue
- 發(fā)生問題的代碼在語(yǔ)法樹上的節(jié)點(diǎn)
- 發(fā)生問題的代碼位置
- 警告的信息
4.3.3.5 生成 jar 包
完成上述步驟百匆,可以在控制臺(tái)中通過命令 ../../gradlew assemble
來執(zhí)行編譯任務(wù),就可以輸出我們需要的 jar 文件 (htlintrules_jar-0.0.1.jar
) 了
4.3.3.6 jar 的使用
按照 Google 方法瞧毙,可以將 htlintrules_jar-0.0.1.jar
拷貝到 ~/.android/lint
中胧华,但缺點(diǎn)是針對(duì)會(huì)影響一臺(tái)機(jī)器其他的工程。很明顯宙彪,我們的自定義 Lint
檢查有很多是項(xiàng)目中特有的一些編碼規(guī)范矩动。
為此,我們采用 LinkedIn
方案:將 jar 放到一個(gè) aar 中释漆。這樣我們就可以針對(duì)工程進(jìn)行自定義 Lint悲没,lint.jar 只對(duì)當(dāng)前工程有效。
在現(xiàn)有的 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'
同時(shí)新建另一個(gè)工程 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 檢查庫(kù)
...
}
4.3.3.7 自定義 Lint 檢查執(zhí)行及結(jié)果檢查
在 ${項(xiàng)目工程}/app/
目錄下執(zhí)行 ../gradlew lint
:
根據(jù)提示查看 lint-result.html
文件逊笆,可以查看到前面編寫的 ActivitySuperClassDetector.ACTIVITY_SUPER_CLASS_ISSUE
已經(jīng)生效栈戳,并且檢查出了相關(guān)的非規(guī)范代碼。
4.3.3.8 排除錯(cuò)誤的檢查結(jié)果
前面很好的給出了檢查結(jié)果了难裆,然而我們會(huì)發(fā)現(xiàn)子檀,FullScreenVideoActivity
確實(shí)是需要的錯(cuò)誤檢查結(jié)果镊掖,而 WXEntryActivity
卻不是,這個(gè)類是有集成微信分享時(shí)需要的褂痰,并且按照微信開放平臺(tái)的文檔來編寫亩进,因此并不需要按照項(xiàng)目規(guī)范,繼承 BaseBlankActivity
或 BaseActionBarActivity
缩歪。為此归薛,我們期望 WXEntryActivity
不應(yīng)該被檢查出 WrongActivitySuperClass
錯(cuò)誤
為此,我們可以在 WXEntryActivity
類名簽名添加 SuppressLint
注解:
@SuppressLint("WrongActivitySuperClass")
public class WXEntryActivity extends Activity implements IWXAPIEventHandler{
...
}
-
排除 java 類或者方法的 Lint 檢查
若需要抑制某個(gè) Issue 檢查匪蝙,可以在類定義簽名或者方法定義簽名主籍,添加注解
@SuppressLint(${IssueId})
。這里設(shè)置的就是具體某個(gè)Issue
的id
值若需要抑制全部的 Issue 檢查骗污,可以使用
all
關(guān)鍵字崇猫,比如:@SuppressLint("all")
-
排除 xml 資源的 Lint 檢查
如項(xiàng)目中引入微博分享 sdk,按照官方文檔需忿,需要在 AndroidManifest 中聲明
com.sina.weibo.sdk.net.DownloadService
這個(gè) Service诅炉,而這個(gè) Service 會(huì)被 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>
這里對(duì)于單個(gè) Issue 過濾的規(guī)則為:
tools:ignore=${IssueId}
如果需要過濾全部的 Issue屋厘,可以使用
all
關(guān)鍵字:tools:ignore="all"
4.4 其他代碼檢查工具
4.4.1 360 火線
360 火線 是 360 公司和信息安全部門深度合作涕烧,定制的適用于 360 公司產(chǎn)品的安卓 APP 安全檢查規(guī)則『谷鳎總共覆蓋 61 項(xiàng)代碼檢查议纯。使用也非常方便,細(xì)節(jié)看 使用文檔溢谤,可以直接使用 jar 包并執(zhí)行命令或集成 Android Studio Plugin 執(zhí)行檢查
4.4.2 pmd
pmd 代碼檢查工具瞻凤,包含 16 個(gè)規(guī)則集,涵蓋了 Java 的各種常見問題世杀。其中規(guī)則集包含 基本(rulesets/basic.xml)
阀参,終結(jié)函數(shù)(finalizer)
,未使用的代碼(rulesets/unusedcode.xml)
瞻坝,設(shè)計(jì)(rulesets/design.xml)
等蛛壳。
相比 FindBugs
,pmd
的一些規(guī)則更具爭(zhēng)議所刀,但 pmd
支持我們構(gòu)建自己的規(guī)則集
<?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>
4.5 代碼檢查工具整合及集成 jenkins
為整合這些檢查工具衙荐,在 gradle
中自定義 check 命名,并依賴其他的 task浮创。在執(zhí)行檢查的時(shí)候忧吟,可以通過 ./gradlew check
來執(zhí)行全部的檢查命令。
check.dependsOn 'checkstyle', 'findbugs', 'pmd', 'lint'
另一方面斩披,這種代碼檢查溜族,如果等到開發(fā)完成的時(shí)候再去執(zhí)行胸嘴,很可能問題積累了很多,甚至導(dǎo)致產(chǎn)品上線前斩祭,開發(fā)并不能來得及修正全部的問題。為此乡话,可以將代碼檢查的命令集成 jenkins
摧玫,保證開發(fā)每天都能看到當(dāng)前的代碼的缺陷,能及時(shí)的修改
5 總結(jié)
我們從編碼前的編碼規(guī)范绑青,編碼進(jìn)行中的編碼模板诬像,編碼結(jié)束后的代碼靜態(tài)檢查,保障了程序小伙伴們的代碼闸婴。除此之外坏挠,還有很多不完善的地方需要我們做進(jìn)一步處理:
- 和特定項(xiàng)目相關(guān)的自定義 Lint 檢查項(xiàng),僅支持了一部分的編碼規(guī)范邪乍,自定義 Lint 檢查項(xiàng)需要后續(xù)完成
- 雖然檢查工具很多降狠,檢查的結(jié)果內(nèi)容也很多,而很明顯庇楞,這些檢查工具相互之間是有重復(fù)的情況(主要是純 java 代碼部分的檢查)榜配,因此如果整理檢查結(jié)果,并過濾出我們真正關(guān)心的問題吕晌,也是后續(xù)需要完成的內(nèi)容