通關Android Lint

Lintphp

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

優勢

  • 無需實際執行應用
  • 沒必要編寫測試用例

Lint工做流

下圖顯示了 lint 工具如何處理應用源文件。java

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

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

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

allowBackup屬性肯定是否能夠備份應用程序的數據並恢復。編程

在SDK版本23及更高版本中,您的應用程序數據將在應用程序安裝時自動備份和還原。考慮添加屬性「 android:fullBackupContent」以指定「 @xml」資源,該資源配置要備份的文件。這個問題屬於Security。api

  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.八、類型解析等。使用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時,能夠自動應用這些類型的修復程序,在該模式下,它將應用全部建議的(合格)修復程序。

相關文章
相關標籤/搜索