Anroid App内存优化
背景
在移动开发过程中,由于app内存和存储空间有限,而系统分配给每个app的内存是有一定限制的,当app申请的内存超过限制后,就有可能出现内存溢出而导致app崩溃,影响用户体验甚至导致用户卸载app,因此性能优化是非常值得投入的一个事情。一般来说,所有的优化都可以分为以下两种情况:
* 以空间换时间(数据预加载)
* 以时间换时间(1.串行改并行 2.懒加载:先初始化优先级高的,优先级低的延迟加载)
定义
优化处理 应用程序的内存使用、空间占
作用
避免因内存使用/管理不当,导致内存泄漏、 内存溢出、内存占用过大,导致app卡顿甚至崩溃
常见的内存问题 & 解决方案
- 常见的内存问题如下:
内存泄露
内存抖动
图片Bitmap相关
代码质量 & 数量
日常不正确使用
内存泄漏
- 定义:在程序申请内存后,当该内存不再需要时,无法释放 & 归还给程序
- 影响:当泄漏的内存达到一定程度,新申请的内存大于系统可分配内存时,则会造出内存溢出(OOM)
- 发生内存泄漏的本质原因:生命周期长的对象持有生命周期短的对象
- 常见内存泄漏
集合类
Static关键字修饰的成员变量
非静态内部类 / 匿名类
资源对象使用后未关闭
图片资源Bitmap相关
- 为什么需要优化bitmap:Android分配给每个应用的内存有限,而图片非常占用内存,很多情况下,图片所占内存占整个应用内存的大部分
- 优化方向:
- 使用完毕后释放资源:Bitmap.recycle, SoftWeafrence
- 根据分辨率适配&缩放图片: 设置多套图片资源、BitmapFactory.decodeResouce、BitmapOption.inSampleSize
- 按需选择合适的解码方式
- 设置图片缓存: 1. 3级缓存 2.SoftReference
内存抖动
简介
- 定义: 内存大小频繁上下较大范围波动
- 原因:短时间内程序频繁创建对象 & 垃圾回收
- 后果:频繁gc导致卡顿,甚至oom
优化方案
尽量避免频繁创建大量、临时的小对象
常用性能优化tips
- 线程池
优先采用ThreadPollExecutor, 不要用new Thread(), 好处是
1.线程可重复利用,节省线程的创建和回收开销
2.控制线程的并发数,减少并发问题
3.控制线程状态
- UI
- 合理利用include、merge、viewstube,使用ConstraintLayout, 减少布局层级,提高页面渲染效率
- 窗口默认有一个不透明的背景,可以去掉的: getWindow().setBackground(null),或者修改xml
- ui局部刷新:用adatper.notifyItemChanged 替换adatper.notifyDataChanged()
- RecycleView替代ListView
- Bitmap
1.使用BitmapFactory.options 对图片压缩读取, inSampleSize:缩放比例,把图片载入内存之前,先计算合适的缩放比例。
- 对像素要求不高的情况下,Config由ARGB_8888改为 ARGB_565。
- 及时将bitmap置为null。
- 显示本地图片时,用tinypng压缩之后再使用
- 许多地方不需要存内存缓存,比如闪屏广告图,app启动之后就不会再使用了,可以加载的时候 memoryCache(false)
- 许多地方不需要磁盘缓存,比如发布动态,从图库中选图,不需要再存一份磁盘缓存了,本身那些图片都是本地图片。直接 diskCache(false)
-
广播
优先使用LocalBroadcastManger, 好处是:安全性,性能,运行效率更高 -
在onActivity的onDestroy方法中,对资源和引用进行清除(例如imageview.setImageDrawable(null), editText.clearTextWatchers()),取消网络请求。
-
耗时操作尽量使用Intentservice而不是service
-
用SparseArray 替换key为int的HashMap 好处:SpaceArray更节省内存(SparseIntArray、SparseBooleanArray、SparseLongArray,可以支持存储<Integer,Integer>、<Integer,Boolean>、<Integer,Long>),HashMap对于key为int类型时,每次put值的时候会将int包装成Integer,涉及装箱
-
ObjectPool
通过对象池技术达到重复利用,减少重复对象的创建
-
Job Schedule
将不紧急的任务交给jobSchedule来处理,选择合适的时间,合适的网络,再一起进行
-
Anroid避免使用enum, 建议用annotation(参考View.setVisibility)
-
使用StringBuffer或者StringBuilder,避免大量字符串的拼接
-
onLowMemory、onTrimMemory 释放资源
-
使用Parceable代替Seriable传递序列化数据
-
在性能敏感的代码中避免创建对象, 例如:onMeasure、onLayout、ondraw
-
WebView优化
- WebView第一次创建比较耗时,可以预先创建WebView,提前将其内核初始化(webview首次初始化需200ms,第二次只需要20ms)
- 使用WebView缓存池,用到WebView的地方都从缓存池取,缓存池中没有缓存再创建,注意内存泄漏问题
- 本地预置html和css,WebView创建的时候先预加载本地html,之后通过js脚本填充内容部分
- 提前从cdn中请求部分落地html。缓存到本地,点击详情时,只需从缓存中加载即可
- WebView初始化完成,立刻loadUrl,无需等待框架onCreate或者OnResume结束
- WebView初始完成后到页面首屏绘制完成之间,尽量减少UI线程的其他操作,繁忙的UI线程会拖慢WebView.loadUrl的速度
-
启一个后台线程,定时去监控实时的内存使用情况,如果内存紧急了,直接清空Glide或者fresco的内存缓存
启动优化
- 通过AsyncLayoutInflater异步加载布局
- 闪屏页优化
- MultipDex优化(1.新启动一个进程去执行MultiDex优化,创建一个临时文件,作为判断MultiDex是否加载完的条件 2.启动LoadDexActivity去加载MultiDex(LoadDexActivity在单独进程,加载完会删除临时文件) 3.开启while循环,直到临时文件不存在跳出循环,进入Application的onCreate方法 )
- 在Application的onCreate方法中预创建预创建SplashActivity和MainActivity
- 数据预加载(对象第一次创建的时候,java虚拟机首先检查类对应的Class
对象是否已经加载。如果没有加载,jvm会根据类名查找.class文件,将其Class对象载入。同一个类第二次new的时候就不需要加载类对象,而是直接实例化,创建时间就缩短了) - 第三方库懒加载(延迟 或者MessageQueue.addIdleHandler)
- 线程优化
- 系统调用优化
避免内存泄漏
- 优先使用Application 的Context而不是Activity Context
- 内部类引用导致activity泄漏,尤其是Handler(采用静态内部类+弱引用解决)
- Handler泄漏(弱饮用,onDestory中removeCallbacksAndMessages(null), 使用LifeCycleHandler)
- 定时轮询任务
- Cursor,Sqlite,File,IO等资源使用完记得关闭
- 动画销毁的时候,停止动画
- 各种监听器(例如广播接收者 ,EditText的addTextChangeListener)及时注销,静态集合及时清空
定位性能问题
- LeakCancary(继承displayLeakService,上报埋点)
- BlockCancary 监听主线程耗时方法
- 通过Aspect J等方式插桩(监控setContentView耗时,主线程耗时)
- Systrace 和 函数插桩
- TraceView
- 替换looper的Printer(涉及到字符串拼接,推荐Debug模式下使用)
- Profile
- Mat