Bitmap高效加载
- 采用BitmapFactory.Options来加载所需要的的尺寸
- 采样缩放
- 设置采样率
- imSampleSize 乘方递减缩放 例子200乘以200 设置为2 结果100乘以100
获取采样率
- 将BitmapFactory.Options的inJustDecodeBounds的参数设置为true并加载图片
- 从BitmapFactory.Options中取出图片的原始宽高信息,他们对应outWidth和outHeight
- 根据采样率规则并结合目标View的大小计算出采样率inSampleSize
- 将BitmapFactory.Options的inJustDecodeBounds参数设置为false,重新加载图片即可
Android的缓存策略
删除旧缓存添加新缓存,那么如何定义缓存的新旧,其实就是一种策略
不同的缓存策略,对应不同的缓存算法
- 根据文件的的最后修改时间,来定义文件的新旧
常用的缓存算法:LRU (Least Recently Used) 近期最少使用算法
- 核心思想:当缓存存满的时候,优先淘汰掉近期最少使用的缓存对象
- 两种:LruCache、DiskLruCache
- LruCache内存存储缓存、DiskLruCache存储设备缓存,两者完美结合,很方便的实现一个具有很高价值的ImageLoader
LruCache
- Android3.1提供
- 泛型类
- 内部采用一个LinkedHashMap以强引用的方式存储外界的缓存对象
- 存储满时,移除较早的缓存对象,然后再添加新的缓存对象
- 强引用:直接的对象引用
- 软引用:当一个对象只有软引用的存在,系统内存不足会被GC回收
- 弱引用:当一个对象只有弱引用的存在,此对象会被GC回收
- 线程安全
LruCache的缓存实现过程
- 提供缓存的总容量大小 = 重写sizeOf方法即可
package com.example.androidperformance
import android.graphics.Bitmap
import android.util.LruCache
/**
* 缓存单例类
*/
object CacheUtils {
fun getMemoryCache() : LruCache<String, Bitmap> {
val maxMemory = (Runtime.getRuntime().maxMemory() / 1024).toInt()
val cacheSize = maxMemory / 8
return object : LruCache<String, Bitmap>(cacheSize) {
override fun sizeOf(key: String, bitmap: Bitmap): Int {
return bitmap.rowBytes * bitmap.height / 1024
}
}
}
fun putBitmapCache(key: String, bitmap: Bitmap) {
getMemoryCache().put(key, bitmap)
}
fun getBitmapWithKey(key: String) {
getMemoryCache().get(key)
}
fun deleteBitmapWithKey(key: String) {
getMemoryCache().remove(key)
}
}
DiskLruCache 磁盘缓存
创建 open方法 open(directory: File, appVersion: int, valueCount: int, maxSize: long)
- 第一个参数:SD卡的缓存目录 /sdcard/Android/data/package_name/cache 应用被卸载,目录被删除 建议:如果希望卸载后数据删除,选择data下当前应用目录,反之,选择其他目录
- 第二个参数:应用版本号:一般设置为1
- 第三个参数:单节点对应的数据的个数
- 第四个参数:缓存总大小:例如50MB,超过50MB,自动删除,保证不大于这个值
DiskLruCache缓存添加
- 通过Editer编辑对象添加
- url的md5值作为key添加
- editor.commit()
DiskLruCache缓存查找
- url转换为key
- 通过DiskLruCache的get方法得到Snapshot对象
- 通过snapshot对象得到缓存文件的输入输出流,有了文件的输入流,自然就可以得到了
ImageLoader
- 图片的同步加载
- 图片的异步加载
- 图片压缩 设计一个ImageResizer类即可
- 内存缓存
- 磁盘缓存 LruCache DiskLruCache
- 网络拉取 采用线程池来加载图片 原因:普通线程随着列表滑动,创建太多,不利于整体效率提升 AsyncTask不支持3.0以下的版本 所以采用Runnable+线程池的方式来解决这个问题
ImageLoader处理多线程图片下载的ImageView的重用错位问题
- 通过给ImageView设置TAG值,解决这个问题
- 拿到图片的uri比对ImageView的uri即可
优雅解决列表的卡顿现象
- 不要再getView中执行耗时操作
- 控制异步任务的执行频率,比如用户频繁上下滑动,造成的一瞬间产生几百个异步任务
- 造成了线程池的拥堵,并且频繁更新UI,这是没有意义的
- 而且UI更新是在主线程中,会造成一定程度的卡顿
- 如何解决?
- 考虑在列表滚动的时候,停止异步加载
- 尽管过程是异步的,但是等列表停止滚动,依然可以获得良好的用户体验
- 在onScrollStateChanged方法中,设置滚动状态,停止的时候调用Adapter.notifyDataSetChanged
- 然后再getView中,判断当列表静止的时候才能加载图片即可
- 开启硬件加速
内存泄漏优化
- 借助内存泄漏分析工具MAT
- 代码的可维护性、可扩展性
- 良好的代码风格
- 清晰地代码层级
- 代码的可扩展性、合理的设计模式
- 静态变量导致的内存泄漏优化:静态变量引用了有生命周期的对象(如Activity)
- 单例模式导致内存泄漏:缺少解除注册导致无法被回收 在onDestory解除注册
- 属性动画导致的内存泄漏:无限循环动画在Activity中循环播放并且没有在onDestory中停止动画,那么就会导致Activity销毁的时候动画还在播放,可以使用animator.cancel()停止动画即可
布局优化
- 减少布局文件的层级
- 删除无用控件、层级
- <include> 标签
- ViewSub按需加载
绘制优化
- onDraw不要重复创建局部对象
- onDraw内部不要做耗时任务
- onDraw不能执行成千上万的循环操作
响应速度优化
- 核心:避免在主线程中做耗时操作
- 把耗时操作放在子线程中去做即可
- 如果主线程中做的事情太多,就会导致Activity出现黑屏、甚至ANR
- Android规定:如果Activity5秒钟之内没有响应屏幕触摸事件,或者键盘输入事件就会出现ANR
ANR日志分析
- 当ANR问题出现,系统会在data/anr目录下面创建一个文件叫做traces.txt,我们通过这个文件就能定位出来ANR出现的原因
- 例如在主线程中休眠30S,中间重复点击屏幕,就会出现ANR,这个时候去分析文件即可
- 子线程和主线程抢占同步锁的情况,也会导致ANR 两个耗时方法同时等待一个Activiy的锁,就造成了你等我,我等你的问题
RecyclerView和Bitmap的优化
- 避免getView中执行耗时操作
- 根据列表滑动状态,控制任务的执行频率
线程优化
- 采用线程池
- 避免程序中使用大量的Thread
- 因为线程池可以重用,有效控制最大并发数,避免大量的线程抢占系统资源从而导致阻塞现象的发生
其他优化
- 避免重复创建过多的对象
- 不要过多地使用枚举,因为枚举占用的内存空间要比整形大
- 常量需要使用static final来修饰
- 使用Android特有的数据结构,如:SparseArray、Pair
- 适当使用弱引用、软件用
- 采用内存缓存+磁盘缓存
- 尽量采用静态内部类,这样可以避免潜在的由于内部类而导致的内存泄漏
内存泄漏工具Profiler
提高程序的可维护性
代码风格
- 私有成员变量以m开头、静态成员以s开头,常量都用大写
- 合理留白
- 仅为关键代码添加注释
- 代码的层次性
- 单一职责
- 面向扩展编程
- 设计模式
推荐书籍
《大话设计模式》
《Android源码设计模式解析与实战》