Android内存泄漏
直入正题,首先说说什么是内存泄漏
一、什么是内存泄漏
内存泄漏是当程序向系统申请内存空间后,在使用完毕后未释放内存或释放内存失败,从而产生了无用的内存消耗。各位看官也可以顺道去了解一下垃圾回收机制(gc)。这里还是简单讲一下,当一个不可到达的对象时,在执行gc操作时会被回收,而当一个我们不再使用的数据依然是可到达的状态的时候,这就是发生了内存泄漏。
二、内存泄漏的影响
在说内存泄漏的影响之前大家需要先了解一下内存溢出,简单来说,内存溢出就是所需要的内存超出了系统为之分配的内存。
而过多的内存泄漏将会导致内存溢出,这将会导致应用出现Crash,这对用户来说将会是很不好的体验,所以在开发过程中我们应该尽量避免内存泄漏的发生。
三、常见的内存泄漏情形
在开发过程中发生内存泄漏的情况很多,下面我将对比较常见的内存泄漏情况进行简单说明
1.单例模式引发的内存泄漏
Android的单例模式非常受开发者的喜爱,不过使用的不恰当的话也会造成内存泄漏。因为单例的静态特性使得单例的生命周期和应用的生命周期一样长,这就说明了如果一个对象已经不需要使用了,而单例对象还持有该对象的引用,那么这个对象将不能被正常回收,这就导致了内存泄漏。
下面给个单例发生内存泄漏的例子:
public class AppManager {
private static AppManager instance;
private Context context;
private AppManager(Context context) {
this.context = context;
}
public static AppManager getInstance(Context context) {
if (instance != null) {
instance = new AppManager(context);
}
return instance;
}
}
当我们在初始化getInstance的时候传入的context为Activity的context的时候将会发生内存泄漏,因为在Activity退出时,activity本该被回收,但是由于单例模式持有了Activity的引用,而单例模式的生命周期和应用一样长,所以会导致Activity不能被回收,这也就导致了内存泄漏的发生。
避免方法:用Application的Context代替Activity的Context,因为Application的生命周期和单例是相同的,所以将不会有内存泄漏问题。
2.非静态内部类引发的内存泄漏
非静态内部内部类会默认持有外部类的引用,这在内部类与外部类的生命周期不相同的时候将会发生内存泄漏。
例:
public class MainActivity extends AppCompatActivity {
private static DemoResource mResource = null;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
if(mManager == null){
mManager = new DemoResource();
}
//...
}
class DemoResource {
//...
}
}
由于DemoResource是非静态内部类,持有外部类的引用,而mResource 的生命周期和应用的相同,所以当activity结束时会发生内存泄漏。
避免方法:避免使用非静态内部类,尽量使用静态内部类。
3.Handler造成的内存泄漏
在开发过程中需要访问网络或一些耗时的操作时经常会使用Handler进行处理,这时候不加留意便会发生内存泄漏。(我们知道消息队列是在一个Looper线程中不断轮询处理消息,那么当这个Activity退出时消息队列中还有未处理的消息或者正在处理消息,而消息队列中的Message持有mHandler实例的引用)
下面举一个Handler内存泄漏的例子:
public class MainActivity extends AppCompatActivity {
private Handler mHandler = new Handler() {
@Override
public void handleMessage(Message msg) {
//...
}
};
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
loadData();
}
private void loadData(){
//...request
//发送消息
Message message = Message.obtain();
mHandler.sendMessage(message);
}
}
这种写法相信很多同学都有过,然而这样将会造成内存泄漏,因为mHandler持有Activity的引用。在上面括号中的情况发生时会发生内存泄漏。
解决办法:
继承Handler写一个静态内部类,然后对Handler的持有对象使用弱引用,这样再回收是也可以回收handler持有的对象。并在Activity的Destroy时或者Stop时应该移除消息队列中的消息。
具体的示例如下:
public class MainActivity extends AppCompatActivity {
private MyHandler mHandler = new MyHandler(this);
private TextView mTextView ;
private static class MyHandler extends Handler {
private WeakReference<Context> reference;
public MyHandler(Context context) {
reference = new WeakReference<>(context);
}
@Override
public void handleMessage(Message msg) {
MainActivity activity = (MainActivity) reference.get();
if(activity != null){
activity.mTextView.setText("");
}
}
}
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
mTextView = (TextView)findViewById(R.id.textview);
loadData();
}
private void loadData() {
//...request
Message message = Message.obtain();
mHandler.sendMessage(message);
}
@Override
protected void onDestroy() {
super.onDestroy();
mHandler.removeCallbacksAndMessages(null);
}
}
注意,因为使用的是弱引用持有数据,所以在使用时一定要先进行判Null,否则可能会造成空指针异常
4.线程造成的内存泄漏
对于线程造成的内存泄漏,也是平时比较常见的,废话不说,直接上示例:
new Thread(new Runnable() {
@Override
public void run() {
SystemClock.sleep(10000);
//延时操作
}
}).start();
上面的Runnable是一个匿名内部类,所以ta对activity有一个隐式的引用, 很明显ta进行了一个延时操作,当我们在现在还没有执行完之前关闭了这个activity,也就发生了内存泄漏。
解决办法:你都能猜到的方法,依然还是使用静态内部类。示例代码就不上了
5.资源未关闭造成的内存泄漏
对于使用了BraodcastReceiver,ContentObserver,File,Cursor,Stream,Bitmap等资源的使用,应该在Activity销毁时及时关闭或者注销,否则这些资源将不会被回收,造成内存泄漏。
总结
以上是在android开发中常见的内存泄漏情形,对于生命周期不一致的问题(其实前四种都可以看作这种情况),主要是采用静态内部类,还有就是合理使用弱引用(ps:大家也可以去看看弱引用、软引用、强引用等的区别),其他具体开发中的问题还需具体分析。对于资源,一定要及时关闭