Android性能优化

1,076 阅读6分钟

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源码设计模式解析与实战》