【翻译】Android避免内存泄露(Activity的context 与Context.getApplicationContext)

原谅地址:http://android-developers.blogspot.com/2009/01/avoiding-memory-leaks.html ,英文原文在翻译之后

Android 应用至少,在T-Mobile G1这个型号,就有16MB的堆内存。这个容量对于手机来说是很大了,但是对于有些开发者来说是少了些。为了全其他应用可以运行而不被系统杀掉,即使你没有打算使用完所有分配的容量,你也应该尽量少地使用这些容量。

Android能保存越多的应用,用户在切换应用的时候就会越快。我在工作中,遇到了内存泄露,它们出现的原因很多时候是因为同一个错误:保存Context持有一个生命周期很长的引用

安卓开发中,Context在很多操作中都需要,它大部分是用来加载和操作应用资源(resources). 这也是为什么一些组件会在它们的构造函数里接收一个Context参数。一般在一个安卓应用中,通常有两个种Context:Activity 和 Application. 通常大家都是把第一个Context(Activity)传给需要这个参数的方法和类:代码如下

@Override
protected void onCreate(Bundle state) {
  super.onCreate(state);

  TextView label = new TextView(this);
  label.setText("Leaks are bad");

  setContentView(label);
}

这意味着这些视图(Views)对这整个Activity持有一个引用,因此也对这个Activity所持有的对象持有引用。这些对象通常是整个View的hierachy和它们所使用的资源(resources)。因此,一旦你的Context有内存泄露(leak:意思是你对Context引用着,导致它不能给GC回收),意思着你会泄露很多的内存。如果你不小心,Activity的内存泄露是很容易出现的。

当屏幕的方向(orientation)改变,系统默认地,会销毁当前的activity, 然后保存状态(state)的时候,创建一个新的activity。在做这个的时候,安卓会从资源(resources)中重新加载UI。在这样做的时候,Android会重新从资源(resources)里加载UI。想像一下,你在写一个应用,有一张很大的图片(bitmap),你不想在任何旋转操作的时候加载。 那么想要在任何旋转时,保存它,不重新加载的最简单方法就是把它声明成‘静态成员变量‘(static field):代码如下

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);
  //这个代码的情况就是,TextView持有一个Activity的Context的引用,然后又因为它持有一个静态变量Drawable的引用,导致这个TextView与类的生命周期相同,从而导致Activity的Context也被长期持有,导致这个activity被人引用着而不能被GC回收
  setContentView(label);
}

这份代码运行会很快,但是也是错误的。 它会泄露第一个在屏幕旋转时创建的activity。当一个Drawable对象被分配到一个view上的时候, 这个view会被设成这个drawable的回调(callback)。在上面的代码片中,这意味着drawable对象持有TextView的引用,而TextView又因为context的原因持有Activity的引用,也因此持有很多其它的引用,具体看你代码的情况。

这份代码是Context导致内存泄露的其中一个最简单的例子。

Android applications are, at least on the T-Mobile G1, limited to 16 MB of heap. It‘s both a lot of memory for a phone and yet very little for what some developers want to achieve. Even if you do not plan on using all of this memory, you should use as little as possible to let other applications run without getting them killed. The more applications Android can keep in memory, the faster it will be for the user to switch between his apps. As part of my job, I ran into memory leaks issues in Android applications and they are most of the time due to the same mistake: keeping a long-lived reference to a Context.

On Android, a Context is used for many operations but mostly to load and access resources. This is why all the widgets receive a Context parameter in their constructor. In a regular Android application, you usually have two kinds of ContextActivity andApplication. It‘s usually the first one that the developer passes to classes and methods that need a Context:

@Override
protected void onCreate(Bundle state) {
  super.onCreate(state);

  TextView label = new TextView(this);
  label.setText("Leaks are bad");

  setContentView(label);
}

This means that views have a reference to the entire activity and therefore to anything your activity is holding onto; usually the entire View hierarchy and all its resources. Therefore, if you leak the Context ("leak" meaning you keep a reference to it thus preventing the GC from collecting it), you leak a lot of memory. Leaking an entire activity can be really easy if you‘re not careful.

When the screen orientation changes the system will, by default, destroy the current activity and create a new one while preserving its state. In doing so, Android will reload the application‘s UI from the resources. Now imagine you wrote an application with a large bitmap that you don‘t want to load on every rotation. The easiest way to keep it around and not having to reload it on every rotation is to keep in a static field:

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);
}

This code is very fast and also very wrong; it leaks the first activity created upon the first screen orientation change. When aDrawable is attached to a view, the view is set as a callback on the drawable. In the code snippet above, this means the drawable has a reference to the TextView which itself has a reference to the activity (the Context) which in turns has references to pretty much anything (depending on your code.)

This example is one of the simplest cases of leaking the Context and you can see how we worked around it in the Home screen‘s source code (look for the unbindDrawables() method) by setting the stored drawables‘ callbacks to null when the activity is destroyed. Interestingly enough, there are cases where you can create a chain of leaked contexts, and they are bad. They make you run out of memory rather quickly.

There are two easy ways to avoid context-related memory leaks. The most obvious one is to avoid escaping the context outside of its own scope. The example above showed the case of a static reference but inner classes and their implicit reference to the outer class can be equally dangerous. The second solution is to use the Application context. This context will live as long as your application is alive and does not depend on the activities life cycle. If you plan on keeping long-lived objects that need a context, remember the application object. You can obtain it easily by callingContext.getApplicationContext() or Activity.getApplication().

In summary, to avoid context-related memory leaks, remember the following:

  • Do not keep long-lived references to a context-activity (a reference to an activity should have the same life cycle as the activity itself)
  • Try using the context-application instead of a context-activity
  • Avoid non-static inner classes in an activity if you don‘t control their life cycle, use a static inner class and make a weak reference to the activity inside. The solution to this issue is to use a static inner class with a WeakReferenceto the outer class, as done in ViewRoot and its W inner class for instance
  • A garbage collector is not an insurance against memory leaks
时间: 2024-08-24 07:25:11

【翻译】Android避免内存泄露(Activity的context 与Context.getApplicationContext)的相关文章

Android App 内存泄露之Thread

Thread 内存泄露 线程也是造成内存泄露的一个重要的源头.线程产生内存泄露的主要原因在于线程生命周期的不可控. 1.看一下下面是否存在问题 <span style="white-space:pre"> </span>/** * * @version 1.0.0 * @author Abay Zhuang <br/> * Create at 2014-7-17 */ public class ThreadActivity extends Activ

Android 常见内存泄露 &amp; 解决方案

前言 内存泄漏(Memory Leak)是指程序中己动态分配的堆内存由于某种原因程序未释放或无法释放,造成系统内存的浪费,导致程序运行速度减慢甚至系统崩溃 (OOM) 等严重后果. 那什么情况下不能被回收呢? 目前 java 垃圾回收主流算法是虚拟机采用 GC Roots Tracing 算法.算法的基本思路是:通过一系列的名为 GC Roots (GC 根节点)的对象作为起始点,从这些节点开始向下搜索,搜索所走过的路径,当一个对象到GC Roots没有任何引用链相连(图论说:从GC Roots

Android App 内存泄露之Handler

Android App 内存泄露之Handler Handler也是造成内存泄露的一个重要的源头,主要Handler属于TLS(Thread Local Storage)变量,生命周期和Activity是不一致的 ,Handler引用Activity会存在内存泄露. 看一下如下代码 /** * * 实现的主要功能. * @version 1.0.0 * @author Abay Zhuang <br/> * Create at 2014-7-28 */ public class Handler

Android App 内存泄露之调试工具(1)

Android App 内存泄露之工具(1) 使用内存监测工具 DDMS –> Heap 操作步骤 启动eclipse后,切换到DDMS透视图,并确认Devices视图.Heap视图都是打开的,没打开的直接Window>ShowView>自己选 将手机通过USB链接至电脑,链接时需要确认手机是处于"USB调试"模式 链接成功后,在DDMS的Devices视图中将会显示手机设备的序列号,以及设备中正在运行的部分进程信息: 点击选中想要监测的进程,如果在进程列表中未出现你

如何用MAT分析Android应用内存泄露

使用工具:Android Studio 2.0 Preview, Android Device Monitor, MAT(Memory Analyzer). 点击Android Studio工具栏上的“Android Device Monitor”,如下图 打开后选中应用进程,然后点击“Update heap”,接着反复点击应用的每个activity,最后“Dump HPROF file”,如下图1-2-3所示 保存hprof文件. 下面需要对hprof文件进行转换. 打开CMD终端,进入到\s

【转载】 Android App 内存泄露之Thread

转载地址http://blog.csdn.net/zhuanglonghai/article/details/37909553 Thread 内存泄露 线程也是造成内存泄露的一个重要的源头.线程产生内存泄露的主要原因在于线程生命周期的不可控. 1.看一下下面是否存在问题 <span style="white-space:pre"> </span>/** * * @version 1.0.0 * @author Abay Zhuang <br/> *

Android常见内存泄露,学会这六招优化APP性能

很多开发者都知道,在面试的时候会经常被问到内存泄露和内存溢出的问题. 1.内存溢出(Out Of Memory,简称 OOM),通俗理解就是内存不够,即内存占用超出内存的空间大小. 2.内存泄漏(Memory Leak),简单理解就是内存使用完毕之后本该垃圾回收却未被回收. 2 在正式了解内存泄露之前,首先来简单回顾一下 Java 内存分配策略. Java 程序运行时的内存分配策略有三种,分别是静态分配.栈式分配.堆式分配,对应的主要内存空间分别是静态存储区(也称方法区).栈区.堆区. 1.静态

Android中内存泄露与如何有效避免OOM总结

一.关于OOM与内存泄露的概念 我们在Android开发过程中经常会遇到OOM的错误,这是因为我们在APP中没有考虑dalvik虚拟机内存消耗的问题. 1.什么是OOM OOM:即OutOfMemoery,顾名思义就是指内存溢出了.内存溢出是指APP向系统申请超过最大阀值的内存请求,系统不会再分配多余的空间,就会造成OOM error.在我们Android平台下,多数情况是出现在图片不当处理加载的时候. Android系统为每个应用程序分配的内存有限,当一个应用中产生的内存泄漏比较多时,就难免会

【转】.. Android应用内存泄露分析、改善经验总结

原文网址:http://wetest.qq.com/lab/view/107.html?from=ads_test2_qqtips&sessionUserType=BFT.PARAMS.194206.TASKID&ADUIN=554147273&ADSESSION=1467939955&ADTAG=CLIENT.QQ.5479_.0&ADPUBNO=26582 前言   通过这几天对好几个应用的内存泄露检测和改善,效果明显: 完全退出应用时,手动触发GC,从原来占有