常见Android内存泄漏汇总

首先

我们我们先看一下内存泄漏与内存溢出的区别,因为这是笔试与面试常出现的问题,他们的区别,此章我将着重讲解Android中常见的内存泄漏

看了内存泄漏的定义,现在问题来了,Java中不是有垃圾回收机制吗?怎么会存在内存泄漏呢?

要想知道内存泄漏,首先我们要了解垃圾回收机制,垃圾回收就是它会选择它了解且还存活的对象为根节点,依次遍历(如何遍历——在对象中存在对下一个对象的引用),如果遍历对象完,有些对象没有遍历到,这说明该对象没有直接或者间接引用到,那就是垃圾回收机制需要回收掉的对象!但是在写代码时,可能平时没注意到,有些对象使用过后就没有了使用价值,但是某些对象依旧保留对它的直接或间接的引用,这就导致了垃圾回收无法回收该对象。无用的对象占用了内存空间,就使得实际可以使用内存变小了,这样就出现了内存泄漏!

正文

单例(static)造成的内存泄漏

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;
    }
}

上方App管理类的写法就可能会出现内存泄漏,因为其中存在一个单例,单例的生命周期和应用的一样长!

  1. 传入的是Application的Context,这是没有问题的,因为Application的生命周期就是应用的时间长短

  2. 传入的是Activity的Context,当Activity退出的时候,此时这个单例保存了对这个Activity的引用,所以Activity就不会被释放,那这个Activity上所有占内存都不会释放,这就造成了内存泄漏

所以正确的写法是(改变一下构造方法):

private AppManager(Context context) {
    this.context = contextt.getApplicationContext();
}

这样就使得所有的Context是Application的Context,防止了内存泄漏

非静态内部类创建静态实例造成的内存泄漏

平时我们可能需要重复启动一个Activity,但是为了防止重复创建相同的数据资源,我们可能存在如下写法:

public class MainActivity extends AppCompatActivity {
    private static TestResource mManager = null;
    @Override 
    Protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState); 
        setContentView(R.layout.activity_main);
        if(mManager == null){
            mManager = new TestResource();
        }
        //...
    }
    class TestResource {
        //... 
    }
}

非静态内部类默认会持有外部类的引用,然而非静态内部类创建的是一个静态实例,这个实例和应用的生命周期一样长,这就导致了静态实例一直持有该Activity的引用,导致这个Activity的资源不能正常回收,正确的做法是:

  1. 将class TestResource 改为静态内部类,也就是class static TestResource

  2. 将class TestResource抽取出来,封装成一个单独的类

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是Handler的非静态匿名内部类的实例,通过上一个的案例,我们知道mHandler持有外部类Activity的引用,我们知道消息队列是在Looper线程中不断地轮循处理消息,那么当这个Activity退出的时候,消息队列中还有未处理的消息以及真在处理的消息,Message中的mHandler持有Activity的引用,就会导致内存无法及时回收,引发内存泄漏,正确的写法如下:

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);
    }
}

将Handler匿名内部类改为静态内部类,然后对Handler持有的对象Activity采用弱引用的方式,这样回收时就能正常回收了,避免了Activity的泄漏,最后在Activity销毁时,我们调用mHandler.removeCallbacksAndMessages(null);来移除消息队列中所有的消息以及Runnable。当然也可以使用mHandler.removeCallbacks();或mHandler.removeMessages();来移除指定的Runnable和Message。

线程造成的内存泄漏

//——————test1
new AsyncTask<Void, Void, Void>() {
    @Override
    protected Void doInBackground(Void... params) {
        SystemClock.sleep(10000);
        return null;
    }
}.execute();
//——————test2 
new Thread(new Runnable() {
    @Override
    public void run() {
        SystemClock.sleep(10000);
    }
}).start();

同上,他们是匿名内部类,默认持有当前Activity的隐式引用。如果Activity退出时,这些线程中的任务没有完成,就会导致Activity的资源无法回收,造成内存泄漏。正确的做法如下:

//——————test1
static class MyAsyncTask extends AsyncTask<Void, Void, Void> {
    private WeakReference<Context> weakReference;

    public MyAsyncTask(Context context) {
        weakReference = new WeakReference<>(context);
    }

    @Override 
    protected Void doInBackground(Void... params) {
        SystemClock.sleep(10000);
        return null;
    }

    @Override
    protected void onPostExecute(Void aVoid) {
        super.onPostExecute(aVoid); 
        MainActivity activity = (MainActivity) weakReference.get();
        if (activity != null) {
            //... 
        }
    }
}
new MyAsyncTask(this).execute();

//——————test2 
static class MyRunnable implements Runnable{
    @Override
    public void run() {
        SystemClock.sleep(10000);
    }
}
//—————— 
new Thread(new MyRunnable()).start();

这样就避免了Activity的内存资源泄漏,当然在Activity销毁时候也应该取消相应的任务AsyncTask::cancel(),避免任务在后台执行浪费资源。

资源未关闭造成的内存泄漏

对于使用Broadcast Receiver、Content Provider、File、Cursor、Stream、Bitmap等资源的使用,应该在Activity退出时及时关闭这些资源,否则这些资源不会被回收,造成内存泄漏

建议

1、对于Context,如果生命周期比Activity长的对象应该使用ApplicationContext

2、在涉及到Context时先考虑ApplicationContext,当然它并不是万能的,对于有些地方则必须使用Activity的Context,对于Application,Service,Activity三者的Context的应用场景如下:

功能ApplicationServiceActivity
Start an ActivityNO1NO1YES
Show a DialogNONOYES
Layout InflationYESYESYES
Start a ServiceYESYESYES
Bind to a ServiceYESYESYES
Send a BroadcastYESYESYES
Register BroadcastReceiverYESYESYES
Load Resource ValuesYESYESYES

其中:NO1表示Application和Service可以启动一个Activity,不过需要创建一个新的task任务队列。而对于Dialog而言,只有在Activity中才能创建

3、对于需要在静态内部类中使用非静态外部成员变量(如:Context、View ),可以在静态内部类中使用弱引用来引用外部类的变量来避免内存泄漏

4、对于生命周期比Activity长的内部类对象,并且内部类中使用了外部类的成员变量,可以这样做避免内存泄漏:

  1. 将内部类改为静态内部类

  2. 静态内部类中使用弱引用来引用外部类的成员变量

5、对于不再需要使用的对象,显示的将其赋值为null,比如使用完Bitmap后先调用recycle(),再赋为null

6、保持对对象生命周期的敏感,特别注意单例、静态对象、全局性集合等的生命周期

参考资料

  1. Android内存泄漏分析及调试
  2. Android内存泄露——全解析和处理办法
  3. Android中常见的内存泄漏汇总
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值