Android基础之内存泄露

来源:互联网 发布:知乎七七网红duebass 编辑:程序博客网 时间:2024/05/19 18:16

上一篇介绍了Android内存溢出,今篇我来继续介绍一下关于Android内存优化的内存泄露。

内存泄露的基础理解

  1. 一般内存泄露的原因是:由忘记释放分配的内存导致的。(如Cursor忘记关闭等)
  2. 逻辑内存泄露的原因是:当一个对象已经不需要再使用了,本该被回收时,而有另外一个正在使用的对象持有它的引用从而导致它不能被回收,这导致本该被回收的对象不能被回收而停留在堆内存中。

这样一方面占用了宝贵的内存空间,这样容易导致后续需要分配内存的时候,空闲空间不足而出现内存溢出OOM。所以我么要理解好内存泄露,泄露形象理解成煤气泄露,是我们不用煤气的时候,应该是关掉阀口,但是在不正常的情况中,阀口没有关好,导致煤气泄露了。

Android中常见的内存泄漏汇总

我们关键需要做到的是:及时回收没有使用的对象

  • 需手动关闭的对象没有关闭,在try/catch/finally中的处理

    • HTTP
    • File
    • ContendProvider
    • Bitmap
    • Uri
    • Socket
    • Cursor
  • onDestory()或者onPause()中未及时关闭对象
    部分实例:

    • Handler泄露:我们的Handler是要求写出static的,但是实际开发中我们并没有写到,因此我们要使用弱引用和在onDestory()中removeCallbacksAndMessages(null)手动关闭。
    • 广播泄露:在手动注册广播时:需要退出的时候unregisterReceiver()。
    • Service泄露:使用Service进行某耗时操作结束后,需要手动stopself()。
    • WebView需要手动调用WebView.onPause()和WebView.destory()。
    • 常用第三方/开源框架泄露:ShareSDK、JPush、BaiduMap、ButterKnife等需要注意在对应的Activity的生命周期中关闭。

这些我就不一一说明了,大家或多或少都有接触到,这些都是需要我们手动关闭,属于一般内存泄露。

  • 然而我们可以借助LeakCanary、MAT等工具来检测应用程序是否存在以下几种情况内存泄漏。

单例造成的内存泄漏

由于单例的静态特性使得单例的生命周期和应用的生命周期一样长,这就说明了如果一个对象已经不需要使用了,而单例对象还持有该对象的引用,那么这个对象将不能被正常回收,这就导致了内存泄漏。如下这个典例:

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,而单例的生命周期和应用的一样长,这样就防止了内存泄漏

静态View

有时,当一个Activity经常启动,但是对应的View读取非常耗时,我们可以通过静态View变量来保持对该Activity的rootView引用。这样就可以不用每次启动Activity都去读取并渲染View了。这确实是一个提高Activity启动速度的好方法!但是要注意,一旦View attach到我们的Window上,就会持有一个Context(即Activity)的引用。而我们的View有事一个静态变量,所以导致Activity不被回收。当然了,也不是说不能使用静态View,但是在使用静态View时,需要确保在资源回收时,将静态View detach掉。

内部类

我们知道,非静态内部类持有外部类的一个引用。因此,如果我们在一个外部类中定义一个静态变量,这个静态变量是引用内部类对象。将会导致内存泄漏!因为这相当于间接导致静态引用外部类。

static InnerClass innerClass;@Overrideprotected void onCreate(Bundle savedInstanceState) {    super.onCreate(savedInstanceState);    setContentView(R.layout.activity_main);     innerClass = this.new InnerClass(); }class InnerClass { //}

匿名类

与内部类一样,匿名类也会持有外部类的引用。

@Overrideprotected void onCreate(Bundle savedInstanceState) {    super.onCreate(savedInstanceState);    setContentView(R.layout.activity_main);    new AsyncTask<Void, Void, Void>() {        @Override        protected Void doInBackground(Void... params) {            //另一个线程中持有Activity的引用,并且不释放            while (true) ;        }    }.execute();}

Handler类引起内存泄漏

在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<MainActivity> mWeakReference;        public CustomHandler(MainActivity activity) {            mWeakReference = new WeakReference<MainActivity>(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);    }

Thread对象引起内存泄露

同Handler对象可能造成内存泄露的原理一样,Thread的生命周期不一定是和Activity生命周期一致。
而且因为Thread主要面向多任务,往往会造成大量的Thread实例。
据此,Thread对象有2个需要注意的泄漏点:

  1. 创建过多的Thread对象
  2. Thread对象在Activity退出后依然在后台执行

解决方案是:

  1. 使用ThreadPoolExecutor,在同时做很多异步事件的时候是很常用的,这个不细说。
  2. 当Activity退出的时候,退出Thread。

TimerTask引起泄露

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工程师的面试过程中,如果你是一名老鸟,这个问题想必已经深入你的心。但是你是一个对内存模模糊糊的工程师,你的答案可能让面试官并不满意。下面的总结对你或许有点帮助。

  • 常见的内存泄露问题

    1. 单例造成的泄露。
    2. 静态View造成的泄露。
    3. 内部类持有外部类实例的强引用。
    4. Handler、Thread耗时操作劫持Activity的外部类。
    5. TimerTask对象没有及时回收和置空。
    6. 系统监听器的引起的泄露。
  • 避免内存泄露的方法

    1. 注意Context上下文的使用。
    2. 谨慎使用static。
    3. 使用静态内部类来代替内部类。
    4. 静态内部类使用弱引用WeakReference来引用外部类。
    5. 在声明周期结束的时候释放资源。
    6. 优化布局文件,减少层级关系。
    7. 谨慎使用第三方框架。
    8. 谨慎使用多线程。
    9. 横竖屏切换引起的GC。
    10. 避免创建不必要的对象。

还有就是如何检测内存泄露问题了。具体操作下次再详细分析

  1. 善用Android Studio的标签Memory。
  2. LeakCanary的使用。
  3. MAT的使用。

这里写图片描述

0 0
原创粉丝点击