通關Android Lint

Android Lint 是Android Studio 在ADT(Android Developer Tools)16提供的代碼掃描工具,可以幫助我們發現和更正代碼結構質量的問題。系統會報告該工具檢測到的每個問題並提供問題的描述消息和嚴重級別,以便快速確定需要優先進行的修改。此外,我們還可以通過降低問題的嚴重級別以忽略與項目無關的問題,或者提高嚴重級別以突出特定問題。

優點

  • 無需實際執行應用

  • 不必編寫測試用例

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 檢查。

在左側窗格樹狀視圖中,通過展開並選擇錯誤類別、類型和問題來查看檢查結果。

右側窗格顯示選定錯誤類別、類型或問題的檢查報告,並提供錯誤的名稱和位置。在適用情況下,檢查報告會顯示問題概要等其他信息,以幫助您更正問題。

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. 檢查項目中是否存在某個問題

該功能允許我們輸入一個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) 範圍

在 Android Studio 中配置 Lint

在項目根目錄創建一個lint.xml文件

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>

配置對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:

add this:

after:

比如這個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 文件的位置。

運行 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

ItemDescription
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 DevGuidewww.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

與此同時,lint --show/list 均可以查看到這條Issue

被測試的代碼:

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  看結果

其他項目只要引入這個lintlibrary即可使用其中定義的Lint規則。

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

智能糾錯

有時IDE給出提示的同時,會有一個ALT+ENTER的快捷鍵來讓我們直接使用它的建議修改代碼,方便快捷,我們也可以如此:

下圖是我修改了一下檢查的內容:發現使用Log.wtf()的時候,給出提示,並且給一個建議使用Log.e來代替Log.wtf

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

實現方式:

 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時,可以自動應用這些類型的修復程序,在該模式下,它將應用所有建議的(合格)修復程序。


作者:__white
鏈接:https://juejin.im/post/5ee828b7f265da77031b7c55

關注我獲取更多知識或者投稿

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章