摘要:自定義方案提供了一種思路將放到一個(gè)中。這樣我們就可以針對(duì)工程進(jìn)行自定義只對(duì)當(dāng)前工程有效。開(kāi)發(fā)插件后,繼承了原生和自定義的所有檢查規(guī)則,內(nèi)置。我們創(chuàng)建一個(gè)內(nèi)部類(lèi)來(lái)表示檢查樹(shù)的過(guò)程。
為什么需要自定義
原生Lint無(wú)法滿(mǎn)足我們團(tuán)隊(duì)特有的需求,例如:編碼規(guī)范。
原生Lint存在一些檢測(cè)缺陷或者缺少一些我們認(rèn)為有必要的檢測(cè)。
自定義方案LinkedIn提供了一種思路 : 將jar放到一個(gè)aar中。這樣我們就可以針對(duì)工程進(jìn)行自定義Lint,lint.jar只對(duì)當(dāng)前工程有效。
Google指出,aar文件可以包含一個(gè)自定義的lint.jar文件
aar雖然方便,但依然有很多問(wèn)題,原因在于,要想統(tǒng)一開(kāi)發(fā)者的lint檢查,每個(gè)開(kāi)發(fā)者都需要配置lint.xml、lintOptions。
開(kāi)發(fā)插件,統(tǒng)一管理lint.xml和lintOptions,自動(dòng)添加aar。
開(kāi)發(fā)插件后,繼承了原生lint和自定義lint的所有檢查規(guī)則,內(nèi)置lintOptions。
方案實(shí)施 創(chuàng)建Android項(xiàng)目該項(xiàng)目主要用于測(cè)試規(guī)則是否正確
創(chuàng)建lint依賴(lài)項(xiàng)目該項(xiàng)目主要用于將lint.jar轉(zhuǎn)換為lint.aar文件,并提供maven庫(kù)發(fā)布功能
創(chuàng)建lint的Java項(xiàng)目該項(xiàng)目主要用于編寫(xiě)lint的自定義規(guī)則類(lèi),以及打包成lint.jar文件
創(chuàng)建plugin的groovy項(xiàng)目該項(xiàng)目主要用于編寫(xiě)lint的引用,lint的自定義規(guī)則,并提供maven庫(kù)發(fā)布功能
自定義lint類(lèi)引入lint-api和lint-checks的依賴(lài)包
創(chuàng)建IssueRegistry類(lèi),用于注冊(cè)所有的ISSUE
創(chuàng)建相關(guān)的Detector類(lèi),繼承自Detector,實(shí)現(xiàn)相關(guān)的接口
以CustomEquaslDetector類(lèi)為例,其繼承Detector類(lèi),實(shí)現(xiàn)JavaScanner接口。注意Detector類(lèi)是一個(gè)抽象類(lèi),其包含了很多內(nèi)部接口類(lèi),并實(shí)現(xiàn)了它們的所有方法。接口類(lèi)如下所示:
XmlScanner
ResourceFolderScanner
OtherFileScanner
JavaScanner
GradleScanner
ClassScanner
BinaryResoucrceScanner
看類(lèi)的名稱(chēng)就知道其相對(duì)應(yīng)的作用,所以我們實(shí)現(xiàn)JavaScanner類(lèi)。
創(chuàng)建ISSUE對(duì)象,并注冊(cè)到IssueRegistry類(lèi)中,創(chuàng)建對(duì)象的方式是靜態(tài)工程方法創(chuàng)建:
public static final Issue ISSUE = Issue.create( "LogUse", "避免使用Log/System.out.println", "使用Ln,防止在正式包打印log", Category.SECURITY, 5, Severity.ERROR, new Implementation(LogDetector.class, Scope.JAVA_FILE_SCOPE));
id : 唯一值,應(yīng)該能簡(jiǎn)短描述當(dāng)前問(wèn)題。利用Java注解或者XML屬性進(jìn)行屏蔽時(shí),使用的就是這個(gè)id。
summary : 簡(jiǎn)短的總結(jié),通常5-6個(gè)字符,描述問(wèn)題而不是修復(fù)措施。
explanation : 完整的問(wèn)題解釋和修復(fù)建議。
category : 問(wèn)題類(lèi)別。詳見(jiàn)下文詳述部分。
priority : 優(yōu)先級(jí)。1-10的數(shù)字,10為最重要/最嚴(yán)重。
severity : 嚴(yán)重級(jí)別:Fatal, Error, Warning, Informational, Ignore。
Implementation : 為Issue和Detector提供映射關(guān)系,Detector就是當(dāng)前Detector。聲明掃描檢測(cè)的范圍Scope,Scope用來(lái)描述Detector需要分析時(shí)需要考慮的文件集,包括:Resource文件或目錄、Java文件、Class文件。
相對(duì)應(yīng)的,其在lint的html報(bào)告中對(duì)應(yīng)的關(guān)系,如下:
總結(jié)下,每個(gè)Lint檢查都需要四部分:
Issues 一個(gè)issue對(duì)應(yīng)于Android項(xiàng)目中的一個(gè)可能的問(wèn)題或bug。
Detectors 一個(gè)detector用于搜尋代碼潛在的Issues,一個(gè)多帶帶的detector可以搜尋多個(gè)獨(dú)立但相關(guān)的Issues。
implementations 一個(gè)implementation將一個(gè)Issue連接到對(duì)應(yīng)的Detector類(lèi),并指定在哪兒搜尋Issue。
Registries 一個(gè)注冊(cè)類(lèi)包含一系列的Issues,默認(rèn)的Registry類(lèi)是BuiltinIssueRegistry類(lèi),因?yàn)槲覀兙帉?xiě)了自己的自定義Issues,所以我們需要提供自定義Registry類(lèi)。
舉個(gè)例子:
public class EnumDetector extends Detector implements Detector.JavaScanner { ... // Implementation and Issue code from above /** * Constructs a new {@link EnumDetector} check */ public EnumDetector() { } @Override public boolean appliesTo(@NonNull Context context, @NonNull File file) { return true; } @Override public EnumSetgetApplicableFiles() { return Scope.JAVA_FILE_SCOPE; } @Override public List > getApplicableNodeTypes() { return Arrays. >asList( EnumDeclaration.class ); } @Override public AstVisitor createJavaVisitor(@NonNull JavaContext context) { return new EnumChecker(context); } private static class EnumChecker extends ForwardingAstVisitor { private final JavaContext mContext; public EnumChecker(JavaContext context) { mContext = context; } @Override public boolean visitEnumDeclaration(EnumDeclaration node) { mContext.report(ISSUE, Location.create(mContext.file), ISSUE.getBriefDescription(TextFormat.TEXT)); return super.visitEnumDeclaration(node); } } }
appliesTo方法 決定是否給定的文件可用并可被掃描,我們r(jià)eturn true來(lái)檢查給定的范圍
getApplicableFiles方法定義了Detector的范圍,該例是所有的Java文件。
getApplicableNodeTypes方法,注意其中的node,指的是一段代碼。一個(gè)node可以是一個(gè)類(lèi)的申明,一個(gè)方法的調(diào)用,或者一個(gè)注釋?zhuān)驗(yàn)槲覀冎魂P(guān)心Enum的申明,所有返回 EnumDeclaration.class。
createJavaVisitor方法是Lombok遍歷Java樹(shù)的方法。我們創(chuàng)建一個(gè)EnumChecker內(nèi)部類(lèi)來(lái)表示檢查node樹(shù)的過(guò)程。
因?yàn)橹挥幸粋€(gè)node類(lèi)型需要被檢查,所有覆寫(xiě)visitEnumDeclaration方法。每當(dāng)有一個(gè)Enum的申明,該方法就會(huì)被執(zhí)行一次。
mContext.report方法用于問(wèn)題的報(bào)告。ISSUE為哪一種Issue,location為問(wèn)題的發(fā)現(xiàn)地,以及Issue的簡(jiǎn)要描述。
在看下IntentExtraKeyDetector類(lèi):
public class IntentExtraKeyDetector extends Detector implements JavaScanner { public static final Issue ISSUE = Issue.create( "extraKey", "please avoid use hardcode defined intent extra key", "defined in another activity", Category.SECURITY, 5, Severity.ERROR, new Implementation(IntentExtraKeyDetector.class, Scope.JAVA_FILE_SCOPE)); public IntentExtraKeyDetector() {} @Override public boolean appliesTo(@NonNull Context context, @NonNull File file) { return true; } @NonNull @Override public Speed getSpeed() { return Speed.FAST; } // ---- Implements JavaScanner ---- @Override public List使用指南getApplicableMethodNames() { return Collections.singletonList("putExtra"); } @Override public void visitMethod(@NonNull JavaContext context, @Nullable AstVisitor visitor, @NonNull MethodInvocation node) { ResolvedNode resolved = context.resolve(node); if (resolved instanceof ResolvedMethod) { ResolvedMethod method = (ResolvedMethod) resolved; if (method.getContainingClass().isSubclassOf("android.content.Intent", false) && method.getArgumentCount() == 2) { ensureExtraKey(context, node); } } } private static void ensureExtraKey(JavaContext context, @NonNull MethodInvocation node) { //獲取method的參數(shù)值 StrictListAccessor accessor = node.astArguments(); if (accessor.size() != 2) { return; } Expression expression = accessor.first(); //當(dāng)?shù)谝粋€(gè)參數(shù)值類(lèi)型為String,這樣是硬編碼 if (expression instanceof StringLiteral){ context.report(ISSUE, node, context.getLocation(node), "please avoid use hardcode defined Intent.putExtra key"); return; } //當(dāng)?shù)谝粋€(gè)參數(shù)值類(lèi)型為變量 //ConstantEvaluator.evaluate(context, expression); if (expression instanceof VariableReference){ //獲取該變量的定義name String targetName = ((VariableReference)expression).astIdentifier().astValue(); if (!targetName.startsWith("EXTRA_")){ context.report(ISSUE, node, context.getLocation(node), "please defined intent extra key start with EXTRA_"); } } //當(dāng)?shù)谝粋€(gè)參數(shù)值是其他類(lèi)的變量時(shí) if (expression instanceof Select){ String targetName = ((Select)expression).astIdentifier().astValue(); if (!targetName.startsWith("EXTRA_")){ context.report(ISSUE, node, context.getLocation(node), "please defined intent extra key start with EXTRA_"); } } }
在project中的build.gradle文件中的dependencies中添加
classpath "com.mucfc.muna.lint:plugin:latest.integration"
在module app中的build.gradle文件中,添加:
apply plugin: "MuLintPlugin"融合項(xiàng)目后 使用系統(tǒng)Toast,而沒(méi)有使用muna中的自定義toast 使用Bundle.putXXX("key","value")
注意key不應(yīng)該這樣定義
使用Intent.putExtra(key,value);注意key不能直接硬編碼,且key定義的String引用必須為EXTRA_開(kāi)頭
定義Activity類(lèi)必須繼承BaseActivity(Fragment類(lèi)同)注意因?yàn)樽约壕帉?xiě)的BaseActivity,可以使用@Suppressint("activityUse")去除錯(cuò)誤
使用equals方法在equals(value)中,value不能為硬編碼或定義在該類(lèi)中的static final字符串,因?yàn)楫?dāng)為指定字符串的時(shí)候,需要value.equals(),防止空指針
使用原始的Log.d()方法因?yàn)橛蠱uLog,所以不應(yīng)該再次使用Log.d,防止敏感信息泄露。
文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請(qǐng)注明本文地址:http://specialneedsforspecialkids.com/yun/65055.html
摘要:模仿的功能掘金本模仿了的功能。國(guó)內(nèi)曾經(jīng)出現(xiàn)的團(tuán)購(gòu)類(lèi)網(wǎng)站有多家,到四年多以后的現(xiàn)在,美團(tuán)已經(jīng)是成為國(guó)內(nèi)最大的本地生活服務(wù)平臺(tái),不管怎餓了么移動(dòng)的架構(gòu)演進(jìn)掘金引言時(shí)代演進(jìn),技術(shù)也隨之發(fā)展。 模仿 Smartisan OS 的 BigBang 功能 ??? - Android - 掘金 本 Demo 模仿了 Smartisan OS 的 BigBang 功能。App 打開(kāi)會(huì)從剪切板讀取文字并...
閱讀 3213·2021-11-24 09:39
閱讀 2942·2021-11-23 09:51
閱讀 899·2021-11-18 10:07
閱讀 3549·2021-10-11 10:57
閱讀 2750·2021-10-08 10:04
閱讀 3008·2021-09-26 10:11
閱讀 1053·2021-09-23 11:21
閱讀 2796·2019-08-29 17:28