您的浏览器不支持CSS3,建议使用Firfox、Chrome等浏览器,以取得最佳显示效果

Android Lint:基本使用与配置

Android 1,661℃ 0 7个月前 (12-30)

说明

本文部分内容要求读者有一定的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相关的使用技巧可以参考 http://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的使用可参考 http://www.paincker.com/android-studio-skill

可在Android Studio的设置中配置代码实时检查时,要检查的项

  1. Preference – Editor – Inspections:代码检查
  2. 默认有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

参考资料与扩展阅读

来自为知笔记(Wiz)

最后,欢迎扫码关注微信公众号。

0

暂无评论

评论前:需填写以下信息,或 登录

用户登录

忘记密码?