您的位置:首页 > 移动开发 > Android开发

避免Android中Context引起的内存泄露--binbinyang

2017-04-24 22:33 302 查看
今天在写代码的时候,提交代码给同事,同事检查了我的代码 ,当时就犯了个错误,会引起内存泄漏


单例引起的内存泄露

单例是我们比较简单常用的一种设计模式,然而如果单例使用不当也会导致内存泄露。 比如这样一个例子,我们使用饿汉式初始化单例,AppSettings我们需要持有一个Context作为成员变量,如果我们按照下面的实现其实是有问题。
public class AppSettings {
private Context mAppContext;
private static AppSettings sInstance = new AppSettings();

//some other codes
public static AppSettings getInstance() {
return sInstance;
}

public final void setup(Context context) {
mAppContext = context;
}
}


sInstance作为静态对象,其生命周期要长于普通的对象,其中也包含Activity,当我们进行屏幕旋转,默认情况下,系统会销毁当前Activity,然后当前的Activity被一个单例持有,导致垃圾回收器无法进行回收,进而产生了内存泄露。

解决的方法就是不持有Activity的引用,而是持有Application的Context引用。代码如下修改

public final void setup(Context context) {
mAppContext = context.getApplicationContext();
}

下面这些,是我在别人帖子上面看到的 拿过来


先作个总结:
1.getApplicationContext()可以取到Application对象,而getContext()通常认为返回Activity对象(当然,事实上并不局限于Activity)。

2.对于Application,从Manifest文件中可以看出一个应用程序一般只有一个application节点。Application其实就是一个应用,即:当前应用程序只要还处于运行状态,那么就可以取到Application对象。

3.Application是一个长引用,Activity是短引用。Application适用于存储那些需要反复读取的对象,比如用户的用户名和密码,应用程序的当前设置等。Activity适用于当前活动窗体,比如显示一个dialog,或新建一个View,传入的context对象就应该是当前Activity,而非Application。
    
 Android应用程序限制使用的堆内存是16M(注:堆内存与设备的性能也有一定关系,性能高的设备可用堆内存可能是24M或者更高),其中电话功能会占用一部分,而开发者能够使用的则非常有限。如果你不打算用完所有内存,那么你的应用就应该尽可能少用内存,从而使其他的程序在运行时不致于被杀掉。Android系统在内存中能够持有的应用程序越多,用户在程序间进行切换时就越快。作为工作的一部分,我研究Android应用中的内存泄露问题,结果发现多数情况是同一个问题引起的:对Context持有一个过长的引用。
     在Android中,Context被用在许多操作中,但多数是为了加载或访问资源。这就是为什么所有的Widget组件都会在构造器中接收一个Context参数的原因所在。对于一般的Android应用,通常有两种Context,即ActivityApplication。开发者通常需要context在类或方法间传递时会选择Activity。



    这表明Views对整个Activity有一个引用,因此你的Activity中任何东西都会被(Views)持有,一般被持有的是视图层级和它对应的资源。因此,如果你泄露了Context(泄露:即你持有一个Context引用,结果阻止GC对其进行回收),你就泄露了一些内存。如果你不细心,泄露一个Activity就会变得非常容易。

    当屏幕方向改变,系统默认会保存它的状态,销毁当前Activity并重新创建一个。为了做到这一点,Android会从资源中重新加载应用程序的UI。现在假设你开发一个应用,包含了大量位图(Bitmap),你不想每次屏幕切换时都去加载位图。那么最简单的方法是利用static静态域。



    上面的代码非常简洁,但也容易出错:第一次屏幕方向改变时,最初创建的Activity(每次屏幕改变都会重新创建Activity)就会出现内存泄漏。当一个Drawable对象被附着到View中,View就会在这个Drawable上被设定作为回调callback。上面的代码片段表明drawable对TextView有一个引用,而TextView本身又对Activity(即Context)持有引用,反过来,Activity又对许多东西持有引用(这取决于你的代码)。

    上面的例子是泄露Context最简单的一种情况,你能在Home screen的源代码(查找unbindDrawables()方法)中看到我们是怎么解决它的:当Activity被销毁时,设置被存储的drawable的回调为null。有趣的是,你创建一系列内存泄露的Context会出现许多情况,并且它们都是糟糕的。它们很快使你在运行过程时出现内存溢出。

    有两种简单的方式去避免Context相关的内存泄露。最明显的一种就是避免Context超出它的范围。上面的例子展现了静态引用的情况:内部类以及对外部类的间接引用是很危险的。第二种方案是用Application。这种Context不依赖于Activity的生命周期,而是与你的应用程序同生共死。如果你打算持有一个长期活动并且需要Context引用的对象,记得使用Application对象。你可以通过调用Context.getApplicationContext() 或者 Activity.getApplication()方法得到它。

总的来说,要避免Context相关的内存泄露,铭记以下几条:

•不要对Activity(Activity继承自Context)作长期的引用(一个指向Activity的引用与Activity本身有相同的生命周期);

•(如果使用长引用)试着用Application代替Activity;

•如果你不能控制内部类的生命周期,避免使用非静态内部类,应该用静态内部类,并且对里面的Activity作弱引用。该问题的解决方法是:对于外部类,用WeakReference构造静态内部类,同时要在视图根完成,并且它的WeakReference内部类要有一个实例(WeakReference)。
•垃圾回收不是防止内存泄露的保险方式。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: