Android基础之内存泄露

本文深入探讨Android中的内存泄露问题,包括内存泄露的概念、常见原因及其解决办法。文章列举了多种可能导致内存泄露的情况,如单例模式不当使用、静态View持有Activity引用等,并提供了预防措施。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

上一篇介绍了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;

@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<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的使用。

这里写图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值