1.布局优化
布局优化的思想很简单,就是尽量减少布局文件的层级,这个道理是很浅显的,布局中的层级少了,意味着Android绘制时的工作量少了,那么程序的性能自然就高了。
如何进行布局优化?
首先删除布局中无用的控件和层级
其次有选择地使用性能较低的ViewGroup,比如RelativeLayout。如果布局中既可以使用LinearLayout也可以使用RelativeLayout,那么就采用LinearLayout,这是因为RelativeLayout的功能比较复杂,它的布局过程需要花费更多的cpu时间。如果需要通过嵌套的方法来完成布局还是建议采用RelativeLayout。
另一种手段是采用标签、标签和ViewStub(按需加载)。
2.绘制优化
绘制优化是指View的onDraw方法要避免执行大量的操作,主要体现在两个方面:
首先onDraw中不要创建新的局部对象,这是因为onDraw方法可能会被频繁调用,这样就会在一瞬间产生大量的临时对象,这不仅占用了过多的内存而且还会导致系统更加频繁gc,降低了程序的执行效率。
另一方面,onDraw方法中不要做耗时的任务,也不能执行成千上万次的循环操作,尽管每次循环都很轻量级,但是大量的循环仍然十分抢占CPU的时间片,这会造成View的绘制过程不流畅。
3.内存泄露优化
首先解释一下内存泄露和内存溢出的区别:
内存泄露是指程序在申请内存后,无法释放已申请的内存空间,一次内存泄露似乎不会有很大的影响,但内存泄露堆积后的后果就是内存溢出。
内存溢出指程序申请内存时,没有足够的内存供申请者使用,或者说给了你一块存储int类型数据的存储空间,但是你却存储long类型的数据,那么结果就是内存不够用,此时就会报错OOM,即所谓的内存溢出。
①静态变量导致的内存泄露
②单例模式导致的内存泄露
③属性动画导致的内存泄漏
5.线程优化
线程优化的思想是采用线程池,避免程序中存在大量的Thread。线程池可以重用内部的线程,从而避免了线程的创建和销毁所带来的性能开销,同时线程池还能有效的控制线程池的最大并发数,避免大量的线程因互相抢占系统资源从而导致阻塞现象的发生。
6.Bitmap优化
采用BitmaoFactory.Options来加载所需尺寸的图片。
这里假设通过ImageView来显示图片,很多时候ImageView并没有图片的原始尺寸那么大,这个时候把整个图片加载进来后再设给ImageView,这显然是没必要的,因为ImageView并没有办法显示原始的图片。通过BitmapFactory.Options就可以按一定的采样率来加载缩小后的图片,将缩小后的图片再ImageView中显示,这样就会降低内存占用从而在一定程度上避免OOM,提高了Bitmap加载时的性能。BitmaoFactory提供的加载图片的四类方法(decodeFile、decodeResource、decodeStream、decodeByteArray)都支持BitmaoFactory.Options参数,通过它们就可以很方便的对一个图片进行采样缩放。
通过BitmaoFactory.Options来缩放图片,主要是用到了它的inSampleSize参数,即采样率。采样率需要根据控件的大小和图片的原始大小来设置,否则就会导致图片失真。
如何获取采样率?
①设置不加载到内存,将BitmaoFactory.Options的inJustDecodeBounds参数设为true
②从BitmaoFactory.Options中取出图片的原始宽高信息,对应outWidth和outHeight参数
③根据采样率的规则并结合目标View的所需大小计算出采样率inSampleSize
④将BitmaoFactory.Options的inJustDecodeBounds参数设为false,然后重新加载图片
val options = BitmapFactory.Options()
//不加载到内存
options.inJustDecodeBounds = true
//对bufferedInputStream进行重复利用
bufferedInputStream.mark(0)
BitmapFactory.decodeStream(bufferedInputStream, null, options)
bufferedInputStream.reset()
//计算inSampleSize值
options.inSampleSize = getBitmapSampleSize(options)
//设置为false,图片加载到内存种
options.inJustDecodeBounds = false
bitmap = BitmapFactory.decodeStream(bufferedInputStream, null, options)
private fun getBitmapSampleSize(options: BitmapFactory.Options): Int {
// 目标图片的高度和宽度
val widthPix = CommonUtils.dip2px(105F)
val heightPix = CommonUtils.dip2px(105F)
// 源图片的高度和宽度
val outWidth = options.outWidth
val outHeight = options.outHeight
Log.i(TAG, "getBitmapSampleSize: $widthPix $heightPix $outWidth $outHeight")
var sampleSize = 1
if (outHeight > heightPix || outWidth > widthPix) {
// 计算出实际宽高和目标宽高的比率
val btHeight = outHeight / heightPix
val btWidth = outWidth / widthPix
// 选择宽和高中最小的比率作为inSampleSize的值,这样可以保证最终图片的宽和高
// 一定都会大于等于目标的宽和高。
sampleSize = if (btHeight > btWidth) btHeight else btWidth
}
Log.i(TAG, "getBitmapSampleSize: $sampleSize")
return sampleSize
}
经过上面4个步骤,加载出的图片就是最终缩放后的图片,当然也有可能不需要缩放。
7.其他一些性能优化建议
避免创建过多的对象
不要过多使用枚举,枚举占用的内存空间要比整型大
常量请使用const val来定义
使用一些Android特有的数据结构,比如SparseArray和Pair等,它们都具有更好的性能
适当使用软引用和弱引用
采用内存缓存和磁盘缓存
尽量采用静态内部类,这样可以避免潜在的由于内部类导致的内存泄露
内存泄露工具LeakCanary使用教程:
https://juejin.cn/post/7134728428003000356#heading-16