上一篇介绍了Android内存溢出,今篇我来继续介绍一下关于Android内存优化的内存泄露。
这样一方面占用了宝贵的内存空间,这样容易导致后续需要分配内存的时候,空闲空间不足而出现内存溢出OOM。所以我么要理解好内存泄露,泄露形象理解成煤气泄露,是我们不用煤气的时候,应该是关掉阀口,但是在不正常的情况中,阀口没有关好,导致煤气泄露了。
我们关键需要做到的是:及时回收没有使用的对象
需手动关闭的对象没有关闭,在try/catch/finally中的处理
onDestory()或者onPause()中未及时关闭对象
部分实例:
这些我就不一一说明了,大家或多或少都有接触到,这些都是需要我们手动关闭,属于一般内存泄露。
由于单例的静态特性使得单例的生命周期和应用的生命周期一样长,这就说明了如果一个对象已经不需要使用了,而单例对象还持有该对象的引用,那么这个对象将不能被正常回收,这就导致了内存泄漏。如下这个典例:
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;
}
}
这是一个普通的单例模式,当创建这个单例的时候,由于需要传入一个Context,所以这个Context的生命周期的长短至关重要:
1、传入的是Application的Context:这将没有任何问题,因为单例的生命周期和Application的一样长。
2、传入的是Activity的Context:当这个Context所对应的Activity退出时,由于该Context和Activity的生命周期一样长(Activity间接继承于Context),所以当前Activity退出时它的内存并不会被回收,因为单例对象持有该Activity的引用。
所以正确的单例应该修改为下面这种方式:
public class AppManager {
private static AppManager instance;
private Context context;
private AppManager(Context context) {
this.context = context.getApplicationContext();
}
public static AppManager getInstance(Context context) {
if (instance != null) {
instance = new AppManager(context);
}
return instance;
}
}
这样不管传入什么Context最终将使用Application的Context,而单例的生命周期和应用的一样长,这样就防止了内存泄漏
有时,当一个Activity经常启动,但是对应的View读取非常耗时,我们可以通过静态View变量来保持对该Activity的rootView引用。这样就可以不用每次启动Activity都去读取并渲染View了。这确实是一个提高Activity启动速度的好方法!但是要注意,一旦View attach到我们的Window上,就会持有一个Context(即Activity)的引用。而我们的View有事一个静态变量,所以导致Activity不被回收。当然了,也不是说不能使用静态View,但是在使用静态View时,需要确保在资源回收时,将静态View detach掉。
我们知道,非静态内部类持有外部类的一个引用。因此,如果我们在一个外部类中定义一个静态变量,这个静态变量是引用内部类对象。将会导致内存泄漏!因为这相当于间接导致静态引用外部类。
static InnerClass innerClass;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
innerClass = this.new InnerClass();
}
class InnerClass {
//
}
与内部类一样,匿名类也会持有外部类的引用。
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
new AsyncTask() {
@Override
protected Void doInBackground(Void... params) {
//另一个线程中持有Activity的引用,并且不释放
while (true) ;
}
}.execute();
}
在Activity中定义一下Handler类:
public class MainActivity extends Activity {
private Handler mHandler = new Handler() {
@Override
public void handleMessage(Message msg) {
//TODO
}
};
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
mHandler.sendMessageDelayed(Message.obtain(), 60000);//延迟一分钟执行
finish();
}
}
我们知道非静态内部类会持有外部类的引用,这里Activity finish后,延时消息会继续存在主线程消息队列中1分钟,然后处理消息。而该消息引用了Activity的Handler对象,然后这个Handler又引用了这个Activity。这些引用对象会保持到该消息被处理完,这样就导致该Activity对象无法被回收,从而导致了上面说的 Activity泄露。
要修改这个问题,把Handler类定义为静态,然后通过WeakReference来持有外部的Activity对象。还在onDestory()中清掉Handler消息。
public class MainActivity extends Activity {
private CustomHandler mHandler;
private static class CustomHandler extends Handler {
private WeakReference mWeakReference;
public CustomHandler(MainActivity activity) {
mWeakReference = new WeakReference(activity);
}
@Override
public void handleMessage(Message msg) {
super.handleMessage(msg);
MainActivity activity = mWeakReference.get();
if (null != activity)
switch (msg.what) {
///
}
}
}
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
mHandler = new CustomHandler(this);
}
@Override
protected void onDestroy() {
// TODO Auto-generated method stub
super.onDestroy();
mHandler.removeCallbacksAndMessages(null);
}
同Handler对象可能造成内存泄露的原理一样,Thread的生命周期不一定是和Activity生命周期一致。
而且因为Thread主要面向多任务,往往会造成大量的Thread实例。
据此,Thread对象有2个需要注意的泄漏点:
解决方案是:
TimerTask对象在和Timer的schedule()方法配合使用的时候极容易造成内存泄露。要在合适的时候进行Cancel即可。
private void cancelTimer(){
if (mTimer != null) {
mTimer.cancel();
mTimer = null;
}
if (mTimerTask != null) {
mTimerTask.cancel();
mTimerTask = null;
}
}
当我们需要使用系统服务时,比如执行某些后台任务、为硬件访问提供接口等等系统服务。我们需要把自己注册到服务的监听器中。然而,这会让服务持有 activity 的引用,如果在Activity销毁的时候没有注销这些监听器,会导致内存泄漏。
void registerListener() {
SensorManager sensorManager = (SensorManager) getSystemServic(SENSOR_SERVICE);
Sensor sensor = sensorManager.getDefaultSensor(Sensor.TYPE_ALL);
sensorManager.registerListene(this,sensor,SensorManager.SENSOR_DELAY_FASTEST);
}
然而对于中级Android工程师的面试过程中,如果你是一名老鸟,这个问题想必已经深入你的心。但是你是一个对内存模模糊糊的工程师,你的答案可能让面试官并不满意。下面的总结对你或许有点帮助。
常见的内存泄露问题
避免内存泄露的方法
还有就是如何检测内存泄露问题了。具体操作下次再详细分析