摘要
Android Lint:基本使用与配置
说明
本文部分内容要求读者有一定的Gradle基础,例如Gradle基本操作、Gradle插件开发基础等。
Lint简介
Lint是Android提供的一套静态代码检查工具,可对Java代码、资源文件等做检查。
在Android Studio中开发时,经常会看到一些关于Android API的警告,例如使用Toast时忘了调用show方法、布局文件中直接引用了硬编码的文本而没有定义到strings.xml
中等,这些都是Android Lint检查实现的。
Lint检查执行方式
Lint有多种执行方式,这里做个简单总结。
也可以参考谷歌官方文档 https://developer.android.com/studio/write/lint.html
Android Studio相关的使用技巧可以参考 https://www.paincker.com/android-studio-skill
Android Studio的Code Inspections
Inspections是IDEA中用于代码静态检查的组件,包括语法检查、拼写检查等,Android Studio在IDEA的Inspections基础上实现了Lint检查。
实时执行
在Android Studio中写代码时,Inspections会实时执行,其中包括Android定义的Lint规则,并在代码中直接标为黄色警告或红色错误。还可以使用Andorid Studio的Intentions
工具快速添加SupressLint
或自动修复。
Intentions的使用可参考 https://www.paincker.com/android-studio-skill
可在Android Studio的设置中配置代码实时检查时,要检查的项
- Preference – Editor – Inspections:代码检查
- 默认有Default和Project Default两套配置,作用范围分别是全局和当前工程
手动执行
在Android Studio中可从菜单Analyze - Inspect Code
手动执行Inspections,可选择要检查代码的范围(Inspection Scope),以及要检查的问题(Inspection Profile)。执行完成后效果如图,可以双击结果定位有问题的代码
Gradle Task执行
Gradle中编译代码时,会以任务(Task)的形式组织每一步操作,Task之间可以存在依赖关系,执行一个Task前,会先执行它所依赖的Task。
Android的Gradle环境下,有下面几个常见的Task。
- assemble:Gradle内建的编译任务
- check:Gradle内建的检查任务
- test:Gradle内建的测试任务
- build:包含assemble、check、test
- lint:Android插件定义的Lint检查任务,被包含到check任务中
在命令行中可调用gradle执行Task,例如在命令行中调用gradle lint
即可执行lint任务。对于某个具体的BuildType(例如Debug)和ProductFlavor(例如Huawei),还可以执行gradle lintHuaweiDebug
只对这种Build版本做Lint检查。
Lint默认会把所有结果以XML和HTML格式,输出到build/reports/lint-results-xxx
中,可以在此查看所有Lint问题,包括具体在哪一行,以及Lint问题对应的ID。
Lint命令行
直接执行lint
命令,可执行文件位于<android-home>/tools/lint
(<android-home>
即AndroidSDK所在目录)。结果也会输出到报表中。具体参数配置可以用lint --help
查看。
Lint配置
Issue、Severity
每个Lint规则,称为一个Issue。
每个Issue有一个id,例如NewApi、WrongViewCast、ShowToast等。
每个Issue都定义了默认的优先级(Severity),包括Fatal、Error、Warning、Information、Ignore。其中Ignore表示忽略该Issue的检查。
实际Issue的Severity可被Lint配置覆盖。
SupressLint
有时Lint会误报,或者特定代码明确不希望或不需要检查特定Lint问题。此时可使用SupressLint注解,对这个类、方法等跳过指定id的Lint检查,类似下面的写法。
@SuppressLint("NewApi")
private void method() {
new View(this).setOnScrollChangeListener(new View.OnScrollChangeListener() {
@Override
public void onScrollChange(View v, int scrollX, int scrollY, int oldScrollX, int oldScrollY) {
}
});
}
Lint.xml的配置
在Android工程中,可以创建XML文件配置Lint规则的优先级和忽略路径,示例如下。配置文件默认路径为模块目录下的lint.xml
,也可以通过LintOptions指定。
<?xml version="1.0" encoding="utf-8" ?>
<lint>
<!--配置所有Issue的默认优先级-->
<issue id="all" severity="warning"/>
<!--配置指定Issue的优先级-->
<issue id="ShowToast" severity="error"/>
<!--忽略指定路径的指定问题,支持正则表达式或path匹配-->
<issue id="NewApi">
<ignore regexp=".*MainActivity.java"/>
<ignore path="**/com/demo/lint/MainActivity.java"/>
<ignore path="res/layout/activation.xml"/>
</issue>
</lint>
LintOptions的配置
在Android工程的build.gradle
中,可以通过lintOptions
配置Lint,例如下面的代码。LintOptions中配置的Issue优先级,会覆盖lint.xml
中的配置。
android {
lintOptions {
abortOnError true
ignoreWarnings false
warningsAsErrors false
check('LogUse', 'SystemOut')
lintConfig file('lint.xml')
}
}
其中一些常用配置项如下:
abortOnError = true
,则发现Error级别Lint问题时会中断编译ignoreWarnings = true
,则不检查Warning级别的Lint问题warningsAsErrors = true
,则对Warnning问题会和Error一样处理checkReleaseBuilds
,默认为true,Release时检查fatal级别的问题lintConfig
,默认为lint.xml
,指定XML配置文件位置enable
,配置要检查的问题disable
,指定不检查的问题check
,指定所有要检查的问题,没有指定的都不检查
LintOptions的配置,可以参考
Android Gradle DSL官方文档 http://google.github.io/android-gradle-dsl/current/com.android.build.gradle.internal.dsl.LintOptions.html
Severity计算规则
实际学习发现,官方文档中说的LintOptions配置并不是很明白,特别是check、disable、enable等参数的优先级。通过查看其代码实现,才弄清楚了配置的使用。
对于某个Issue的Severity计算规则,可用伪代码描述如下。
CliConfiguration.getSeverity() {
severity = CliConfiguration.computeSeverity() {
severity = DefaultConfiguration.getSeverity() {
XML配置文件中设置的优先级(例如<issue id="NewApi" severity="ignore"/>)
XML配置文件中用“all”设置的默认优先级(例如<issue id="all" severity="ignore"/>)
如果mParent非空,则返回mParent.getSeverity (例如defaultConfig和Flavor中分别配置了LintOptions)
LintCliClient.getDefaultSeverity() {
如果设置了checkAllWarnings,则返回Issue定义的优先级
DefaultConfiguration.getDefaultSeverity {
如果Issue定义了默认不启用(isEnabledByDefault),则返回Ignore
返回Issue定义的优先级
}
}
}
如果disable参数包含了Issue则返回Ignore
SeverityOverrides参数设置的优先级
如果是enabled和check参数都包含的Issue {
如果severity == Ignore,则返回Warning
返回severity
}
如果check非空且没有包含该Issue,则返回Ignore
返回severity
}
如果设置了FatalOnly且severity != Fatal,则返回Ignore
如果设置了WarningsAsErrors且severity == Warning,则返回Error
如果设置了IgnoreWarning且severity == Warning,则返回Ignore
返回severity
}
具体实现可参考下面代码中的CliConfiguration.getSeverity
。
// com.android.tools.lint.client.api.DefaultConfiguration
@NonNull
protected Severity getDefaultSeverity(@NonNull Issue issue) {
if (!issue.isEnabledByDefault()) {
return Severity.IGNORE;
}
return issue.getDefaultSeverity();
}
@Override
@NonNull
public Severity getSeverity(@NonNull Issue issue) {
ensureInitialized();
Severity severity = mSeverity.get(issue.getId());
if (severity == null) {
severity = mSeverity.get(VALUE_ALL);
}
if (severity != null) {
return severity;
}
if (mParent != null) {
return mParent.getSeverity(issue);
}
return getDefaultSeverity(issue);
}
// com.android.tools.lint.LintCliClient$CliConfiguration extends DefaultConfiguration
@NonNull
@Override
protected Severity getDefaultSeverity(@NonNull Issue issue) {
if (mFlags.isCheckAllWarnings()) {
return issue.getDefaultSeverity();
}
return super.getDefaultSeverity(issue);
}
private Severity computeSeverity(@NonNull Issue issue) {
Severity severity = super.getSeverity(issue);
String id = issue.getId();
Set<String> suppress = mFlags.getSuppressedIds();
if (suppress.contains(id)) {
return Severity.IGNORE;
}
Severity manual = mFlags.getSeverityOverrides().get(id);
if (manual != null) {
return manual;
}
Set<String> enabled = mFlags.getEnabledIds();
Set<String> check = mFlags.getExactCheckedIds();
if (enabled.contains(id) || (check != null && check.contains(id))) {
// Overriding default
// Detectors shouldn't be returning ignore as a default severity,
// but in case they do, force it up to warning here to ensure that
// it's run
if (severity == Severity.IGNORE) {
severity = issue.getDefaultSeverity();
if (severity == Severity.IGNORE) {
severity = Severity.WARNING;
}
}
return severity;
}
if (check != null && issue != LINT_ERROR && issue != PARSER_ERROR) {
return Severity.IGNORE;
}
return severity;
}
@NonNull
@Override
public Severity getSeverity(@NonNull Issue issue) {
Severity severity = computeSeverity(issue);
if (mFatalOnly && severity != Severity.FATAL) {
return Severity.IGNORE;
}
if (mFlags.isWarningsAsErrors() && severity == Severity.WARNING) {
severity = Severity.ERROR;
}
if (mFlags.isIgnoreWarnings() && severity == Severity.WARNING) {
severity = Severity.IGNORE;
}
return severity;
}
查看支持的所有Lint Issue Id
执行<android-home>/tools/lint --list
命令,可查看支持的所有Issue的id。
Valid issue categories:
Correctness
Correctness:Messages
Security
Performance
Usability:Typography
Usability:Icons
Usability
Accessibility
Internationalization
Bi-directional Text
Valid issue id's:
"ContentDescription": Image without contentDescription
"AddJavascriptInterface": addJavascriptInterface Called
"ShortAlarm": Short or Frequent Alarm
"AlwaysShowAction": Usage of showAsAction=always
"ShiftFlags": Dangerous Flag Constant Declaration
"LocalSuppress": @SuppressLint on invalid element
"UniqueConstants": Overlapping Enumeration Constants
"InlinedApi": Using inlined constants on older versions
"Override": Method conflicts with new inherited method
"NewApi": Calling new methods on older versions
"UnusedAttribute": Attribute unused on older versions
"AppCompatMethod": Using Wrong AppCompat Method
"AppCompatResource": Menu namespace
"AppIndexingError": Wrong Usage of App Indexing
"AppIndexingWarning": Missing App Indexing Support
"InconsistentArrays": Inconsistencies in array element counts
"Assert": Assertions
"BackButton": Back button
"ButtonCase": Cancel/OK dialog button capitalization
"ButtonOrder": Button order
"ButtonStyle": Button should be borderless
"ByteOrderMark": Byte order mark inside files
"MissingSuperCall": Missing Super Call
"AdapterViewChildren": AdapterViews cannot have children in XML
"ScrollViewCount": ScrollViews can have only one child
"GetInstance": Cipher.getInstance with ECB
"CommitTransaction": Missing commit() calls
"Recycle": Missing recycle() calls
"ClickableViewAccessibility": Accessibility in Custom Views
"EasterEgg": Code contains easter egg
"StopShip": Code contains STOPSHIP marker
"CustomViewStyleable": Mismatched Styleable/Custom View Name
"CutPasteId": Likely cut & paste mistakes
"SimpleDateFormat": Implied locale in date format
"Deprecated": Using deprecated resources
"MissingPrefix": Missing Android XML namespace
"MangledCRLF": Mangled file line endings
"DuplicateIncludedIds": Duplicate ids across layouts combined with include
tags
"DuplicateIds": Duplicate ids within a single layout
"DuplicateDefinition": Duplicate definitions of resources
"ReferenceType": Incorrect reference types
"ExtraText": Extraneous text in resource files
"FieldGetter": Using getter instead of field
"FullBackupContent": Valid Full Backup Content File
"ValidFragment": Fragment not instantiatable
"PackageManagerGetSignatures": Potential Multiple Certificate Exploit
"GradleCompatible": Incompatible Gradle Versions
"AndroidGradlePluginVersion": Incompatible Android Gradle Plugin
"GradleDependency": Obsolete Gradle Dependency
"GradleDeprecated": Deprecated Gradle Construct
"GradleGetter": Gradle Implicit Getter Call
"GradleIdeError": Gradle IDE Support Issues
"GradlePath": Gradle Path Issues
"GradleDynamicVersion": Gradle Dynamic Version
"StringShouldBeInt": String should be int
"NewerVersionAvailable": Newer Library Versions Available
"AccidentalOctal": Accidental Octal
"GridLayout": GridLayout validation
"HandlerLeak": Handler reference leaks
"HardcodedDebugMode": Hardcoded value of android:debuggable in the manifest
"HardcodedText": Hardcoded text
"IconDuplicatesConfig": Identical bitmaps across various configurations
"IconDuplicates": Duplicated icons under different names
"GifUsage": Using .gif format for bitmaps is discouraged
"IconColors": Icon colors do not follow the recommended visual style
"IconDensities": Icon densities validation
"IconDipSize": Icon density-independent size validation
"IconExpectedSize": Icon has incorrect size
"IconExtension": Icon format does not match the file extension
"IconLauncherShape": The launcher icon shape should use a distinct silhouette
"IconLocation": Image defined in density-independent drawable folder
"IconMissingDensityFolder": Missing density folder
"IconMixedNinePatch": Clashing PNG and 9-PNG files
"IconNoDpi": Icon appears in both -nodpi and dpi folders
"IconXmlAndPng": Icon is specified both as .xml file and as a bitmap
"IncludeLayoutParam": Ignored layout params on include
"DisableBaselineAlignment": Missing baselineAligned attribute
"InefficientWeight": Inefficient layout weight
"NestedWeights": Nested layout weights
"Orientation": Missing explicit orientation
"Suspicious0dp": Suspicious 0dp dimension
"InvalidPackage": Package not included in Android
"DrawAllocation": Memory allocations within drawing code
"UseSparseArrays": HashMap can be replaced with SparseArray
"UseValueOf": Should use valueOf instead of new
"JavascriptInterface": Missing @JavascriptInterface on methods
"LabelFor": Missing labelFor attribute
"InconsistentLayout": Inconsistent Layouts
"InflateParams": Layout Inflation without a Parent
"DefaultLocale": Implied default locale in case conversion
"LocaleFolder": Wrong locale name
"InvalidResourceFolder": Invalid Resource Folder
"WrongRegion": Suspicious Language/Region Combination
"UseAlpha2": Using 3-letter Codes
"LogConditional": Unconditional Logging Calls
"LongLogTag": Too Long Log Tags
"LogTagMismatch": Mismatched Log Tags
"AllowBackup": Missing allowBackup attribute
"MissingApplicationIcon": Missing application icon
"DeviceAdmin": Malformed Device Admin
"DuplicateActivity": Activity registered more than once
"DuplicateUsesFeature": Feature declared more than once
"GradleOverrides": Value overridden by Gradle build script
"IllegalResourceRef": Name and version must be integer or string, not
resource
"MipmapIcons": Use Mipmap Launcher Icons
"MockLocation": Using mock location provider in production
"MultipleUsesSdk": Multiple <uses-sdk> elements in the manifest
"ManifestOrder": Incorrect order of elements in manifest
"MissingVersion": Missing application name/version
"OldTargetApi": Target SDK attribute is not targeting latest version
"UniquePermission": Permission names are not unique
"UsesMinSdkAttributes": Minimum SDK and target SDK attributes not defined
"WrongManifestParent": Wrong manifest parent
"ManifestTypo": Typos in manifest tags
"FloatMath": Using FloatMath instead of Math
"MergeRootFrame": FrameLayout can be replaced with <merge> tag
"InnerclassSeparator": Inner classes should use $ rather than .
"Instantiatable": Registered class is not instantiatable
"MissingRegistered": Missing registered class
"MissingId": Fragments should specify an id or tag
"LibraryCustomView": Custom views in libraries should use res-auto-namespace
"ResAuto": Hardcoded Package in Namespace
"NamespaceTypo": Misspelled namespace declaration
"UnusedNamespace": Unused namespace
"NegativeMargin": Negative Margins
"NestedScrolling": Nested scrolling widgets
"NfcTechWhitespace": Whitespace in NFC tech lists
"UnlocalizedSms": SMS phone number missing country code
"ObsoleteLayoutParam": Obsolete layout params
"OnClick": onClick method does not exist
"Overdraw": Overdraw: Painting regions more than once
"DalvikOverride": Method considered overridden by Dalvik
"OverrideAbstract": Not overriding abstract methods on older platforms
"ParcelCreator": Missing Parcelable CREATOR field
"UnusedQuantity": Unused quantity translations
"MissingQuantity": Missing quantity translation
"ImpliedQuantity": Implied Quantities
"ExportedPreferenceActivity": PreferenceActivity should not be exported
"PackagedPrivateKey": Packaged private key
"PrivateResource": Using private resources
"ProguardSplit": Proguard.cfg file contains generic Android rules
"Proguard": Using obsolete ProGuard configuration
"PropertyEscape": Incorrect property escapes
"UsingHttp": Using HTTP instead of HTTPS
"SpUsage": Using dp instead of sp for text sizes
"InOrMmUsage": Using mm or in dimensions
"PxUsage": Using 'px' dimension
"SmallSp": Text size is too small
"Registered": Class is not registered in the manifest
"RelativeOverlap": Overlapping items in RelativeLayout
"RequiredSize": Missing layout_width or layout_height attributes
"AaptCrash": Potential AAPT crash
"ResourceCycle": Cycle in resource definitions
"ResourceName": Resource with Wrong Prefix
"RtlCompat": Right-to-left text compatibility issues
"RtlEnabled": Using RTL attributes without enabling RTL support
"RtlSymmetry": Padding and margin symmetry
"RtlHardcoded": Using left/right instead of start/end attributes
"ScrollViewSize": ScrollView size validation
"SdCardPath": Hardcoded reference to /sdcard
"SecureRandom": Using a fixed seed with SecureRandom
"TrulyRandom": Weak RNG
"ExportedContentProvider": Content provider does not require permission
"ExportedReceiver": Receiver does not require permission
"ExportedService": Exported service does not require permission
"GrantAllUris": Content provider shares everything
"WorldReadableFiles": openFileOutput() call passing MODE_WORLD_READABLE
"WorldWriteableFiles": openFileOutput() call passing MODE_WORLD_WRITEABLE
"ServiceCast": Wrong system service casts
"SetJavaScriptEnabled": Using setJavaScriptEnabled
"CommitPrefEdits": Missing commit() on SharedPreference editor
"SignatureOrSystemPermissions": signatureOrSystem permissions declared
"SQLiteString": Using STRING instead of TEXT
"StateListReachable": Unreachable state in a <selector>
"StringFormatCount": Formatting argument types incomplete or inconsistent
"StringFormatMatches": String.format string doesn't match the XML format
string
"StringFormatInvalid": Invalid format string
"PluralsCandidate": Potential Plurals
"UseCheckPermission": Using the result of check permission calls
"CheckResult": Ignoring results
"ResourceAsColor": Should pass resolved color instead of resource id
"MissingPermission": Missing Permissions
"Range": Outside Range
"ResourceType": Wrong Resource Type
"WrongThread": Wrong Thread
"WrongConstant": Incorrect constant
"ProtectedPermissions": Using system app permission
"TextFields": Missing inputType or hint
"TextViewEdits": TextView should probably be an EditText instead
"SelectableText": Dynamic text should probably be selectable
"MenuTitle": Missing menu title
"ShowToast": Toast created but not shown
"TooDeepLayout": Layout hierarchy is too deep
"TooManyViews": Layout has too many views
"ExtraTranslation": Extra translation
"MissingTranslation": Incomplete translation
"Typos": Spelling error
"TypographyDashes": Hyphen can be replaced with dash
"TypographyEllipsis": Ellipsis string can be replaced with ellipsis character
"TypographyFractions": Fraction string can be replaced with fraction
character
"TypographyOther": Other typographical problems
"TypographyQuotes": Straight quotes can be replaced with curvy quotes
"UnusedResources": Unused resources
"UnusedIds": Unused id
"UseCompoundDrawables": Node can be replaced by a TextView with compound
drawables
"UselessLeaf": Useless leaf layout
"UselessParent": Useless parent layout
"EnforceUTF8": Encoding used in resource files is not UTF-8
"ViewConstructor": Missing View constructors for XML inflation
"ViewHolder": View Holder Candidates
"ViewTag": Tagged object leaks
"WrongViewCast": Mismatched view type
"Wakelock": Incorrect WakeLock usage
"WebViewLayout": WebViews in wrap_content parents
"WrongCall": Using wrong draw/layout method
"WrongCase": Wrong case for view tag
"InvalidId": Invalid ID declaration
"NotSibling": RelativeLayout Invalid Constraints
"UnknownId": Reference to an unknown id
"UnknownIdInLayout": Reference to an id that is not in the current layout
"SuspiciousImport": 'import android.R' statement
"WrongFolder": Resource file in the wrong res folder
参考资料与扩展阅读
使用 Lint 改进您的代码
https://developer.android.com/studio/write/lint.htmlAndAndroid Plugin DSL Reference:LintOptions
http://google.github.io/android-gradle-dsl/current/com.android.build.gradle.internal.dsl.LintOptions.htmlHow Do We Configure Android Studio to Run Its Lint on Every Build?
http://stackoverflow.com/questions/32631131/how-do-we-configure-android-studio-to-run-its-lint-on-every-buildWriting custom lint rules and integrating them with Android Studio inspections
https://android.jlelse.eu/writing-custom-lint-rules-and-integrating-them-with-android-studio-inspections-or-carefulnow-c54d72f00d30你可能不知道的Android Studio/IDEA使用技巧
https://www.paincker.com/android-studio-skillAndroid自定义Lint实践
http://tech.meituan.com/android_custom_lint.htmlAndroid Gradle配置快速入门
https://www.paincker.com/android-gradle-basicsGradle开发快速入门——DSL语法原理与常用API介绍
https://www.paincker.com/gradle-develop-basicsViewing PSI Structure
https://www.jetbrains.com/help/idea/viewing-psi-structure.htmlGit – Documentation
https://git-scm.com/documentation
最后,欢迎扫码关注微信公众号。程序员同行学习交流,聊天交友,国内外名企求职内推(微软 / 小冰 / Amazon / Shopee / Coupang / ATM / 头条 / 拼多多等),可加我微信 jzj2015 进技术群(备注进技术群,并简单自我介绍)。

本文由jzj1993原创,转载请注明来源:https://www.paincker.com/android-lint-1-usage
(标注了原文链接的文章除外)
暂无评论