2021-02-22内存泄漏的场景以及优化

被使用的对象仍被其他对象所引用时,会造成该对象无法被gc及时回收,所占用的内存空间无法释放,从而导致内存单元的浪费。

Android开发过程中,一些不合理的开发方式会导致app存在内存泄露的情况,导致app性能下降,严重时会产生crash。下面介绍Android几种常见的内存泄露场景,以及优化方案。

单例导致的内存泄露

【问题】单例模式在Android开发过程中会经常用到,但是对于单例的使用不当可能会造成内存泄露。因为单例的静态特性,使得它的生命周期与application是一致的,如果单例对象持有了不再需要使用的对象的引用,那么在整个application生命周期中这个对象都不会被释放,从而造成内存泄露。


例如以上代码中,单例的getInstance方法传入了context的引用,该context指向的对象一般为Activity、Service等上下文,这时就会导致在整个application的生命周期内,该单例所持有的context无法释放,进一步导致Activity/Service内部资源以及视图无法被释放,严重影响app运行过程中的性能表现。

静态变量导致的内存泄露

【问题】静态变量存储在方法区(Method Area),它的生命周期从类加载开始,一直持续到进程结束。一旦静态变量初始化,那么它所持有的引用就只有等到进程结束才会释放。

比如,在Activity中为了避免重复创建Info,将sInfo声明为静态变量:


sInfo作为Activity的静态成员变量,持有Activity实例的引用,同时生命周期比Activity要长,在Activity退出时sInfo仍然引用了Activity,导致Activity无法及时释放,造成内存泄露。

【解决】在开发过程中,静态成员变量的生命周期很容易与其使用方生命周期不一致,为避免泄露,可尽量减少静态变量的使用,或者提供api回调,在使用方生命周期结束时将静态变量的引用指向null。


非静态内部类导致的内存泄露

【问题】非静态内部类,也就是匿名内部类,默认会持有其外部类的引用,当非静态内部类的生命周期长于外部类时,也会造成内存泄露。

Android开发过程中,非静态内部类的一个典型使用场景就是Handler,例如:


通过Handler的消息机制我们可以知道,Handler的引用会作为成员变量保存在msg中,也就是msg持有对Hanlder的引用,而Handler作为匿名内部类持有外部类Activity的引用,也就相当于发出的msg间接持有了Activity的引用。msg对象被发送到MessageQueue中,等待Looper的轮询处理,若Activity退出时,MessageQueue中仍存在未处理的msg,那么此时Activity就无法及时被释放,从而造成内存泄露。

【解决】通常在Android开发过程中,如果要使用内部类,那么可以采取静态内部类+弱引用的方式。


Handler通过弱引用的方式持有Activity的引用,这样在GC进行回收时,Activity占用的内存可以及时释放,从而避免了内存泄露。

当然,这样的做法是依赖GC的回收时机的,在Activity退出到GC到达之间的这段时间内,还是会有不必要的内存占用,所以最完善的做法是在Activity销毁时,将Handler中的msg全部都移除掉,不再进行处理:


匿名内部类的使用还有Thread,回调接口Listener等等,可使用类似的方法进行代码优化,避免不规范的内存使用。


未取消注册或回调造成的内存泄露

【问题】比如在Activity中动态注册广播监听,如果在Activity销毁时未进行unregister,那么这么广播会一直存在于系统中,并且持有Activity的引用,从而造成内存泄露。

【解决】注册与取消注册一定要成对使用。


在观察者模式的使用中,同样需要注意注册与取消注册的问题,比如RxJava+Retrofit的Observeble使用。


Timer和TimerTask造成的内存泄露

【问题】Timer和TimerTask一般会用于一些与时间相关的任务,比如循环滚动的ViewPager、倒计时相关的业务逻辑、轮询请求接口实现实时刷新等。如果Activity在销毁时,其成员变量Timer/TimerTask中的计时任务并没有完成,仍然持有Activity的引用,这时也会造成内存泄露。

【解决】在Activity#onDestroy()中将所有Timer/TimerTask成员进行cancel,可有效避免内存泄露。


集合保存的对象未及时清除导致的内存泄露

【问题】同样的生命周期不一致的问题,如果Collection/Map中保存的item不再需要使用,而集合仍然持有它们的引用,这时也会造成内存不合理占用。

【解决】这个问题需要关注的就是Collection/Map的生命周期,使用方在对其保存的items不再使用的时候需要及时clear/remove掉。


资源未及时关闭导致的内存泄露

【问题】文件存取的I/O操作、Stream流操作,数据库读取的Cursor操作,bitmap的读写操作,TypedArray的attr属性读取操作等,未及时进行关闭或者释放,也会造成内存泄露。

【解决】在业务操作结束的时候,及时释放相关的资源,避免占用内存空间,这些操作可大可小,很容易长时间累积之后OOM,需特别注意。


属性动画造成的内存泄露

【问题】与Timer类似,属性动画也是一个耗时任务,在Activity销毁时如果还有执行中的属性动画,同样会造成内存泄露。

【解决】在Activity销毁时对Activity中的属性动画执行canel操作。

WebView造成的内存泄露

【问题1】WebView在加载页面时,会长期占用内存而不能被释放,因为WebView的网络请求由内核实现,所以app端无法对其进行控制。

【解决】同大多数操作一样,在Activity销毁时调用WebView#destroy()。

【问题2】在Android 5.1版本以后,WebView中添加的Callback会持有Activity的引用,从而造成即使调用了destroy()方法,也无法释放WebView占用的内存。

【解决】在销毁WebView之前,先把WebView从其父容器之中移除。



总结

内存泄露在 Android 内存优化是一个比较重要的一个方面,很多时候程序中发生了内存泄露我们 不一定就能注意到,所有在编码的过程要养成良好的习惯。总结下来只要做到以下这几点就能避 免大多数情况的内存泄漏:

1. 单例的构造尽量不要依赖具体的Activity,两边的生命周期不一致。

2. 减少静态引用的使用,必要时在使用方生命周期结束回调中将静态变量置空。

3. 使用静态内部类+弱引用的方式代替非静态内部类。

4. 注册/取消注册广播或者观察者需要成对出现,并且对应使用方的生命周期。

5. Timer/TimerTask、ObjectAnimator及时取消。

6. I/O stream、File、bitmap、TypedArray及时关闭或者回收。

7. Activity销毁时对WebView进行完整的销毁。

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 199,830评论 5 468
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 83,992评论 2 376
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 146,875评论 0 331
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 53,837评论 1 271
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 62,734评论 5 360
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,091评论 1 277
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,550评论 3 390
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,217评论 0 254
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,368评论 1 294
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,298评论 2 317
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,350评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,027评论 3 315
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,623评论 3 303
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,706评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,940评论 1 255
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,349评论 2 346
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 41,936评论 2 341

推荐阅读更多精彩内容