通关Android Lint

12,554 阅读28分钟

Lint

​ Android Lint 是Android Studio 在ADT(Android Developer Tools)16提供的代码扫描工具,可以帮助我们发现和更正代码结构质量的问题。系统会报告该工具检测到的每个问题并提供问题的描述消息和严重级别,以便快速确定需要优先进行的修改。此外,我们还可以通过降低问题的严重级别以忽略与项目无关的问题,或者提高严重级别以突出特定问题。

优点

  • 无需实际执行应用
  • 不必编写测试用例

Lint工作流

下图显示了 lint 工具如何处理应用源文件。

lint

  • App Source Files : 应用源文件,包含组成Anroid项目的文件,包括Java,Kotlin和XML文件,图标以及Progurad配置文件。
  • lint.xml : 一个配置文件,可用于指定要排除的任何 lint 检查以及自定义问题严重级别。
  • lint Tool :一个静态代码扫描工具,可以从命令行或在 Android Studio 中对 Android 项目运行该工具。
  • lint Output :lint检查结果,可以在控制台或 Android Studio 的 Inspection Results 窗口中查看 lint 检查结果

手动进行Lint检查

  1. 依次选择 Analyze > Inspect Code,手动运行配置的 lint 及其他 IDE 检查。
image-20200610130611752

在左侧窗格树状视图中,通过展开并选择错误类别、类型和问题来查看检查结果。

右侧窗格显示选定错误类别、类型或问题的检查报告,并提供错误的名称和位置。在适用情况下,检查报告会显示问题概要等其他信息,以帮助您更正问题。

allowBackup属性确定是否可以备份应用程序的数据并恢复。

在SDK版本23及更高版本中,您的应用程序数据将在应用程序安装时自动备份和还原。考虑添加属性“ android:fullBackupContent”以指定“ @xml”资源,该资源配置要备份的文件。这个问题属于Security。

  1. 在Gradle命令行环境下,可直接用./gradlew lint执行Lint检查。

想使用Lint命令需要配置Lint环境变量,或者进入%ANDROID_HOME%\tools\bin目录下

会在命令行中输出相应信息:

> Task :app:lint
Ran lint on variant debug: 2 issues found
Ran lint on variant release: 2 issues found
Wrote HTML report to file:///D:/Develop/Project/AndroidLint/app/build/reports/lint-results.html
Wrote XML report to file:///D:/Develop/Project/AndroidLint/app/build/reports/lint-results.xml
Lint found no errors or warnings (1 error filtered by baseline lint-baseline.xml)

在一个项目目录下运行lint检查一系列文件,使用下面的命令:

 lint [flags] <project directory>

例如,你可以执行下面的命令,来扫描项目目录下和它的子目录下的文件。

MissingPrefix问题ID告诉lint只扫描缺少Android命名空间前缀的XML属性

lint --check MissingPrefix myproject
  1. 检查项目中是否存在某个问题

image-20200611180916145

image-20200611180945298

该功能允许我们输入一个Issue的id来检查项目中是否存在该id对应的Issue的问题

会在Inspection Results窗口显示对应的问题信息

Lint会帮助我们查找并优化哪些问题

  • 遗漏的翻译(比如国际化未被翻译的字段)
  • 布局性能(解决布局无用或太多,嵌套过多问题)
  • 没有引用的资源文件
  • 不一致的数组大小
  • 国际化的硬编码问题
  • 图标重复问题
  • 可用性问题(文本输入类型没有指定)
  • manifest文件内的错误

Lint 发现的每个问题都有描述信息和等级,我们可以很方便地定位问题,同时按照严重程度进行解决。当然这个“严重程度”我们可以手动调节,有些原则问题不容侵犯,必须提升到 error,而有的个别问题也可以无视。

Lint 问题种类

  • Correctness : 正确性,比如硬编码、使用过时 API 等
  • Performanc : 性能 有影响的编码,比如:静态引用,循环引用等
  • Internationalization : 国际化,直接使用汉字,没有使用资源引用等
  • Security : 安全性,比如在 WebView 中允许使用 JavaScriptInterface 等
  • Usability : 易用性,有更好的替换的 比如排版、图标格式建议.png格式 等
  • Accessibility : 无障碍,比如ImageView的contentDescription往往建议在属性中定义 等
1. Correctness 
    1) DuplicatedIds
    Layout中id应该唯一
    2) NewApi
    代码中使用的某些API高于Manifest中的Min SDK
    3) InconsistentArrays
    字符串国际化中,同一名字的的String-Array对应的item值不相同
    4) Registered
    Activity/Service/ContentProvider没有通过AndroidManifest注册
    5) Deprecated
    使用已经废弃的API
    6) PxUsage
    避免使用px,使用dp
    7) AppCompatCustomView
	Appcompat自定义小部件一般会让你继承自 android.support.v7.widget.AppCompat...
	不要直接扩展android.widget类,而应该扩展android.support.v7.widget.AppCompat中的一个委托类。
 
2. Correctness:Messeges
    1) MissingTranslation
    字符串国际化不完全
    2) ExtraTranslation
    国际化的字符串,在默认位置(defaultlocale),没有定义
    3) StringFormatInvalid
    如果字符串包含'%'字符,则该字符串可能是格式化字符串,将从Java代码传递给String.format以用特定值替换每个'%'事件。(有可能误报错误)
    4) Typos
    一般为Spelling error,该检查将检查字符串定义,如果发现任何看起来像拼写错误的单词,则会对其进行标记。
    虽然很常见,但一般不做处理。
    
3. Correctness:Assertions
	1) Assert (warning)
	尽量使用其他方式替代`Assert`,例如
	if (BuildConfig.DEBUG && !(speed > 0)) { throw new AssertionError() }
	替代
	assert maxSize > 0;
	2) UnusedAttribute
	此检查将查找在XML文件中设置的属性,如果文件的引入版本比应用程序所针对的最旧版本(具有minSdkVersion属性)要新就会有提示。
	如果外观/功能影响很大/很重要,应考虑其他实现方式,否则可以忽略。
	3) DuplicateIncludedIds
	两个独立的布局使用相同的ID是可以的。但是,如果布局与include标签结合使用,那么ID在任何情况下都必须是唯一的。
    (例如 Layout_A 通过include引入Layout_B,这两个布局不应该有相同ID的标签)
	

4. Security
    1) SetJavaScriptEnabled
    不确定你的程序中确实需要JavaScript就不要执行SetJavaScriptEnabled。
    2)ExportedContentProvider/ExportedReceiver/ExportedService/ExportedActivity
    ContentProvider/Receiver/Service/Activity的exported为true时,设置一个Permission,让使用者获取了Permission才能使用。
    3) HardcodedDebugMode
    不要在manifest中设置android:debuggable。
    设置它,编译的任何版本都要采用指定的debug模式。不设置,编译Eng版本采用debug模式;编译User版本采用release模式。

5. Performance
    1) DrawAllocation
    避免在绘制或者解析布局(draw/layout)时分配对象。E.g.,Ondraw()中实例化Paint对象。
    2) ObsoleteLayoutParam
    Layout中无用的参数。
    3) UseCompoundDrawables
    可优化的布局:如包含一个Imageview和一个TextView的线性布局,可被采用CompoundDrawable的TextView代替。
    4) UseSparseArrays
    尽量用Android的SparseArray代替Hashmap
    5) DisableBaselineAlignment
    如果LinearLayout被用于嵌套的layout空间计算,它的android:baselineAligned属性应该设置成false,以加速layout计算。
    6) FloatMath
    使用FloatMath代替Math。
    7) NestedWeights
    避免嵌套weight,那将拖累执行效率
    8) UnusedResources/UnusedIds
    未使用的资源会让应用程序变大并减慢了构建速度
    9) Overdraw
    如果为RootView指定一个背景Drawable,会先用Theme的背景绘制一遍,然后才用指定的背景,这就是所谓的“Overdraw”。
    可以设置theme的background为null来避免。
    10) UselessLeaf/UselessParent
    没有子级或者背景的layout可以删除(因为它是不可见的)
    具有子级且没有兄弟级,不是滚动视图或根级布局,且没有背景的布局可以删除,并将其子级直接移到父级中。


6. Usability:Icons
    1) IconNoDpi
    Icon在nodpi和指定dpi的目录下都出现,应删除一个。
    2) GifUsage
    Image不要用GIF,最好用PNG,可以用JPG。

7. Usability
    1) BackButton
    Android中不要设计有Back的按钮,Android中一般有Back的硬按键。
    2) ButtonCase
    Button的“Ok”/“Cancel”显示大小写一定,不要全大写或全小写。有标准的资源的字符串,不要自己再定义,而要用系统定义的:@android:string/ok和@android:string/cancel

8. Accessibility
    1) ContentDescription
    ImageView和ImageButton应该提供contentDescription

9. Internationalization
    1) HardcodeText
    硬编码的字符串应该在资源里定义
    2) EnforceUTF8
    所有XML资源文件都应该以UTF-8编码

    ...
lint --list
Valid issue categories:
    Correctness
    Correctness:Messages
    Correctness:Chrome OS
    Security
    Performance
    Usability:Typography
    Usability:Icons
    Usability
    Accessibility
    Internationalization
    Internationalization:Bidirectional Text

Valid issue id's:
"ContentDescription": Image without contentDescription
"AddJavascriptInterface": addJavascriptInterface Called
"ShortAlarm": Short or Frequent Alarm
"AllCaps": Combining textAllCaps and markup
"AllowAllHostnameVerifier": Insecure HostnameVerifier
"AlwaysShowAction": Usage of showAsAction=always
"InvalidUsesTagAttribute": Invalid name attribute for uses element.
"MissingIntentFilterForMediaSearch": Missing intent-filter with action
      android.media.action.MEDIA_PLAY_FROM_SEARCH
"MissingMediaBrowserServiceIntentFilter": Missing intent-filter with action
      android.media.browse.MediaBrowserService.
"MissingOnPlayFromSearch": Missing onPlayFromSearch.
"ImpliedTouchscreenHardware": Hardware feature touchscreen not explicitly
      marked as optional
"MissingTvBanner": TV Missing Banner
"MissingLeanbackLauncher": Missing Leanback Launcher Intent Filter.
"MissingLeanbackSupport": Missing Leanback Support.
"PermissionImpliesUnsupportedHardware": Permission Implies Unsupported
      Hardware
"UnsupportedTvHardware": Unsupported TV Hardware Feature
"SupportAnnotationUsage": Incorrect support annotation usage
"ShiftFlags": Dangerous Flag Constant Declaration
"LocalSuppress": @SuppressLint on invalid element
"SwitchIntDef": Missing @IntDef in Switch
"UniqueConstants": Overlapping Enumeration Constants
"InlinedApi": Using inlined constants on older versions
"Override": Method conflicts with new inherited method
"ObsoleteSdkInt": Obsolete SDK_INT Version Check
"NewApi": Calling new methods on older versions
"UnusedAttribute": Attribute unused on older versions
"AppCompatMethod": Using Wrong AppCompat Method
"AppCompatCustomView": Appcompat Custom Widgets
"AppCompatResource": Menu namespace
"GoogleAppIndexingApiWarning": Missing support for Firebase App Indexing Api
"GoogleAppIndexingWarning": Missing support for Firebase App Indexing
"AppLinksAutoVerifyError": App Links Auto Verification Failure
"AppLinksAutoVerifyWarning": Potential App Links Auto Verification Failure
"AppLinkUrlError": URL not supported by app for Firebase App Indexing
"TestAppLink": Unmatched URLs
"InconsistentArrays": Inconsistencies in array element counts
"Assert": Assertions
"BadHostnameVerifier": Insecure HostnameVerifier
"BatteryLife": Battery Life Issues
"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
"PermissionImpliesUnsupportedChromeOsHardware": Permission Implies Unsupported
      Chrome OS Hardware
"UnsupportedChromeOsHardware": Unsupported Chrome OS Hardware Feature
"GetInstance": Cipher.getInstance with ECB
"CommitTransaction": Missing commit() calls
"Recycle": Missing recycle() calls
"CommitPrefEdits": Missing commit() on SharedPreference editor
"ApplySharedPref": Use apply() on SharedPreferences
"ClickableViewAccessibility": Accessibility in Custom Views
"EasterEgg": Code contains easter egg
"StopShip": Code contains STOPSHIP marker
"MissingConstraints": Missing Constraints in ConstraintLayout
"VulnerableCordovaVersion": Vulnerable Cordova Version
"CustomViewStyleable": Mismatched Styleable/Custom View Name
"CutPasteId": Likely cut & paste mistakes
"SimpleDateFormat": Implied locale in date format
"SetTextI18n": TextView Internationalization
"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
"StringEscaping": Invalid string escapes
"UnpackedNativeCode": Missing android:extractNativeLibs=false
"UnsafeDynamicallyLoadedCode": load used to dynamically load code
"UnsafeNativeCodeLocation": Native code outside library directory
"EllipsizeMaxLines": Combining Ellipsize and Maxlines
"ExifInterface": Using android.media.ExifInterface
"ExtraText": Extraneous text in resource files
"FieldGetter": Using getter instead of field
"InvalidAnalyticsName": Invalid Analytics Name
"MissingFirebaseInstanceTokenRefresh": Missing Firebase Instance ID Token
      Refresh
"FontValidationError": Validation of font files
"FontValidationWarning": Validation of font files
"FullBackupContent": Valid Full Backup Content File
"ValidFragment": Fragment not instantiatable
"GetContentDescriptionOverride": Overriding getContentDescription() on a View
"PackageManagerGetSignatures": Potential Multiple Certificate Exploit
"AccidentalOctal": Accidental Octal
"UseOfBundledGooglePlayServices": Use of bundled version of Google Play
      services
"GradleCompatible": Incompatible Gradle Versions
"GradleDependency": Obsolete Gradle Dependency
"GradleDeprecated": Deprecated Gradle Construct
"DevModeObsolete": Dev Mode Obsolete
"DuplicatePlatformClasses": Duplicate Platform Classes
"GradleGetter": Gradle Implicit Getter Call
"GradlePluginVersion": Incompatible Android Gradle Plugin
"HighAppVersionCode": VersionCode too high
"GradleIdeError": Gradle IDE Support Issues
"GradlePath": Gradle Path Issues
"GradleDynamicVersion": Gradle Dynamic Version
"NotInterpolated": Incorrect Interpolation
"StringShouldBeInt": String should be int
"NewerVersionAvailable": Newer Library Versions Available
"MinSdkTooLow": API Version Too Low
"GridLayout": GridLayout validation
"HandlerLeak": Handler reference leaks
"HardcodedDebugMode": Hardcoded value of android:debuggable in the manifest
"HardcodedText": Hardcoded text
"HardwareIds": Hardware Id Usage
"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
"ConvertToWebp": Convert to WebP
"WebpUnsupported": WebP Unsupported
"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
"InstantApps": Instant App Issues
"DuplicateDivider": Unnecessary Divider Copy
"TrustAllX509TrustManager": Insecure TLS/SSL trust manager
"InvalidImeActionId": Invalid imeActionId declaration
"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
"JobSchedulerService": JobScheduler problems
"KeyboardInaccessibleWidget": Keyboard inaccessible widget
"LabelFor": Missing labelFor attribute
"InconsistentLayout": Inconsistent Layouts
"InflateParams": Layout Inflation without a Parent
"StaticFieldLeak": Static Field Leaks
"DefaultLocale": Implied default locale in case conversion
"LocaleFolder": Wrong locale name
"GetLocales": Locale crash
"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": AllowBackup/FullBackupContent Problems
"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
"WearableBindListener": Usage of Android Wear BIND_LISTENER is deprecated
"WrongManifestParent": Wrong manifest parent
"InvalidPermission": Invalid Permission Attribute
"ManifestResource": Manifest Resource References
"ManifestTypo": Typos in manifest tags
"FloatMath": Using FloatMath instead of Math
"MergeMarker": Code contains merge marker
"MergeRootFrame": FrameLayout can be replaced with <merge> tag
"IncompatibleMediaBrowserServiceCompatVersion": Obsolete version of
      MediaBrowserServiceCompat
"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
"NetworkSecurityConfig": Valid Network Security Config File
"MissingBackupPin": Missing Backup Pin
"PinSetExpiry": Validate <pin-set> expiration attribute
"NfcTechWhitespace": Whitespace in NFC tech lists
"UnlocalizedSms": SMS phone number missing country code
"ObjectAnimatorBinding": Incorrect ObjectAnimator Property
"AnimatorKeep": Missing @Keep for Animated Properties
"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
"PrivateApi": Using Private APIs
"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
"ParcelClassLoader": Default Parcel Class Loader
"PendingBindings": Missing Pending Bindings
"RecyclerView": RecyclerView Problems
"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
"ValidRestrictions": Invalid Restrictions Descriptor
"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
"SetWorldReadable": File.setReadable() used to make file world-readable
"SetWorldWritable": File.setWritable() used to make file world-writable
"GrantAllUris": Content provider shares everything
"WorldReadableFiles": openFileOutput() or similar call passing
      MODE_WORLD_READABLE
"WorldWriteableFiles": openFileOutput() or similar call passing
      MODE_WORLD_WRITEABLE
"ServiceCast": Wrong system service casts
"WifiManagerLeak": WifiManager Leak
"WifiManagerPotentialLeak": WifiManager Potential Leak
"SetJavaScriptEnabled": Using setJavaScriptEnabled
"SignatureOrSystemPermissions": signatureOrSystem permissions declared
"SQLiteString": Using STRING instead of TEXT
"SSLCertificateSocketFactoryCreateSocket": Insecure call to
      SSLCertificateSocketFactory.createSocket()
"SSLCertificateSocketFactoryGetInsecure": Call to
      SSLCertificateSocketFactory.getInsecure()
"StateListReachable": Unreachable state in a <selector>
"AuthLeak": Code might contain an auth leak
"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
"RestrictedApi": Restricted API
"WrongThread": Wrong Thread
"WrongConstant": Incorrect constant
"VisibleForTests": Visible Only For Tests
"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
"UnsafeProtectedBroadcastReceiver": Unsafe Protected BroadcastReceiver
"UnprotectedSMSBroadcastReceiver": Unprotected SMS BroadcastReceiver
"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
"VectorRaster": Vector Image Generation
"VectorDrawableCompat": Using VectorDrawableCompat
"VectorPath": Long vector paths
"InvalidVectorPath": Invalid vector paths
"ViewConstructor": Missing View constructors for XML inflation
"ViewHolder": View Holder Candidates
"ViewTag": Tagged object leaks
"WrongViewCast": Mismatched view type
"FindViewByIdCast": Add Explicit Cast
"Wakelock": Incorrect WakeLock usage
"WakelockTimeout": Using wakeLock without timeout
"InvalidWearFeatureAttribute": Invalid attribute for Wear uses-feature
"WearStandaloneAppFlag": Invalid or missing Wear standalone app flag
"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
"WrongThreadInterprocedural": Wrong Thread (Interprocedural)
查看更详细的信息:
lint --show
或者
访问 http://tools.android.com/tips/lint-checks

Lint 问题等级

从高到低:

  • Fatal : 致命的 , 该类型的错误会直接中断 ADT 导出 APK。
  • Error : 错误,明确需要解决的问题,包括Crash、明确的Bug、严重性能问题、不符合代码规范等,必须修复。
  • Warning : 警告,包括代码编写建议、可能存在的Bug、一些性能优化等,适当放松要求。
  • Information
  • Ignore

设置Lint问题检查

​ 默认情况下,运行 lint 扫描时,lint 工具会检查它支持的所有问题。 但是我们可以限制 lint 要检查的问题,并为这些问题分配严重级别。例如,禁止对与项目无关的特定问题进行 lint 检查,也可以将 lint 配置为以较低的严重级别报告非关键问题。

配置不同级别的 Lint 检查:

  • 全局(整个项目)
  • 项目模块
  • 生产模块
  • 测试模块
  • 打开的文件
  • 类层次结构
  • 版本控制系统 (VCS) 范围

image-20200610140518620

在 Android Studio 中配置 Lint

在项目根目录创建一个lint.xml文件

image-20200610140705912

lint.xml文件由一个封闭的父标签组成,它包含了一个或者多个子标签。Lint为每一个定义了唯一的id属性值,通过设置标识的安全属性,你可以改变一个问题的安全级别,或者这个问题的lint检查,并且可以指定该issue作用于指定的文件还是当前项目。把lint.xml放在app的目录下(lint.xml需要放在build.gradle同级目录),命令行执行lint时候,lint就会用lint.xml中的规则。另外,执行lint时还可以用参数–config指定一个全局的配置用于所有的项目。当项目中已有lint.xml,则对于某个issue而言,在lint.xml中没有对该issue特别定制的情况下,–config指定的文件中的该issue的定制才起作用。

    <?xml version="1.0" encoding="UTF-8"?>
    <lint>
        <!-- 在项目中禁止检测 id为 IconMissingDensityFolder的问题 -->
        <issue id="IconMissingDensityFolder" severity="ignore" />

        <!-- 忽略指定文件中的ObsoleteLayoutParam问题 -->
        <issue id="ObsoleteLayoutParam">
            <ignore path="res/layout/activation.xml" />
            <ignore path="res/layout-xlarge/activation.xml" />
        </issue>

        <!-- 忽略指定文件中的UselessLeaf问题 -->
        <issue id="UselessLeaf">
            <ignore path="res/layout/main.xml" />
        </issue>

        <!-- 将硬编码字符串(HardcodedText)的严重性更改为“错误” -->
        <issue id="HardcodedText" severity="error" />
    </lint>
    

image-20200610170959341

配置对Java或Kotlin的检查

要专门对 Android 项目中的某个类或方法停用 lint 检查,可以向该代码添加 @SuppressLint 注释。

    @SuppressLint("NewApi")
    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.main);
    

配置 XML 的 lint 检查

可以使用 tools:ignore 属性对 XML 文件的特定部分停用 lint 检查。在 lint.xml 文件中添加以下命名空间值,以便 lint 工具能够识别该属性

    namespace xmlns:tools="http://schemas.android.com/tools"

对 XML 布局文件的 <ConstraintLayout>元素中的 SmallSp 问题关闭 lint 检查,

如果某个父元素声明了 ignore 属性,则该元素的子元素会继承此属性。在本例中,也会对 <TextView> 子元素停用 lint 检查。

此时不会提示SmallSp错误

<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"   ------添加命名空间 
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".MainActivity"       
    tools:ignore="SmallSp">    <!--不对SmallSp进行检查-->  
    
    <TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="Hello World!"
        android:textSize="9sp"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintLeft_toLeftOf="parent"
        app:layout_constraintRight_toRightOf="parent"
        app:layout_constraintTop_toTopOf="parent" />

</androidx.constraintlayout.widget.ConstraintLayout>
    

禁止 lint 检查 XML 元素中的所有问题:

tools:ignore="all"                              使用关键字all

禁止检查多个问题:

tools:ignore="NewApi,StringFormatInvalid"       使用逗号分隔

在gradle中配置Lint规则

  android {
    // 移除lint检查的error,可以避免由于编译条件太过严格而编译不过的问题
    lintOptions {
        // 如果为 true,则当lint发现错误时停止 gradle构建 (默认为true)
        abortOnError false
        // 如果为 true,则只报告错误
        ignoreWarnings true
        // 不检查给定的问题id InvalidPackage: Package not included in Android
        disable 'InvalidPackage'
        // 不检查给定的问题id 资源类型错误
        disable "ResourceType"
        // 忽略因MissingTranslation导致Build Failed错误 "app_name"
        disable 'MissingTranslation'
        // 检查给定的问题 id 
        enable 'RtlHardcoded','RtlCompat', 'RtlEnabled' 
        // * 仅 * 检查给定的问题 id 
        check 'NewApi', 'InlinedApi'
        // 配置写入输出结果的位置;它可以是一个文件或 “stdout”(标准输出)
        textOutput 'stdout'
        // 如果为真,会生成一个XML报告,以给Jenkins之类的使用
        xmlReport false
        // 用于写入报告的文件(如果不指定,默认为lint-results.xml)
        xmlOutput file("lint-report.xml")
        // 如果为真,会生成一个HTML报告(包括问题的解释,存在此问题的源码,等等)
        htmlReport true
        // 写入报告的路径,它是可选的(默认为构建目录下的 lint-results.html )
        htmlOutput file("lint-report.html")
        // 设置为 true, 将使所有release 构建都以issus的严重性级别为fatal
        //(severity=false)的设置来运行lint
        // 并且,如果发现了致命(fatal)的问题,将会中止构建
        //(由上面提到的 abortOnError 控制)
        checkReleaseBuilds true
        // 设置给定问题的严重级别(severity)为fatal (这意味着他们将会
        // 在release构建的期间检查 (即使 lint 要检查的问题没有包含在代码中)
        fatal 'NewApi', 'InlineApi'
        // 设置给定问题的严重级别为error
        error 'Wakelock', 'TextViewEdits'
        // 设置给定问题的严重级别为warning
        warning 'ResourceAsColor'
        // 设置给定问题的严重级别(severity)为ignore (和不检查这个问题一样)
        ignore 'TypographyQuotes'
        // 如果为 true,则检查所有的问题,包括默认不检查问题
        checkAllWarnings true
        // 重置 lint 配置(使用默认的严重性等设置)。
        lintConfig file("default-lint.xml")
        // 设置为 true,则当有错误时会显示文件的全路径或绝对路径 (默认情况下为true)
        absolutePaths true
    }

}

before:

image-20200610152739018

add this:

image-20200610152422725

after:

image-20200610152509547

比如这个SmallSp,一般情况下是一个黄色的警告,在gradle中配置成fatal之后,此处提示为红色错误,并且在gralde构建时,如果存在SmallSp问题会停止构建。

记录Lint报错信息

可以为项目的当前警告集创建快照,然后将该快照用作将来运行检查的基准,以便只报告新问题。 有了基准快照,您便可开始使用 lint 让构建失败,而不必先返回并解决所有现有问题。

基准 :当前问题集

创建Lint基准快照

修改项目的 build.gradle 文件

    android {
      lintOptions {
        baseline file("lint-baseline.xml")
      }
    }

首次添加此代码行时,系统会创建 lint-baseline.xml 文件以建立基准。此后,lint 工具仅读取该文件以确定基准。

如果要创建新基准,请手动删除该文件并再次运行 lint 以重新创建它。

然后,从 IDE(依次选择 Analyze > Inspect Code)或从命令行运行 lint,如下所示。系统会输出 lint-baseline.xml 文件的位置。

image-20200611182744778

运行 lint 会将所有当前问题记录在 lint-baseline.xml 文件中。当前问题集称为“基准”,如果要与其他人共享 lint-baseline.xml 文件,可以将其加入版本控制。

创建基准后,如果向代码库添加任何新警告,lint 将仅列出新引入的错误。

如上图所示,执行上述操作后,会把所有问题都放入 lint-baseline.xml中,在此之后出现的新问题,会单独展示出来,例如上图中的 activity_main saaa not found

查看和修改检查配置文件

Android Studio 附带了许多 lint 及其他检查配置文件,这些配置文件可通过 Android 更新进行更新。您可以原封不动地使用这些配置文件,也可以修改它们的名称、说明、严重级别和范围。您还可以激活和禁用整组的配置文件或一组配置文件中的个别配置文件。

要访问 Inspections 对话框,请执行以下操作:

  1. 依次选择 Analyze > Inspect Code

  2. Specify Scope 对话框的 Inspection Profile 下,点击 More(省略号)

    此时将显示 Inspections 对话框,其中列出了支持的检查及其说明。

    支持的检查及其说明

  3. 选择 Profile 下拉列表,以在 Default (Android Studio) 与 Project Default(活动的项目)检查之间切换。如需了解详情,请参阅以下 IntelliJ 页面:“Specify Inspection Scope”对话框

  4. 在左侧窗格的 Inspections 对话框中,选择一个顶级配置文件类别,或展开一个组并选择特定的配置文件。选择一个配置文件类别后,您可以将该类别中的所有检查项目当作一个检查项目进行修改。

  5. 选择 Manage 下拉列表,以复制检查项目、对检查项目进行重命名、向检查项目添加说明以及导出/导入检查项目。

  6. 操作完成后,点击 OK

Item Description
Whole project 选择此选项可以对整个项目进行分析。
File 选择此选项可分析当前在“项目”工具窗口中选择或在编辑器中打开的文件。
Selected files 选择此选项可分析“项目”工具窗口中当前选定的文件。
Custom scope 选择此选项以使用自定义范围。从列表中选择一个预定义的范围,或单击"(更多)省略号",然后选择分析的范围
Include test sources 选择此复选框以对test sources执行分析。
Inspection profile 选择一个配置文件以检查指定范围。从列表中选择一个配置文件。如果所需的配置文件不在列表中,请单击省略号按钮,然后在页面上配置所需的配置文件。

自定义Lint

Android Lint内置了很多lint规则,用来检测一些常见的代码问题(例如,正确性问题、安全问题、性能问题,等等)。同时,Android Lint也支持自定义lint规则,以便开发者灵活应用,更好地提升项目代码质量 。利用自定义lint规则,既可以用来在项目中检测代码质量问题,也可以用来保证编码规范的执行。

Detector

Detector 是自定义规则的核心,它的作用是扫描代码,从而获取代码中的各种信息,然后基于这些信息进行提醒和报告。

以下是可以实现的扫描器接口,选择实现哪种接口取决于你想要的扫描范围。

  • Detector.BinaryResourceScanner 针对二进制资源,例如 res/raw 等目录下的各种 Bitmap
  • Detector.JavaScanner / JavaPsiScanner / UastScanner 针对 Java 代码进行扫描
  • Detector.ClassScanner 相对于 Detector.JavaScanner,更针对于类进行扫描,可以获取类的各种信息
  • Detector.GradleScanner 针对 Gradle 进行扫描
  • Detector.ResourceFolderScanner 针对资源目录进行扫描,只会扫描目录本身
  • Detector.XmlScanner 针对 xml 文件进行扫描
  • Detector.OtherFileScanner 用于除上面6种情况外的其他文件

扫描Java源文件的Scanner先后经历了三个版本。

  1. 最开始使用的是JavaScanner,Lint通过Lombok库将Java源码解析成AST(抽象语法树),然后由JavaScanner扫描。

  2. 在Android Studio 2.2和lint-api 25.2.0版本中,Lint工具将Lombok AST替换为PSI,同时弃用JavaScanner,推荐使用JavaPsiScanner。

    PSI是JetBrains在IDEA中解析Java源码生成语法树后提供的API。相比之前的Lombok AST,可以支持Java 1.8、类型解析等。使用JavaPsiScanner实现的自定义Lint规则,可以被加载到Android Studio 2.2+版本中,在编写Android代码时实时执行。

  3. 在Android Studio 3.0和lint-api 25.4.0版本中,Lint工具将PSI替换为UAST(通用抽象语法树),同时推荐使用新的UastScanner。

    UAST是JetBrains在IDEA新版本中用于替换PSI的API。UAST更加语言无关,除了支持Java,还可以支持Kotlin。

PSI介绍

PSI(Program Structure Interface)是IDEA中用于解析代码的一套API,全称是:程序结构接口 。可将文件的内容表示为特定编程语言中的元素的层级结构。

A PSI (Program Structure Interface) file is the root of a structure representing the contents of a file as a hierarchy of elements in a particular programming language.

每种Psi元素对应一个类,均继承自com.intellij.psi.PsiElement。例如PsiMethodCallExpression表示方法调用语句,PsiNewExpression表示对象实例化语句等。

官方文档

IntelliJ Platform SDK DevGuide www.jetbrains.org/intellij/sd…

UAST

UAST全称是通用抽象语法树,UAST节点本质上是Java和Kotlin所支持的超集。

使用UAST编写规则的时候,这个规则会同时适用Java文件和Kotlin文件,无需为同一个对象编写两套规则。

JavaPsiScanner介绍

JavaPsiScanner中包含6组、12个回调方法,如下。

  1. getApplicablePsiTypes返回了需要检查的Psi元素类型列表时,类型匹配的Psi元素(PsiElement)就会被createPsiVisitor返回的JavaElementVisitor检查。
  2. getApplicableMethodNames返回方法名的列表时,名称匹配的方法调用(PsiMethodCallExpression)就会被visitMethod检查。
  3. getApplicableConstructorTypes返回类名的列表时,类名匹配的构造语句(PsiNewExpression)就会被visitConstructor检查。
  4. getApplicableReferenceNames返回引用名的列表时,名称匹配的引用语句(PsiJavaCodeReferenceElement)就会被visitReference检查。
  5. appliesToResourceRefs返回true时,Java代码中的资源引用(例如R.layout.main)就会被visitResourceReference检查。
  6. applicableSuperClasses返回父类名的列表时,父类名匹配的类声明(PsiClass)就会被checkClass检查。

此处用第二种做了示例

关键代码:

MyIssueDetector

public class MyIssueDetector extends Detector implements Detector.UastScanner {

    static final Issue ISSUE_NOT_USE_LOG_UTIL = Issue.create(
            "LOG_UTIL",  				 //id
            "should use log util",        //简介
            "this is explanation",		 //explanation 
            Category.USABILITY,
            10,							//优先级
            Severity.ERROR,
            new Implementation(MyIssueDetector.class, Scope.JAVA_FILE_SCOPE)
    );

    @Override
    public List<String> getApplicableMethodNames() {
        return Arrays.asList("v", "d", "i", "w", "e", "wtf", "Log");
    }

    /**
     * @param context lint请求的上下文
     * @param node    调用方法的节点
     * @param method  被调用的方法
     */
    @Override
    public void visitMethodCall(@NotNull JavaContext context, @NotNull UCallExpression node, @NotNull PsiMethod method) {
        super.visitMethodCall(context, node, method);
        if(context.getEvaluator().isMemberInClass(method,"android.util.Log")){
            context.report(ISSUE_NOT_USE_LOG_UTIL, context.getLocation(node), "Do not directly call android.util.Log, you should use the unified tool class");
        }
    }
}

当发现有调用v(),d()等方法的时候,我们都会收到回调visitMethodCall, 因为我们只是看了方法名,而不知道类,所以需要使用鉴别器(eveluator)进行检查,

确保它是位于android.util.Log中的,如果是的话就上报一个用例。

id : 唯一值,应该能简短描述当前问题。利用Java注解或者XML属性进行屏蔽时,使用的就是这个id。

summary : 简短的总结,通常5-6个字符,描述问题而不是修复措施。

explanation : 完整的问题解释和修复建议。

category : 问题类别。

priority : 优先级。1-10 递增。

severity : 严重级别:Fatal, Error, Warning, Informational, Ignore。

Implementation : 为Issue和Detector提供映射关系,Detector就是当前Detector。声明扫描检测的范围

Scope:用来描述Detector需要分析时需要考虑的文件集,包括:Resource文件或目录、Java文件、Class文件。

getApplicableMethodNames 返回值指定了需要被检查的方法

visitMethodCall 找到与[.getApplicableMethodNames]返回的任何名称匹配的任何方法调用而调用的方法。

context.report的参数:

第一个参数:是我们定义的Issue; 第二个参数:根据当前节点返回当前的位置信息,便于在报告中显示定位; 第三个参数:字符串用来为警告添加解释。

MyIssueRegistry

创建好的Issue要在IssueRegistry中注册

IssueRegistry 中注册了 Android Lint 自带的 Issue,而自定义的 Issue 则可以通过 getIssues 系列方法传入

public class MyIssueRegistry extends IssueRegistry {

    @NotNull
    @Override
    public List<Issue> getIssues() {
         return Arrays.asList(
                MyIssueDetector.ISSUE_NOT_USE_LOG_UTIL
              // , AttrPrefixDetector.ISSUE_XML_NAME
        );
    }
}

build.gradle

添加lint相关依赖,并生成jar包

apply plugin: 'java-library'

sourceCompatibility = "7"
targetCompatibility = "7"

configurations {
    lintChecks
}

dependencies {
    implementation fileTree(dir: 'libs', include: ['*.jar'])

    implementation "com.android.tools.lint:lint-api:27.0.0"
    implementation "com.android.tools.lint:lint-checks:27.0.0"

    lintChecks files(jar)
}

jar {
    manifest {
        attributes('Lint-Registry': 'com.example.lint_lib.MyIssueRegistry')
    }
}

其中 lint-api 是 Android Lint 的官方接口,基于这些接口可以获取源代码信息,从而进行分析。

lint-checks 是官方已有的检查规则。

Lint-Registry 表示给自定义规则注册,以及打包为 jar。

使用

1.全局使用自定义的Lint

将生成的jar包放入~android/lint文件夹中(如果没有就自己建一个) 我自己的 C:\Users\zhuoy.android\lint

之后使用命令行工具查看是否添加成功:

lint --show issue_id

image-20200612122343568

与此同时,lint --show/list 均可以查看到这条Issue

被测试的代码:

image-20200612153924046

image-20200612151926628

2.单独项目使用自定义Lint

Google 官方的方案是把 jar 文件放到 ~/.android/lint/,如果本地没有 lint 目录可以自行创建,这个使用方式较为简单,但也使得 Android Lint 作用于本地所有的项目,不大灵活。 在主项目中新建一个 Module,将jar引入module,这样各个项目可以以 module 的方式自行引入自定义 Lint,比较灵活,项目之间不会造成干扰。

新建一个Android Library, 在build.gradle中添加以下代码:

configurations {
    lintJarImport
}

dependencies {
    // 调用lintjar的lintJarOutput方法,获得jar包
    lintJarImport project(path: ':lint_lib', configuration: 'lintChecks')
}

// 调用lintJarImport得到jar包,拷贝到指定目录
task copyLintJar(type: Copy) {
    from(configurations.lintJarImport) {
        rename {
            String fileName ->
                'lint.jar'
        }
    }
    into 'build/intermediates/lint/'
}

// 当项目执行到prepareLintJar这一步时执行copyLintJar方法(注意:这个时机需要根据项目具体情况改变)
project.afterEvaluate {
    def compileLintTask = project.tasks.find { it.name == 'prepareLintJar' }
    compileLintTask.dependsOn(copyLintJar)
}

这里,创建了一个叫“lintJarImport”的Gradle configuration,其引用了模块 “:lint_lib”的Gradle configuration “lintChecks”。

同时,对内置的Gradle task “compileLint”做了修改,让其依赖于我们定义的一个task “copyLintJar”。在task “copyLintJar”中,把模块 “:lint_lib”输出的jar包拷贝到了build/intermediates/lint/lint.jar。

然后执行 gradlew build 看结果

image-20200612170127649

其他项目只要引入这个lintlibrary即可使用其中定义的Lint规则。

在代码中也可以看到高亮提示。

智能纠错

有时IDE给出提示的同时,会有一个ALT+ENTER的快捷键来让我们直接使用它的建议修改代码,方便快捷,我们也可以如此:

下图是我修改了一下检查的内容:发现使用Log.wtf()的时候,给出提示,并且给一个建议使用Log.e来代替Log.wtf

wtf不是what the fuck,而是 What a terrible failure

image-20200612181637845

实现方式:

 private void reportUsage(@NotNull JavaContext context, @NotNull UCallExpression node, @NotNull PsiMethod method) {
        LintFix lintFix = LintFix.create()
                .name("Use Log.(e)")
                .replace()
                .text(method.getName())
                .with("e")
                .robot(true)
                .independent(true)
                .build();
        context.report(ISSUE_NOT_USE_LOG_UTIL,
                context.getLocation(node),
                " reportUsage Do not directly call android.util.Log, you should use the unified tool class",
                lintFix);
    }

相较于之前的上报信息,我们在调用report方法时,后面加了一个lintFix的参数。

LintFix是Lint的一个快速修复的方式。

independent :此修补程序是否独立于要应用的其他修补程序。

robot:当在 fix-mode下运行lint时,可以自动应用这些类型的修复程序,在该模式下,它将应用所有建议的(合格)修复程序。