【Android 学习系列】 内存泄露(一)

文章来源: http://hubingforever.blog.163.com/blog/static/17104057920113297362974/


Android的每个应用程序都是有一个专门的Dalvik虚拟机来运行,

它是由Zygote服务进程孵化出来(Zygote: http://blog.csdn.net/lizhiguo0532/article/details/6826005),

Android为不同的进程分配了不同的内存上限(这个上限是如何定的?)


(1) 最常见的泄露-引用没有释放造成的泄露

a.  注册没有取消,造成的泄露

这种Android的内存泄露比纯java的内存泄露还要严重,因为其他一些Android程序可能引用我们的Anroid程序的对象(比如注册机制)。即使我们的Android程序已经结束了,但是别的引用程序仍然还有对我们的Android程序的某个对象的引用,泄露的内存依然不能被垃圾回收。


比如:
假设我们希望在锁屏界面(LockScreen)中,监听系统中的电话服务以获取一些信息(如信号强度等),则可以在LockScreen中定义一个PhoneStateListener的对象,同时将它注册到TelephonyManager服务中。对于LockScreen对象,当需要显示锁屏界面的时候就会创建一个LockScreen对象,而当锁屏界面消失的时候LockScreen对象就会被释放掉。

但是如果在释放LockScreen对象的时候忘记取消我们之前注册的PhoneStateListener对象,则会导致LockScreen无法被垃圾回收。如果不断的使锁屏界面显示和消失,则最终会由于大量的LockScreen对象没有办法被回收而引起OutOfMemory,使得system_process进程挂掉。

虽然有些系统程序,它本身好像是可以自动取消注册的(当然不及时),但是我们还是应该在我们的程序中明确的取消注册,程序结束时应该把所有的注册都取消掉。


b. 集合的对象没有及时清理

主要是Arraylist等集合中的对象,需要清理;如果Arraylist是static,问题会更严重。

c. Context泄漏-----这个还不是很明白

所谓的Context泄漏,其实更多的是指Activity泄露,这是一个很隐晦的OutOfMemoryError的情况。
先看一个Android官网提供的例子:

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

这段代码效率很快,但同时又是极其错误的; 在第一次屏幕方向切换时它泄露了一开始创建的Activity。当一个Drawable附加到一个 View上时, View会将其作为一个callback设定到Drawable上。上述的代码片段,意味着这个静态的Drawable拥有一个TextView的引用, 
而TextView又拥有Activity(Context类型)的引用,换句话说,Drawable拥有了更多的对象引用。即使Activity被 销毁,内存仍然不会被释放。 
另外,对Context的引用超过它本身的生命周期,也会导致该Context无法回收,从而导致内存泄漏。所以尽量使用Application这种Context类型。 
这种Context拥有和应用程序一样长的生命周期,并且不依赖Activity的生命周期。如果你打算保存一个长时间的对象, 
并且其需要一个 Context,记得使用Application对象。你可以通过调用Context.getApplicationContext()或 Activity.getApplication()轻松得到Application对象。 
最近遇到一种情况引起了Context泄漏,就是在Activity销毁时,里面有其他线程没有停。 
总结一下避免Context泄漏应该注意的问题: 
1.尽量使用Application这种Context类型。 
2.注意对Context的引用不要超过它本身的生命周期。 
3.慎重的对Context使用“static”关键字。 
4.Context里如果有线程,一定要在onDestroy()里及时停掉。 

d. Static 关键字的使用要主要,对一些大的资源文件,尽量不要用static;

e. Webview对象需要及时销毁,调用destory()去销毁;

f. GridView的滥用

GridView和ListView的实现方式不太一样。GridView的View不是即时创建的,而是全部保存在内存中的。比如一个GridView有100项,虽然我们只能看到10项,但是其实整个100项都是在内存中的。

(2)资源对象没有关闭

常见的资源对象有cursor,file,socket等,需要调用close();

(3)不良代码造成的内存压力

a.  Bitmap没有调用recycle(),就直接设置为null;

在Android4.0之前,Bitmap的内存是分配在Native堆中,调用recycle()可以立即释放Native内存。
从Android4.0开始,Bitmap的内存就是分配在dalvik堆中,即JAVA堆中的,调用recycle()并不能立即释放Native内存。但是调用recycle()也是一个良好的习惯。


可以通过dumpsys meminfo命令查看一个进程的内存情况。
示例:adb shell "dumpsys meminfo com.lenovo.robin"
运行结果:。。。。。

b. 构造adapter时候,没有使用convertView

public View getView(int position, View convertView, ViewGroup parent)

来向ListView提供每一个item所需要的view对象。初始时ListView会从BaseAdapter中根据当前的屏幕布局实例化一定数量的view对象,同时ListView会将这些view对象缓存起来。当向上滚动ListView时,原先位于最上面的list item的view对象会被回收,然后被用来构造新出现的最下面的list item。这个构造过程就是由getView()方法完成的,getView()的第二个形参 View convertView就是被缓存起来的list item的view对象(初始化时缓存中没有view对象则convertView是null)。

c . ThreadLocal使用不当
如果我们粗暴的把ThreadLocal设置null,而不调用remove()方法或set(null),那么就可能造成ThreadLocal绑定的对象长期也不能被回收,因而产出内存泄露。


(4)JNI代码的内存泄露

关于此的详细内容请参考《JNI引用与垃圾回收》

你可能感兴趣的:(【Android 学习系列】 内存泄露(一))