前言
介于开发中内存泄漏的频繁出现,结合我在项目中发现的一些内存泄漏的使用情境,因此写了这篇文章做个简要的分析,希望能够帮助后来者避免类似的情景。
什么是内存泄露?
Android系统为每个应用程序分配的内存是有限的,当一个应用中产生的内存泄漏的情况比较多时,这就会导致应用所需要的内存超过这个系统分配的内存限额,进而造成了内存溢出而导致应用崩溃。在实际的开发过程中我们稍有不当操作随时都有可能造成内存泄露。
当一个对象已经不需要再使用了,本该被回收时,而有另外一个正在使用的对象持有它的引用从而导致它不能被回收,这导致本该被回收的对象不能被回收而停留在堆内存中,这就产生了内存泄漏。
程序中获取内存信息
通过ActivityManager获取相关信息,下面是一个例子代码:
privatevoid displayBriefMemory()
{
final ActivityManager activityManager = (ActivityManager) getSystemService(ACTIVITY_SERVICE);
ActivityManager.MemoryInfo info = new ActivityManager.MemoryInfo();
activityManager.getMemoryInfo(info);
Log.i(tag,"系统剩余内存:"+(info.availMem >> 10)+"k");
Log.i(tag,"系统是否处于低内存运行:"+info.lowMemory);
Log.i(tag,"当系统剩余内存低于"+info.threshold+"时就看成低内存运行");
}
另外通过Debug的getMemoryInfo(Debug.MemoryInfo memoryInfo)可以得到更加详细的信息。跟我们在ADB Shell看到的信息一样比较详细。
需要指出的是这里获取的内存大小是JVM为进程分配的内存大小,而当我们的应用中存在多个进程的时候,该应用理论上的内存大小限制:
所以当我们应用需要较大内存的时候也可以考虑通过多进程的方式进而获取更多的系统内存。
这样获取到的应用内存大小就是应用所能获取到的最大内存大小,当应用需要更多内存以支持其运行的时候,系统无法为其分配更多的内存,这样就造成了OOM的异常。
内存泄露的常见场景
(1)静态变量引起的内存泄漏
在java中静态变量的生命周期是在类加载时开始,类卸载时结束。换句话说,在android中其生命周期是在进程启动时开始,进程死亡时结束。所以在程序的运行期间,如果进程没有被杀死,静态变量就会一直存在,不会被回收掉。如果静态变量强引用了某个Activity中变量,那么这个Activity就同样也不会被释放,即便是该Activity执行了onDestroy(不要将执行onDestroy和被回收划等号)。这类问题的解决方案为:1.寻找与该静态变量生命周期差不多的替代对象。2.若找不到,将强引用方式改成弱引用。比较典型的例子如下:
/**
* 自定义单例对象
*/
public class Single {
private static Single instance;
private Context context;
private Object obj = new Object();
private Single(Context context) {
this.context = context;
}
/**
* 初始化获取单例对象
*/
public static Single getInstance(Context context) {
if (instance == null) {
synchronized(obj) {
if (instance == null) {
instance = new Single(context);
}
}
}
return instance;
}
当调用getInstance时,如果传入的context是Activity的context。只要这个单例没有被释放,这个Activity也不会被释放。
解决方案
传入Application的context,因为Application的context的生命周期比Activity长,可以理解为Application的context与单例的生命周期一样长,传入它是最合适的。
public class Single {
private Context context;
private static Single mInstance;
public static Single getInstance(Context context) {
if (mInstance == null) {
synchronized (Single.class) {
if (mInstance == null)
//将传入的context转换成Application的context
mInstance = new IMManager(context.getApplicationContext());
}
}
return mInstance;
}
private Single(Context context) {
this.context = context;
}
}
(2)非静态内部类引起的内存泄漏
在java中,创建一个非静态的内部类实例,就会引用它的外围实例。如果这个非静态内部类实例做了一些耗时的操作,就会造成外围对象不会被回收,从而导致内存泄漏。这类问题的解决方案为:1.将内部类变成静态内部类 2.如果有强引用Activity中的属性,则将该属性的引用方式改为弱引用。3.在业务允许的情况下,当Activity执行onDestory时,结束这些耗时任务。
内部线程造成的内存泄漏
public class LeakActivity extends Activity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.aty_leak);
test();
}
public void test() {
//匿名内部类会引用其外围实例LeakActivity.this,所以会导致泄漏
new Thread(new Runnable() {
@Override
public void run() {
while (true) {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}).start();
}
}
(3) Handler引起的内存泄漏
public class LeakAty extends Activity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.aty_leak);
fetchData();
}
private Handler mHandler = new Handler() {
public void handleMessage(android.os.Message msg) {
switch (msg.what) {
case 0:
// 刷新数据
break;
default:
break;
}
};
};
private void fetchData() {
//获取数据
mHandler.sendEmptyMessage(0);
}
}
mHandler 为匿名内部类实例,会引用外围对象LeakAty.this,如果该Handler在Activity退出时依然还有消息需要处理,那么这个Activity就不会被回收。
解决方案
public class LeakAty extends Activity {
private TextView tvResult;
private MyHandler handler;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.aty_leak);
tvResult = (TextView) findViewById(R.id.tvResult);
handler = new MyHandler(this);
fetchData();
}
//第一步,将Handler改成静态内部类。
private static class MyHandler extends Handler {
//第二步,将需要引用Activity的地方,改成弱引用。
private WeakReference<LeakAty> atyInstance;
public MyHandler(LeakAty aty) {
this.atyInstance = new WeakReference<LeakAty>(aty);
}
@Override
public void handleMessage(Message msg) {
super.handleMessage(msg);
LeakAty aty = atyInstance == null ? null : atyInstance.get();
//如果Activity被释放回收了,则不处理这些消息
if (aty == null||aty.isFinishing()) {
return;
}
aty.tvResult.setText("fetch data success");
}
}
private void fetchData() {
// 获取数据
handler.sendEmptyMessage(0);
}
@Override
protected void onDestroy() {
//第三步,在Activity退出的时候移除回调
super.onDestroy();
handler.removeCallbacksAndMessages(null);
}
}
(4)资源未关闭引起的内存泄漏
在使用一些资源性对象比如(Cursor,File,Stream,ContentProvider等)往往都用了一些缓冲,我们在不使用的时候,应该及时关闭它们,以便它们的缓冲及时回收内存。它们的缓冲不仅存在于Java虚拟机内,还存在于Java虚拟机外。如果我们仅仅是把它的引用设置为null,而不关闭它们,往往会造成内存泄露。
因为有些资源性对象,比如SQLiteCursor(在析构函数finalize(),如果我们没有关闭它,它自己会调close()关闭),如果我们没有关闭它,系统在回收它时也会关闭它,但是这样的效率太低了。因此对于资源性对象在不使用的时候,应该立即调用它的close()函数,将其关闭掉,然后再置为null.在我们的程序退出时一定要确保我们的资源性对象已经关闭。
/**
* 初始化Cursor对象
*/
Cursor cursor = getContentResolver().query(uri...);
if (cursor.moveToNext()) {
/**
* 执行自设你的业务代码
*/
doSomeThing();
}
这时候我们应当在doSomeThing之后执行cursor的close方法,关闭资源对象。
/**
* 初始化Cursor对象
*/
Cursor cursor = getContentResolver().query(uri…);
if (cursor.moveToNext()) {
/**
* 执行自设你的业务代码
*/
doSomeThing();
}
if (cursor != null) {
cursor.close();
}
“`
bitmap对象使用的内存较大,当我们不再使用Bitmap对象的时候一定要执行recycler方法,这里需要指出的是当我们在代码中执行recycler方法,Bitmap并不会被立即释放掉,其只是通知虚拟机该Bitmap可以被recycler了。
此外:我们时常使用的事件总线框架-EventBus,如果没有适当的时机解绑稍有不慎也会造成内存泄漏.
下面是一张Context对象的使用场景图
内存泄露检测
比较著名的两个开源的内存泄露检测库:
LeakCanary
BlockCanary