|
|
@ -42,7 +42,17 @@ Apk 包主要分为几个部分,libs so 库、dex、res、assets、resources.a |
|
|
|
|
|
|
|
|
|
|
|
内存泄露我主要的排查思路是使用 LeakCanary + Android Profiler。LeakCanary 平时是一直开着的,在任务的收尾阶段我会跑一下 Android Profiler。打开关闭页面来来回回五六次,看一下 Total Memory 有没有明显升高,然后看一下对应的实例对象有没有被销毁。 |
|
|
|
内存泄露我主要的排查思路是使用 LeakCanary + Android Profiler。LeakCanary 平时是一直开着的,在任务的收尾阶段我会跑一下 Android Profiler。打开关闭页面来来回回五六次,看一下 Total Memory 有没有明显升高,然后看一下对应的实例对象有没有被销毁。 |
|
|
|
|
|
|
|
|
|
|
|
在做积分商城时,有一个编辑地址页面,里面有三个 EditText 并设置了 TextChangedListener,在跑 Profiler 时发现页面已经退出了但是还存在三个该 Activity 的应用,并且都定位到了 TextWatcher 那一行,然后我试了强制 gc 在退回桌面还是存在,说明是内存泄露了。但是大家一直都这么写,并没有在 onDestory 时去移除 Listener 呀。我的测试机是三星 7,然后我试了一下我的小米 9,发现就没问题了。初步怀疑是系统 bug,然后我用 Google 的模拟器,实际测试一下 Android 8 以上没有问题,以下就存在内存泄露。但是项目中很多这样的,解决办法就是可以在 BaseActivity 的 onDestory 去遍历 View 树清空 Listener。 |
|
|
|
在做积分商城时,有一个编辑地址页面,里面有三个 EditText 并设置了 TextChangedListener,在跑 Profiler 时发现页面已经退出了但是还存在三个该 Activity 的应用,并且都定位到了 TextWatcher 那一行,然后我试了强制 gc 再退回桌面还是存在,说明是内存泄露了。但是大家一直都这么写,并没有在 onDestory 时去移除 Listener 呀。我的测试机是三星 7,然后我试了一下我的小米 9,发现就没问题了。初步怀疑是系统 bug,然后我用 Google 的模拟器,实际测试一下 Android 8 以上没有问题,以下就存在内存泄露。但是项目中很多这样的,解决办法就是可以在 BaseActivity 的 onDestory 去遍历 View 树清空 Listener。 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
我在 review 代码时还遇到同事写的一个动画相关的内存泄露,一个静态方法,参数是一个 View,里面用一个静态的 ObjectAnimator 去对这个 View 进行动画处理,然后 LeakCanary 就检测出来内存泄露了。他的做法就是把静态方法改为实例方法就解决了。但是他以为是静态的 ObjectAnimator 持有了 View 的引用导致 Activity 不能被回收,其实呢 ObjectAnimator 在内部持有的是 View 的弱引用,所以事实上并不是这个原因。到底原因是在哪呢?其实是在他给这个 ObjectAnimator 设置了 Listener 然在 Listener 对 View 进行了相关操作,这就形成了一个强引用链 ObjectAnimator -> ArrayList -> View,最终导致了 Activity 未被销毁。所以之前写的静态方法也是可以的,只是需要在 Activity onDestory 时去 removeAllListeners 即可。 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
除此之外,对于内存泄露还有一些经验之谈,比如 Handler 的使用不当可能导致的内存泄露,解决办法就是静态内部类 + 弱引用,还有及时关闭资源文件、Context 的使用不当等。 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
内存抖动遇到的比较少,一般就是不要频繁的去创建对象销毁对象,典型的就是避免在 View 的 onDraw 方法中创建对象。 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
最后呢,还有一些通用的优化手段,比如 onTrimMemory 回调,这个方法会在 Vsync 信号到来时,Choreographer 执行 COMMIT 回调时执行,还有使用一些优化过的集合,比如 SparseArray、ArrayMap 等。这两个集合我也看过源码,SparseArray 用来替代 HashMap 的,但是 key 只能为 int 类型,简化了数据存储结构以及避免了自动装箱;ArrayMap 内部分别有两个长度为 10 的缓存队列用来缓存大小为 4 和 8 的 ArrayMap 对象,Bundle 内部就是使用 ArrayMap 存储数据的。 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
至此,内存优化我就讲完了。 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|