Android中常见的内存泄漏

写在前面

虽然现在手机的内存不断增大,但Android为了实现不同应用间运行隔离,不至于相互影响,所以对单个应用最大可使用的内存做出了限制。限制大小在不同手机设备和ROM上都可能不一样。如Android界的第一款手机HTC G1是16MB,后来的Nexus One是32MB。所以即使手机内存不断变大,但你开发的应用可使用的内存空间并没有增大很多,这也需要你开发时多注意注意内存问题,遵从最少使用内存的原则,避免内存泄漏的发生,这样不但能让你的应用避免被系统无故杀死,还能让用户使用更加流畅。

如果想查看自己应用可以使用的最大内存空间,可以参考:《Detect application heap size in Android》
如果你实在需要增大自己应用的内存使用大小,可以参考这篇文章:《How to increase heap size of an android application》

内存泄漏的产生

Android的虚拟机机制模仿JVM,所以也有垃圾回收机制。Android虚拟机中把内存分为两部分,一部分为栈空间,存储一些全局引用和静态变量等值,该空间的分配与回收由系统机制决定,垃圾回收不作用在这块区域;另一部分为堆空间,里面存储是对象的实例,需要开发者主动创建,垃圾回收主要作用在这部分,回收的一个主要策略是检测堆中的对象在栈空间有无对应的引用。如果没有引用指向它,则会被优先回收,如果有引用指向则不会被回收。所以如果开发者没有在适当的时间把一个对象的引用设置为null,则就会可能会产生内存泄漏。在Android中最常见的一个内存泄漏问题就是长时间持有Context。Context在Android中有非常大的作用,比如用来获取资源,所以基本上所有的视图都需要获得Context才能被创建。使用不当则很可能造成内存泄漏。

Android中内存泄漏表现

你开发了一个应用,刚开始使用起来还挺流畅,但随着使用时间变长,应用就变得越来越慢,最后导致用户不得重启应用才能继续使用。这就很可能出现了内存泄漏。就像上面提到的,如果说一个静态变量持有了一个Activity的引用,用户打开该Activity,会创建一个Activity的实例,此时即使你关闭该Activity,虽然它不再显示,但它的实例一直会在内存中存在,因为有一个静态变量一直指向它,导致它的内存空间就不会被当做垃圾回收。想想这个Activity中可能包含很多属性,很多视图的信息,它未被释放,会浪费很多内存空间。下面我们从两个个例子入手,讲解下内存泄漏和解决办法。

一个例子

private static Drawable sBackground;

@Override
protected void onCreate(Bundle state) {
  super.onCreate(state);
  
  TextView label = new TextView(this);
  label.setText("Leaks are bad");
  
  if (sBackground == null) {
    sBackground = getDrawable(R.drawable.large_bitmap);
  }
  label.setBackgroundDrawable(sBackground);
  
  setContentView(label);
}

以上使用一个静态变量来保存一个drawable。从上分析可以看到,一个TextView的局部变量持有了本Activity的引用,因为label是局部变量,所以并不会引起内存泄漏。但紧接着下面,使用了label.setBackgroundDrawable(sBackground); 有人可能就会想,这也没啥问题啊,即使sBackground作为一个静态变量,持有了一个drawable,这块内存不会被释放,但这块内存毕竟没有持有整个Activity的引用。但实际上你错了。我们来看下View.java中的setBackgroundDrawable源码,源码位置在
(frameworks/base/core/java/android/view/View.java)

public void setBackgroundDrawable(Drawable background) {
        ...

        if (background != null) {
            ...

            background.setCallback(this);
            ...
        } else {
            ...
        }

        ...
}

其中有一个background.setCallback(this);,所以这就导致这个静态变量指向的对象又持有了TextView这个对象的引用。这样,因为是静态变量,像我上一小节所说的,静态变量的生命周期基本和应用同周期,它持有了TextView对象引用,所以TextView不会被回收,然后TextView又持有了整个Activity的引用,所以最后就导致整个Activity在关闭后也不会被系统回收。

当然解决此种问题的方法非常简单,就是把sBackground换成非静态变量就行,这样当Activity关闭后,回收机制就能判断,这个Activity的空间不会被使用到了,所以就启动GC。

另一个例子

下面我们再举一个非常常见的例子,Android开发者很喜欢用单例模式,但有些开发者不注意就可能导致内存泄漏,如下:

private static DaVinci sDaVinci = null;

public static DaVinci with(Context context) {
    if ( sDaVinci == null ) {
        sDaVinci = new DaVinci(context);
    }
    return sDaVinci;
}

大家可能一时觉得这没啥问题啊,但这并不是一个好的写法,因为这可能让用户在使用时把一个Activity的Context传入,导致让一个单例持有了这个Activity的Context引用,造成内存泄漏。一个比较好的写法是使用
sDaVinci = new DaVinci(context.getApplicationContext());。因为Application的生命周期本来就是贯穿整个应用的,所以即使被持有也没关系。

几点建议

1,尽量不要用一个生命周期长于Activity的对象来持有Activity的引用。
2,在需要传入Context的时候尽量考虑使用Application的Context,而不是Activity的。
3,在Activity中尽量避免使用生命周期不受控制的非静态类型的内部类,可以使用静态类型的内部类加上弱引用的方式实现。

作者简介
彭涛(@彭涛me) 致力于让技术变得易懂且有趣
个人博客:http://pengtao.me, GitHub地址:https://github.com/CPPAlien

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

推荐阅读更多精彩内容

  • ###集合类泄漏 集合类如果仅仅有添加元素的方法,而没有相应的删除机制,导致内存被占用。如果这个集合类是全局性的变...
    RunningTeemo阅读 570评论 0 0
  • Android 自定义View的各种姿势1 Activity的显示之ViewRootImpl详解 Activity...
    passiontim阅读 171,413评论 25 707
  • 转载请注明出处:【huachao1001的简书:http://www.jianshu.com/users/0a7e...
    huachao1001阅读 2,295评论 2 26
  • Android 内存泄漏总结 内存管理的目的就是让我们在开发中怎么有效的避免我们的应用出现内存泄漏的问题。内存泄漏...
    _痞子阅读 1,622评论 0 8
  • 有些人一辈子只能活在记忆里 一辈子相离之后再也不要相遇 然后默默的放在心里 我总以为时间过得很慢 可是三年快过去了...
    书殁阅读 246评论 2 1