Android 的虚拟机是基于寄存器的Dalvik,它的最大堆大小一般是16M,有的机器为24M。因此我们所能利用的内存空间是有限的。如果我们的内存占用超过了一定的水平就会出现OutOfMemory 的错误。
内存溢出的几点原因:
1. 资源释放问题
程序代码的问题,长期保持某些资源,如Context、Cursor、IO 流的引用,资源得不到释放造成内存泄露。
Drawable->TextView->Context
所以,最终该Context 也没有得到释放,发生了内存泄露。
虽然这个例子摘自安卓官方文档, 但这个文档是2009 年1 月的, 安卓版本还在3.0或者3.0 之前.
针对static 的解决方案
1) 应该尽量避免static 成员变量引用资源耗费过多的实例,比如Context。
2) Context 尽量使用ApplicationContext,因为Application 的Context 的生命周期比较长, 引用它不会出现内存泄露的问题。( 实际上是因为ApplicationContext 对象程序运行就会创建, 程序退出才销毁, 它有且仅有一个, 所以就算不用他也在那里, 不存在回收的问题.)
3) 使用WeakReference 代替强引用。比如可以使用WeakReference<Context> mContextRef;
(补充一点, getSystemService 方法请尽量使用ApplicationContext 去调用, 某些厂商修改了底层代码导致系统服务引用的Context 不能释放.)
怎么解决? 篇幅太长, 贴一个orcale 的官方文档地址:
http://docs.oracle.com/javase/1.5.0/docs/guide/misc/threadPrimitiveDeprecation.html
有些人喜欢用Android 提供的AsyncTask,但事实上AsyncTask 的问题更加严重,Thread 只有在run 函数不结束时才出现这种内存泄露问题,然而AsyncTask 内部的实现机制是运用了ThreadPoolExcutor,该类产生的Thread 对象的生命周期是不确定的,是应用程序无法控制的,因此如果AsyncTask 作为Activity 的内部类,就更容易出现内存泄露的问题。
针对这种线程导致的内存泄露问题的解决方案:
(一) 将线程的内部类,改为静态内部类(因为非静态内部类拥有外部类对象的强引用,而静态类则不拥有)。
(二) 在线程内部采用弱引用保存Context 引用。
如果是由循环组成的耗时操作, 循环中判断isCancle(), 如果为true, break 掉循环. 在Activity 的onDestory 调用AsyncTask 的cancle()方法.
AsyncTask 适合短耗时的操作, 几秒钟, 如果长耗时, 请使用其它API, 比如Executor,ThreadPoolExecutor 和FutureTask 等)
使用database 要关闭,
使用流要关闭,
使用Timer 必须calcel,
使用TimerTask 必须cancel,
注册内容观察者必须取消注册
内容观察者被添加到ContentQueryMap 后不用时delete 这个内容观察者
Handler 在activity 的onDestory 中removeMessageAndCallback(null)
内存溢出的几点原因:
1. 资源释放问题
程序代码的问题,长期保持某些资源,如Context、Cursor、IO 流的引用,资源得不到释放造成内存泄露。
2. 对象内存过大问题
保存了多个耗用内存过大的对象(如Bitmap、XML 文件),造成内存超出限制。
3. static 关键字的使用问题
static 是Java 中的一个关键字,当用它来修饰成员变量时,那么该变量就属于该类,而不是该类的实例。所以用static 修饰的变量,它的生命周期是很长的,如果用它来引用一些资源耗费过多的实例(Context 的情况最多),这时就要谨慎对待了。public class ClassName {
private static Context mContext;
//省略
}
以上的代码是很危险的,如果将Activity 赋值到mContext 的话。那么即使该Activity已经onDestroy,但是由于仍有对象保存它的引用,因此该Activity 依然不会被释放。
我们举Android 文档中的一个例子。
private static Drawable sBackground;
@Override
protected void onCreate(Bundle state) {
super.onCreate(state);
TextView label = new TextView(this); //getApplicationContext
label.setText("Leaks are bad");
if (sBackground == null) {
sBackground = getDrawable(R.drawable.large_bitmap);
}
label.setBackgroundDrawable(sBackground);
setContentView(label);
}
sBackground 是一个静态的变量,但是我们发现,我们并没有显式的保存Contex的引用,但是,当Drawable 与View 连接之后,Drawable 就将View 设置为一个回调,由于View 中是包含Context 的引用的,所以,实际上我们依然保存了Context的引用。这个引用链如下:
Drawable->TextView->Context
所以,最终该Context 也没有得到释放,发生了内存泄露。
虽然这个例子摘自安卓官方文档, 但这个文档是2009 年1 月的, 安卓版本还在3.0或者3.0 之前.
之所会出现内存泄露是因为在安卓3.0 之前View 的源码中是这样的:
public void setBackgroundDrawable(Drawable background) {
// ... ...
background.setCallback(this);
// ... ...
}
Drawable 中源码是这样的:
public final void setCallback(Callback cb) {
mCallback = cb;
}
这个background 对象持有View 的引用, View 中又持有Activity 的引用,background 是static, 那么background 只有在虚拟机退出时才会被回收, 它持有的引用才会被释放, 也就是这个View 才有可能被回收,这个Activity 才会被回收.当然,上面就是这么说的, 但是安卓3.0 之后这个问题被解决了
安卓3.0 之后源码中的Drawable 的setCallback 方法是这样:
public final void setCallback(Callback cb) {
mCallback = new WeakReference<Callback>(cb);
}
Drawable 中保存的是View 的弱引用, 解决了内存泄露的问题.
针对static 的解决方案
1) 应该尽量避免static 成员变量引用资源耗费过多的实例,比如Context。
2) Context 尽量使用ApplicationContext,因为Application 的Context 的生命周期比较长, 引用它不会出现内存泄露的问题。( 实际上是因为ApplicationContext 对象程序运行就会创建, 程序退出才销毁, 它有且仅有一个, 所以就算不用他也在那里, 不存在回收的问题.)
3) 使用WeakReference 代替强引用。比如可以使用WeakReference<Context> mContextRef;
(补充一点, getSystemService 方法请尽量使用ApplicationContext 去调用, 某些厂商修改了底层代码导致系统服务引用的Context 不能释放.)
4. 线程导致内存溢出
public class MyActivity extends Activity {
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
new MyThread().start();
}
private class MyThread extends Thread{
@Override
public void run() {
super.run();
//do somthing while(true)
}
}
}
这段代码很平常也很简单,是我们经常使用的形式。我们思考一个问题:假设MyThread的run 函数是一个很费时的操作,当我们开启该线程后,将设备的横屏变为了竖屏,一般情况下当屏幕转换时会重新创建Activity,按照我们的想法,老的Activity 应该会被销毁才对,然而事实上并非如此。由于我们的线程是Activity 的内部类,所以MyThread 中保存了Activity 的一个引用,当MyThread 的run 函数没有结束时,MyThread 是不会被销毁的,因此它所引用的老的Activity 也不会被销毁,因此就出现了内存泄露的问题。
怎么解决? 篇幅太长, 贴一个orcale 的官方文档地址:
http://docs.oracle.com/javase/1.5.0/docs/guide/misc/threadPrimitiveDeprecation.html
有些人喜欢用Android 提供的AsyncTask,但事实上AsyncTask 的问题更加严重,Thread 只有在run 函数不结束时才出现这种内存泄露问题,然而AsyncTask 内部的实现机制是运用了ThreadPoolExcutor,该类产生的Thread 对象的生命周期是不确定的,是应用程序无法控制的,因此如果AsyncTask 作为Activity 的内部类,就更容易出现内存泄露的问题。
针对这种线程导致的内存泄露问题的解决方案:
(一) 将线程的内部类,改为静态内部类(因为非静态内部类拥有外部类对象的强引用,而静态类则不拥有)。
(二) 在线程内部采用弱引用保存Context 引用。
(针对AsyncTask 怎么解决?
AsyncTask 有cancle()方法, 终止任务. 但是, 这个方法是不一定成功的!如果是由循环组成的耗时操作, 循环中判断isCancle(), 如果为true, break 掉循环. 在Activity 的onDestory 调用AsyncTask 的cancle()方法.
AsyncTask 适合短耗时的操作, 几秒钟, 如果长耗时, 请使用其它API, 比如Executor,ThreadPoolExecutor 和FutureTask 等)
补充一点: registerReciever 一定要调用unregisterReciever, 但是如果注册广播接收者不成功, 取消注册时将会抛出异常. 解决方法: 取消注册try...catch.
补充:
使用cursor 要关闭,使用database 要关闭,
使用流要关闭,
使用Timer 必须calcel,
使用TimerTask 必须cancel,
注册内容观察者必须取消注册
内容观察者被添加到ContentQueryMap 后不用时delete 这个内容观察者
Handler 在activity 的onDestory 中removeMessageAndCallback(null)