您的位置:首页 > 移动开发 > Android开发

Android内存优化之内存泄露

2017-11-24 16:52 148 查看
转载请标明出处:http://blog.csdn.net/u013254166/article/details/78623104
本文出自: rhino博客 

       本篇主要针对常见内存泄露及优化方案做了一些整理。参考博客我已在最后注明。

一. 什么是内存泄露

       内存泄漏(Memory Leak)是指程序中己动态分配的堆内存由于某种原因程序未释放或无法释放,造成系统内存的浪费,导致程序运行速度减慢甚至系统崩溃等严重后果。

二. 内存泄露的罪魁祸首

1. 单例导致内存泄露

       单例模式在Android开发中会经常用到,但是如果使用不当就会导致内存泄露。因为单例的静态特性使得它的生命周期同应用的生命周期一样长,如果一个对象已经没有用处了,但是单例还持有它的引用,那么在整个应用程序的生命周期它都不能正常被回收,从而导致内存泄露。

public class ScreenUtils{

private static ScreenUtils sInstance;
private Context mContext;

private ScreenUtils(Context context) {
this.mContext = context;
}

public static ScreenUtils getInstance(Context context) {
if (sInstance == null) {
sInstance = new ScreenUtils(context);
}
return sInstance;
}
...
}

       像上面代码中这样的单例,如果我们在调用getInstance(Context context)方法的时候传入的context参数是Activity、Service等上下文,就会导致内存泄露。

       以Activity为例,当我们启动一个Activity,并调用getInstance(Context context)方法去获取ScreenUtils的单例,传入Activity.this作为context,当这Activity退出时,Activity应该被回收, 但是单例中又持有它的引用,导致Activity回收失败,造成内存泄漏。

       为了避免这样单例导致内存泄露,我们可以这样子修改,不管外面传入什么Context,最终都会使用Applicaton的Context,而我们单例的生命周期和应用的一样长,这样就防止了内存泄漏。
private ScreenUtils(Context context) {
this.mContext = context.getApplicationContext();
}

2. 静态变量导致内存泄露

       如在Activity中为了避免重复创建Dialog,使用静态变量,但是Dialog会只有Activity引用,导致Activity在结束后回收失败,造成内存泄漏。

public class MainActivity extends Activity {

private static Dialog mDialog;

private void showDialog(){
if (null != mDialog) {
mDialog= new Dialog(this);
...
}
mDialog.show();
}
}

       所以尽量避免这样使用静态持有的变量,或者也可以在适当的时候将静态量置为null,使其不再持有引用,这样也可以避免内存泄露。

3. 非静态内部类导致内存泄露

       非静态内部类(包括匿名内部类)默认就会持有外部类的引用,当非静态内部类对象的生命周期比外部类对象的生命周期长时,就会导致内存泄露。

(1) 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);
mHandler.sendEmptyMessage(1);
}
}

       因为mHandler会作为成员变量保存在发送的消息msg中,即msg持有mHandler的引用,而mHandler是Activity的非静态内部类实例,即mHandler持有Activity的引用,那么我们就可以理解为msg间接持有Activity的引用。msg被发送后先放到消息队列MessageQueue中,然后等待Looper的轮询处理(MessageQueue和Looper都是与线程相关联的,MessageQueue是Looper引用的成员变量,而Looper是保存在ThreadLocal中的)。那么当Activity退出后,msg可能仍然存在于消息对列MessageQueue中未处理或者正在处理,那么这样就会导致Activity无法被回收,以致发生Activity的内存泄露。

       可以采用静态内部类+弱引用的方式来规避。

public class MainActivity extends AppCompatActivity {

private Handler mHandler;

private static class MyHandler extends Handler {
private WeakReference<Activity> activityWeakReference;

public MyHandler(Activity activity) {
activityWeakReference = new WeakReference<>(activity);
}

@Override
public void handleMessage(Message msg) {
Activity activity = activityWeakReference.get();
if (null != activity) { ...}
}
}

@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
mHandler = new MyHandler(this);
mHandler.sendEmptyMessage(1);
}
}

       mHandler通过弱引用的方式持有Activity,当GC执行垃圾回收时,遇到Activity就会回收并释放所占据的内存单元。但是msg还是有可能存在消息队列MessageQueue中,在Activity销毁的时候最好清除消息。

@Override
protected void onDestroy() {
super.onDestroy();
if(null != mHandler){
mHandler.removeCallbacksAndMessages(null);
}
}

(2) Thread引起的内存泄露

public class MainActivity extends AppCompatActivity {

@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
new Thread(new Runnable() {
@Override
public void run() {
// 模拟相应耗时逻辑
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}).start();
}
}

(3) AsyncTask引起的内存泄露

public class MainActivity extends AppCompatActivity {

@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
new AsyncTask<Void, Void, Void>() {
@Override
protected Void doInBackground(Void... params) {
// 模拟相应耗时逻辑
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
return null;
}
}.execute();
}
}

       很多初学者都会像上面这样新建线程和异步任务,殊不知这样的写法非常地不友好,这种方式新建的子线程Thread和AsyncTask都是匿名内部类对象,默认就隐式的持有外部Activity的引用,导致Activity内存泄露。
       要避免内存泄露的话还是需要像上面Handler一样使用静态内部类+弱应用的方式。

4. 未取消注册或回调导致内存泄露

       比如我们在Activity中注册广播,如果在Activity销毁后不取消注册,那么这个广播会一直存在系统中,同上面所说的非静态内部类一样持有Activity引用,导致内存泄露。因此注册广播后在不用时,或者Activity销毁时一定要取消注册。

public class MainActivity extends AppCompatActivity {

@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
this.registerR
ba58
eceiver(mReceiver, new IntentFilter());
}

private BroadcastReceiver mReceiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
...
}
};

@Override
protected void onDestroy() {
super.onDestroy();
this.unregisterReceiver(mReceiver);
}
}

5. 监听器注册造成的内存泄漏

       通过Context.getSystemService(int name)可以获取系统服务。这些服务工作在各自的进程中,帮助应用处理后台任务,处理硬件交互。如果需要使用这些服务,可以注册监听器,这会导致服务持有了Context的引用,如果在Activity销毁的时候没有注销这些监听器,会导致内存泄漏。

private void init(){
SensorManager sensorManager = (SensorManager) getSystemService(SENSOR_SERVICE);
Sensor sensor = sensorManager.getDefaultSensor(Sensor.TYPE_ALL);
sensorManager.registerListener(this, sensor, SensorManager.SENSOR_DELAY_FASTEST);
}

@Override
protected void onDestroy() {
super.onDestroy();
sensorManager.unregisterListener(this);
}


6. 内部类导致内存泄漏

       如果我们在一个外部类中定义一个静态变量,这个静态变量是非静态内部类对象,这就会导致内存泄漏,因为非静态内部类会持有外部类的引用,从而间接导致静态地引用了外部类。

public class MyActivity extends Activity {
private static InnerClass mInnerClass = null;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
mInnerClass = new InnerClass();
}
class InnerClass{
}
}


       应该在onDestroy方法中手动将mInnerClass置为null,或将内部类定义为静态内部类,使其不能与外部类建立关系。

7. Timer和TimerTask导致内存泄露

       当我们Activity销毁的时,有可能Timer还在继续等待执行TimerTask,它持有Activity的引用不能被回收,因此当我们Activity销毁的时候要立即cancel掉Timer和TimerTask,以避免发生内存泄漏。

8. 集合中的对象未清理造成内存泄露

       如果一个对象放入到ArrayList、HashMap等集合中,这个集合就会持有该对象的引用。当我们不再需要这个对象时,也并没有将它从集合中移除,这样只要集合还在使用(而此对象已经无用了),这个对象就造成了内存泄露。并且如果集合被静态引用的话,集合里面那些没有用的对象更会造成内存泄露了。所以在使用集合时要及时将不用的对象从集合remove,或者clear集合,以避免内存泄漏。

9. 资源未关闭或释放导致内存泄露

       在使用IO、File流或者Sqlite、Cursor等资源时要及时关闭。这些资源在进行读写操作时通常都使用了缓冲,如果及时不关闭,这些缓冲对象就会一直被占用而得不到释放,以致发生内存泄露。因此我们在不需要使用它们的时候就及时关闭,以便缓冲能及时得到释放,从而避免内存泄露。

10. 属性动画造成内存泄露

       动画同样是一个耗时任务,比如在Activity中启动了属性动画(ObjectAnimator),但是在销毁的时候,没有调用cancle方法,虽然我们看不到动画了,但是这个动画依然会不断地播放下去,动画引用所在的控件,所在的控件引用Activity,这就造成Activity无法正常释放。因此同样要在Activity销毁的时候cancel掉属性动画,避免发生内存泄漏。

11. WebView造成内存泄露

       关于WebView的内存泄露,因为WebView在加载网页后会长期占用内存而不能被释放,因此我们在Activity销毁后,先将WebView从父容器中移除,再调用它的destory()方法来销毁它以释放内存。

参考博客:http://www.jianshu.com/p/ab4a7e353076
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签:  内存泄露 内存 优化