句柄泄漏以及对象泄漏问题汇总
一 文件句柄增加
经过测试, 句柄不断增加并且无法降低的原因以及解决:
socket: 进行网络连接过程中会创建socket连接,产生句柄文件.
解决:对于连接完成后需要关闭连接.
contentprovider: 注册的url 监听器 没有被释放
解决: 在注册 以及反注册必须要确保成对调用, 注册中检查listener 是否为null, 是null 则创建并注册,否则不注册;
反注册中判断listener 是否是null, 不是null 则反注册并设置listener = null, 是null 则返回
file , cursor: 文件打开后没有关闭造成泄漏
解决:打开文件的函数中添加finally 调用在代码块中关闭file
ibinder : 对象没有释放,包括service的stub以及service 的callback stub 两种
解决: 对于长连的binder , service binder 模块设计为单例, 外部通过引用使用binder
对于短连接binder 需要提供unbinder 的调用接口给外部调用者,在unbindservice 前需要反注册掉 binder 的callback
线程: 线程创建没有释放
解决: 线程池需要调用shutdown() , 线程一定要要保证退出。
二 对象引用无法回收
java 对象回收机制:
- java 对象如果自己没有被当前活动进程对象引用,则可以被认定为可以回收, 多个对象相互引用但是所有对象都没有被活动对象引用的话,则所有对象都可以算为可以被回收。
- gc 不是实时促发,也不是一次促发就会回收掉所有没有被引用的对象, 部分对象已经被放到finallizer中了,虽然此刻没有被释放,但是不算泄漏。
- finallize()函数不可靠,不要在这里进行逻辑处理。
解决方法:
对象需要频繁创建的,则此对象最容易产生泄漏,特别是我们现在大量使用的各种Manager
对于长期有效的类可以进行进行单例设计或者通过统一创建管理器进行管理,不必每次重新创建,避免不可控的多次调用。
对于每次必须重新创建的对象,则需要做好对象引用的释放操作,建议在每个模块的接口类中添加release()接口提供给外部使用者调用,来释放此模块接口类对外引用
release() 函数主要是做一下几点:
1 释放外部传进来的对象引用 , 设置对象为null, 比如外部传如的context listener , UI的adapter
2 此对象设置给其他的对象的引用设置为null
3 保证 其内部没有句柄相关泄漏
4 系统组件或者系统对象注册的回调等对象,在回调接口如果需要引用外部对象,外部对象需要使用weakreference引用,避免忘记反注册,导致外部对象无法被回收。(此情况跟句柄泄漏类同)
三 泄漏查找
- 通过android profiler 查找, profiler 查找主要是通过查看java对象个数以及每个对象被引用情况来分析查找问题, 此工具经过本地测试可以查找到所有leak类,但是leak原因需要分析堆栈查找。
在实际测试中,leak的对象大部分都有关联性,
一个leak可能是由另一个上层引用类泄漏导致自己泄漏。
也可能是其内部引用的对象泄漏,而泄漏的对象引用本对象,导致自己无法释放。
还有就是自己类内部有泄漏 比如自己没有释放binder 。
在分析堆栈时候本着以下原则和流程:
1 分模块查看,单独调试此泄漏模块
2 查看本泄漏对象的引用关系,添加release()函数在特定时间由外部使用者调用,比如activity ondestroy的时候, 如果有泄漏,先把外部传入的对象全设为null,
如果还有泄漏,把自己创建的传入到子对象的东西从子对象中移除,不要子对象引用自己的对象,
如果还是有泄漏,则查找本类中是否有泄漏
按照以上2条原则可以分析出全部泄漏
profiler的使用可以参考官方文档
https://developer.android.google.cn/studio/profile/android-profiler
- 泄漏查找可以使用开源第三方工具 leakcanary