主要内容
对Android Studio支持的六类Android Lint规则, 本文主要对Performance包含的32个项的说明,主要内容都是文档翻译,适当加一些自己的感想。
分类详细说明
高效使用资源
UnusedIds
未被使用的资源id,在layout文件中定义了资源ID从未被使用过,但有时候它们可以让layout更容易阅读,没有必要删除未使用的资源id。
Overdraw
过度绘制:一个绘制区域被绘制的次数多于一次。
如果给一个root view设置了背景图,就要给它设置一个background=null
的theme
,否则绘制过程会先绘制theme
的background
,然后再绘制设置的背景图,完全覆盖之前绘制的theme.background
,这是 过度绘制。
这个检测器依赖于根据扫描Java代码找出哪些布局文件与哪些Activity相关联,目前它使用的是一种不精确的模式匹配算法。因此,可能会因错误地推断布局与活动的关联而给出错误的提醒。
如果想把一个背景图应用在多个页面上,可以考虑自定义theme
,并把背景图设置在theme
里,在layout
中设置theme
代替设置background
。如果背景图中有透明的部分,并且希望他和theme
的背景有层叠效果,那么可以选择先把两个背景合并成一个背景图之后,在定义到theme
里。
VectorPath
关于SVG的使用,给出一篇参考文章:Android vector标签 PathData 画图超详解,Android Studio可以创建使用SVG绘制出的drawable图像资源。
UselessLeaf
没有包含任何View,也没有设置背景的Layout是多余的,可以去掉。让界面更趋于扁平,嵌套更高效。
UselessParent
如果一个包含
View
的Layout
没有兄弟层级的Layout
,而他的外部ViewGroup
又不是ScrollView
或者root级别,那么这个Layout
可以移除,让他包含的View
直接包含在它的父层级的Layout
中。让界面更趋于扁平,嵌套更高效。
TooDeepLayout
Layout
嵌套过深会影响性能,考虑使用平铺类型的Layout代替。默认最深的View嵌套是10层,也可以通过环境变量ANDROID_LINT_MAX_DEPTH
进行设置。System.getenv("ANDROID_LINT_MAX_DEPTH");
语句获取,如何设置还没找到。
TooManyViews
Layout
内有太多的View
:一个Layout
文件内有过多的View
会影响性能。考虑使用复合drawables或其他技巧来减少这个布局中的视图数量。默认最多的数量是80个,可以通过环境变量ANDROID_LINT_MAX_VIEW_COUNT
进行设置。据说这个变量可以用System.getenv("ANDROID_LINT_MAX_DEPTH");
语句获取,如何设置还没找到。
NestedWeights
Weight嵌套:使用非
0
的layout-weight
值,需要Layout被测量两次,如果一个包含非0
值的LinearLayout
被嵌套在另一个包含非0
值的LinearLayout
内部,那么,测量次数就会呈指数级增长。
DuplicateDivider
这个主要是讲RecyclerView的分割线,
com.android.support:recyclerview-v7
提供了一个类DividerItemDecoration
设置分割线样式,这个类在早期的版本内没有包含,所以在更新为新的版本后,可以使用这个类重新设置分割线。 具体使用,参考文章:Android RecyclerView 使用完全解析
MergeRootFrame
当
FrameLayout
在一个layout文件中是root且没有使用background
或者padding
等属性,通常使用一个merge
标签代替FrameLayout
会更高效。但是这要看上下文设置,所以在替换之前要确认你已经理解了merge
标签的工作原理。
UnusedResources
未使用的资源:多指的是drawable类型的资源。多余的drawable资源会让APP变大,编译过程变长。
InefficientWeight
当LinearLayout只有一个Widget且使用了
android:layout_weight
时,定义对应的width/height
的值为0dp
,Widget就会自动占满剩余空间。因为不需要预先计算自己的尺寸,这种方式更高效。
高效的设置
DisableBaselineAlignment
在使用LinearLayout实现空间的按比例分割时,LinearLayout的空间用layout_weight属性在所包含的几个layout中间分割,那么应该设置被分割LinearLayout
baseLineAligned="false"
,这样可以加快分割空间所做的运算。
LogConditional
LogConditional:使用
android.util.Log
打印调试日志,一般只会在DEBUG
模式下使用,在release
是不需要打印调试日志的,在buildToolsVersion
大于等于17时,BuildConfig
提供两个一个DEBUG
常量来标记是否处于DEBUG模式,我们可以用if(BuildConfig.DEBUG){}
包裹调试日志语句,这样编译器会在编译生成release包时,删除这些语句。如果真的需要在release模式下打印调试日志,可以使用@SuppressLint("LogConditional")
注解告诉编译器在release包中保留这些日志信息。
UnpackedNativeCode
APP使用
System.loadLibrary()
加载Native库时,android 6.0或者更新的版本可以在Manifest文件中application
标签中添加属性android:extractNativeLibs="false"
,这样可以提交加载速度,降低APP占用的存储空间。
更高效的替代方案
FloatMath
不要使用FloatMath类进行数学计算,推荐使用Math类。
Android早期版本因为浮点运算性能的原因,推荐使用FloatMath代替Math类进行数学计算。随着硬件和系统的发展,这个问题已经不复存在,甚至经过JIT优化之后的Math类运算速度会比FloatMath更快,所以,在Android F以上版本的系统上,可以直接使用Math类,而不是FloatMath。
UseValueOf
某些类构造新对象时,建议使用工厂方法,而不是
new
关键字声明新的对象。例如,new Intger(0)
就可以使用Integer.valueOf(0)
替代,工厂方法会使用更少的内存,因为它会让值相等的对象使用同一个实例。
ViewHolder
在给
ListView
或GradView
之类的列表实现Adapter
时,不能每次getView
调用都去inflate
一个新的layout
,如果接口参数中给出了一个可以复用的View对象,就可以使用这个对象而不是重新生成。这个应该都很熟悉,也很简单基础了。
UseSparseArrays
Key
为Integer
类型的HashMap
可以使用SparseArray
代替,性能更好。可以使用替代HashMap
的有SparseBooleanArray、SparseIntArray、SparseLongArray
和泛型类SparseArray
,每个对应的类型代表Value的类型。如果在某些情况一定要用HashMap实现,则可以用@SuppressLint
注解抑制Lint检查。
WakelockTimeout
关于
week lock
的使用,这里提供一篇博客文章:Android 功耗分析之wakelock
UseCompoundDrawables
在一个
TextView
的四周有只具有展示作用的ImageView
时,建议删除ImageView
改用compound drawables:drawableTop, drawableLeft, drawableRight,drawableBottom,drawablePadding
替代方案实现。
有关泄漏的提醒
Recycle
缺少recycle()调用:许多资源例如:
TypedArrays, VelocityTrackers
在使用完之后需要调用recycle()
方法回收资源。
ViewTag
4.0版本系统之前,
View.setTag(int, Object)
的实现方式中,会把Object
存储在一个静态的map
里并且使用的是强引用。这就意味着如果这个Object
包含了对Context
对象的引用,这个Context
就是泄漏了。
传递一个
View
做参数,这个View
就能提供一个对创建它的Context
的引用。类似的,View holders
内包含View
,也会有Context
与这个View
相关联。
HandlerLeak
Handler引用泄漏:声明Handler的子类如
MyHandler
为内部类,如果MyHandler
类对象关联Looper.getMainLooper()
或者Looper.getMainLooper().getQueue()
时,会阻止无用的外部类对象被垃圾回收,导致泄漏。如果对应main thread
的关联,就不会有这个问题。
应对方法,声明
MyHandler
为静态内部类,并用WeakReference
的方式持有一个外部类对象,MyHandler
使用这个对象操作外部类的属性和方法。
DrawAllocation
绘制过程中的内存分配:避免在布局绘制过程中分配内存给新的对象。因为这些操作调用频率比较高,频繁分配内存会唤起垃圾回收,中断UI绘制,导致卡顿。
StaticFieldLeak
非静态内部类具有对其外部类对象的隐式引用。
如果外部类Fragment
或者Activity
,那么这个引用意味着长时间运行的处理程序/加载器/任务(handler/loader/task)将持外部类对象的引用,从而防止外部类对象被回收。
同理,长时间运行的处理程序/加载器/任务(handler/loader/task)对Fragment
或者Activity
的直接引用,也会造成泄漏。
ViewModel类应该禁止引用View或者non-application
类型的Context
对象。
代码提醒
AnimatorKeep
属性动画默认支持的属性如下面列表。如果超出这些范围,会通过反射调用本地定义的函数。声明一个属性动画对象例如:
ObjectAnimator.ofFloat(view, "rotation", 0, 360)
中的“rotation”就是要操作的属性,如果属性不在下面的列表中例如ObjectAnimator.ofFloat(view, "position", 0, 360)
,就需要本地定义一个对应的方法setPosition(float position)
,并且这个方法需要加上@keep
注解,防止被当做无用方法清理掉。
static {
PROXY_PROPERTIES.put("alpha", PreHoneycombCompat.ALPHA);
PROXY_PROPERTIES.put("pivotX", PreHoneycombCompat.PIVOT_X);
PROXY_PROPERTIES.put("pivotY", PreHoneycombCompat.PIVOT_Y);
PROXY_PROPERTIES.put("translationX", PreHoneycombCompat.TRANSLATION_X);
PROXY_PROPERTIES.put("translationY", PreHoneycombCompat.TRANSLATION_Y);
PROXY_PROPERTIES.put("rotation", PreHoneycombCompat.ROTATION);
PROXY_PROPERTIES.put("rotationX", PreHoneycombCompat.ROTATION_X);
PROXY_PROPERTIES.put("rotationY", PreHoneycombCompat.ROTATION_Y);
PROXY_PROPERTIES.put("scaleX", PreHoneycombCompat.SCALE_X);
PROXY_PROPERTIES.put("scaleY", PreHoneycombCompat.SCALE_Y);
PROXY_PROPERTIES.put("scrollX", PreHoneycombCompat.SCROLL_X);
PROXY_PROPERTIES.put("scrollY", PreHoneycombCompat.SCROLL_Y);
PROXY_PROPERTIES.put("x", PreHoneycombCompat.X);
PROXY_PROPERTIES.put("y", PreHoneycombCompat.Y);
}
ObsoleteSdkInt
无用的SDK版本检查:Android SDK的版本更新比较快,许多API的使用都需要通过检查SDK版本防止出现not found之类的崩溃。在APP迭代的过程中提升了
minSdkVersion
的值就会导致部分SDK版本检查不再需要。
这种SDK版本检查会引起不必要的资源搜索。
DevModeObsolete
以前,文档中建议在productFlavors中创建一个dev product。设定
minSdkVersion 21
,在开发过程中激活multidexing加速构建过程。现在已经不需要这么做了,在新版的IDE和Gradle插件中,会自动地识别所连接设备的API level,如果链接的设备API level大于等于21,就会自动打开multindexing,就跟之前设置了dev product的效果一样。
ObsoleteLayoutParam
无用的LayoutParam:当给Widget使用了所在Layout没有提供的LayouParam时,会有这个提示。这种情况一般出现在修改Layout类型时没有同时修改内部Widget的LayoutParam设置或者把一个Widget从一个Layout拷贝到另一个不同类型的Layout内部。
这种无用的LayoutParam在运行时会引起无效的属性解析,也会误导阅读这些代码的人。所以应该把这些无用的属性删除掉。