Android总结:内存泄露

一、内存泄露简介
1.什么是内存泄露?

内存泄露对于我们来说是不可见的,它是出现在堆内存中,我们可以通过LeakCanary,MAT等工具来检测程序中是否存在内存泄露。
2.内存泄露什么时候发生?
当一个对象已经不需要在使用了,本该被回收的时候,而又有另外一个正在使用的对象持有它的引用从而导致它不被回收,这就产生了内存泄露。
3.内存泄露有什么坏处?
内存泄露是产生OOM的主要原因之一,Android系统为每个应用程序分配有限的内存,当内存泄露发生的较多的时候,就难免会造成内存溢出导致应用Crash。
二、常见的内存泄露和处理办法
为了让大家能直观的直观的感受到内存泄露它的存在,下面我们使用leakcanary工具,给大家展示几种常见的内存泄露类型(这里我们基于《内存泄露检测神器—LeakCanary》章节集成LeakCanary后的代码,继续演示。对LeakCanary工具和接入等不太了解的同学,可以到相关章节进行学习)。
1.单例造成的内存泄露
简要说明

单例的静态成员特性使得单例对象和应用的生命周期一样长,如果对象持有了一个已经不需要使用的对象,那么该对象就不能被正常回收而导致内存泄露。
代码实例
summary\src\main\java\com\qandroid\summary\memoryleak\SingletonManager.java
public class SingletonManager {
    //单例的静态成员生命周期和应用一样长
    private static SingletonManager instance;
    //它持有的成员,Activity的上下文在Activity关闭的时候无法回收,故造成了内存泄露
    private Context context;

    private SingletonManager(Context context) {
        this.context = context;
    }

    public static SingletonManager getInstance(Context context) {
        if (instance == null) {
            instance = new SingletonManager(context);
        }
        return instance;
    }
}
summary\src\main\java\com\qandroid\summary\memoryleak\SingletonActivity.java
public class SingletonActivity extends AppCompatActivity {
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_singleton);
        //将SingletonActivity上下文,传递给单例成员持有,在Activity退出时无法被回收
        SingletonManager singletonManager = SingletonManager.getInstance(this);
    }
}
运行日志

处理建议 

在涉及到Context时先考虑ApplicationContext,当然它并不是万能的,对于有些地方则必须使用Activity的Context。这样就没有问题,因为它的生命周期也是和应用一样长。
public class SingletonManager {
    ... ... 
    public static SingletonManager getInstance(Context context) {
        if (instance == null) {
            //在涉及到使用Context的地方,优先使用ApplicationContext。但是要视具体情况,有些地方则必须使用Activity的上下文
            instance = new SingletonManager(context.getApplicationContext());
        }
        return instance;
    }
}
2.非静态内部类创建静态实例造成的内存泄露
简要说明

非静态内部类里面可以直接访问外部类的private成员,这是因为在非静态内部类对象里,保存了一个它寄存在外部类对象的引用。而这个时候如果你有使用了该内部类创建了一个静态的实例,那么该实例的生命周期就和应用一样长,导致该静态实例会间接持有Activity的引用,导致Activity页面退出的时候,无法被回收从而造成内存泄露。
代码实例
summary\src\main\java\com\qandroid\summary\memoryleak\InnerClassActivity.java
public class InnerClassActivity extends AppCompatActivity {
    //使用静态成员,使得资源在此Activity中仅有一份
    private static ResourceClass resourceClass;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_innerclass);

        //在该资源在内存中不存在的情况下,在构造获取资源,避免资源内存的重复分配
        if (resourceClass == null) {
            //但是该资源未非静态内部类,它的实例隐式的含有外部类InnerClassActivity的引用,当Activity退出的时候,该Activity无法被回收导致内存泄露
            resourceClass = new ResourceClass();
        }
    }

    //保存资源的内部类
    class ResourceClass {
         ... ... 
    }
}
运行日志

处理建议

将该内部类设置为静态内部类,或者抽出来封装成一个单例
public class InnerClassActivity extends AppCompatActivity {
    ... ... 
    //修改为静态的内部类
    static class ResourceClass {
         ... ... 
    }
}
3.Thread或者AsyncTask匿名内部或非静态实例对象实现造成的内存泄露
简要说明

在日常开发过程中,我们经常使用如下匿名内部类的形式实现简单的线程或者异步任务。由于匿名内部类内可以访问外部类的成员,同样包含外部类的殷实引用。当Activity页面退出后,线程和异步任务还未完成时,Activity无法被回收而导致内存泄漏。
代码实例
summary\src\main\java\com\qandroid\summary\memoryleak\ThreadAsyncActivity.java
public class ThreadAsyncActivity extends AppCompatActivity {
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_threadasync);

        //匿名内部类实例,包含ThreadAsyncActivity隐式的引用
        new Thread(new Runnable() {
            @Override
            public void run() {
                try {
                    //模拟异步任务执行,在ThreadAsyncActivity退出后还会继续执行,导致Activity无法被回收
                    Thread.sleep(100000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }).start();

        //非静态内部类实例对象,包含ThreadAsyncActivity隐式的引用
        new InnerAsyncTask().execute();
    }

    //非静态内部类实现异步任务
    class InnerAsyncTask extends AsyncTask<Void, Void, Void> {
        @Override
        protected Void doInBackground(Void... voids) {
            try {
                //模拟异步任务执行,在ThreadAsyncActivity退出后还会继续执行,导致Activity无法被回收
                Thread.sleep(100000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            return null;
        }
    }
}
运行日志
           
处理建议
将线程类的实现,使用静态的内部类,这样就不会导致Activity内存无法被回收。在Activity销毁的时候取消未完成的任务执行,避免任务在后台执行浪费资源。
public class ThreadAsyncActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_threadasync);

        new Thread(new MyRunnable()).start();
        new InnerAsyncTask().execute();
    }

    //使用静态内部类,避免对外部类ThreadAsyncActivity实例的引用
    static class InnerAsyncTask extends AsyncTask<Void, Void, Void> {

        @Override
        protected Void doInBackground(Void... voids) {
            try {
                Thread.sleep(100000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            return null;
        }
    }

    //使用静态内部类,避免对外部类ThreadAsyncActivity实例的引用
    static class MyRunnable implements Runnable {
        @Override
        public void run() {
            try {
                Thread.sleep(100000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}
4.Handler匿名内部类实现造成的内存泄露
简要说明

匿名内部类的方式创建Handler的实例可能会造成内存的泄露。因为它持有外部类HandlerActivity的引用,当在Activity页面退出的时候,消息队列中还有未处理的消息或者正在处理的消息,而消息队列中的Messager持有Handler实例的引用,间接持有Activity页面的引用,无法被回收造成内存泄露。
代码实例
QAndroid\summary\src\main\java\com\qandroid\summary\memoryleak\HandlerActivity.java
public class HandlerActivity extends AppCompatActivity {
    private TextView textView;

    //使用非静态匿名内部类实例创建Handler对象,持有HandlerActivity的隐式引用
    private Handler handler = new Handler(){
        @Override
        public void handleMessage(Message msg) {
            super.handleMessage(msg);
            if(msg.what == 1){
                Log.i("HandlerActivity","receive sendMessage");
                textView.setText((CharSequence) msg.obj);
            }
        }
    };

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_handler);
        textView = (TextView) findViewById(R.id.textView1);


        Log.i("HandlerActivity","start loaddata");
        new LoadDataTread().start();
    }

    class LoadDataTread extends Thread{
        @Override
        public void run() {
            try {
                //延迟1秒钟,模拟异步加载数据
                Thread.sleep(1000);
                Log.i("HandlerActivity","done loaddata");
            } catch (InterruptedException e) {
                e.printStackTrace();
            }

            //处理完网络请求,延迟10秒发送消息,并退出HandlerActivity页面,此时消息队列还有消息没有处
            //理或正在处理,而消息队列的message持有handler对象的引用,hander又持有Activity的引用,故造成了Activity无法被回收的内存泄露
            Log.i("HandlerActivity","start sendMessage");
            Message message = new Message();
            message.what = 1;
            message.obj = "receive data";
            handler.sendMessageDelayed(message,10000);
        }
    }
}
运行日志

处理建议

将Handler修改为静态的内部类,并且使用弱引用持有当前Activity的上下文,这样就可以避免Activity不能被回收造成的内存泄露。但是当Looper线程的消息队列中还是可能又未处理的消息,故在Activity的onStop或者onDestroy中移除未处理的消息。
public class HandlerActivity extends AppCompatActivity {
    private TextView textView;
    private MyHander myHander = new MyHander(this);

    //创建静态的内部类,不会有对HandlerActivity实例的隐式引用,防止Activity退出后,消息为处理完Activity无法回收
    private static class MyHander extends Handler{
        //使用弱引用持有上下文,这样即使消息未处理完也可以回收Activity,避免内存泄露
        private WeakReference<Context> contextWeakReference;

        public MyHander(Context context){
            contextWeakReference = new WeakReference<Context>(context);
        }

        @Override
        public void handleMessage(Message msg) {
            super.handleMessage(msg);
            if(msg.what == 1){
                Log.i("HandlerActivity","receive sendMessage");
                //从弱引用中获取上下文,更新视图对象
                HandlerActivity activity = (HandlerActivity) contextWeakReference.get();
                if(activity != null){
                    activity.textView.setText((CharSequence) msg.obj);
                }
            }
        }
    }

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_handler);
        textView = (TextView) findViewById(R.id.textView1);


        Log.i("HandlerActivity","start loaddata");
        new LoadDataTread().start();
    }

    @Override
    protected void onStop() {
        super.onStop();
        //在Activity退出页面将要被销毁的时候哦,移除消息队列的所有消息和Runnable,防止内存泄露
        myHander.removeCallbacksAndMessages(null);
    }

    class LoadDataTread extends Thread{
        @Override
        public void run() {
            try {
                //延迟1秒钟,模拟异步加载数据
                Thread.sleep(1000);
                Log.i("HandlerActivity","done loaddata");
            } catch (InterruptedException e) {
                e.printStackTrace();
            }


            //处理完网络请求,延迟10秒发送消息,并退出HandlerActivity页面,此时消息队列还有消息没有处
            //理或正在处理,而消息队列的message持有handler对象的引用,hander又持有Activity的引用,故造成了Activity无法被回收的内存泄露
            Log.i("HandlerActivity","start sendMessage");
            Message message = new Message();
            message.what = 1;
            message.obj = "receive data";
            myHander.sendMessageDelayed(message,10000);
        }
    }
}
5.资源未关闭造成的内存泄露
对于使用了BraodcastReceiver,ContentObserver,File,Cursor,Stream,Bitmap等资源的使用,应该在Activity销毁时及时关闭或者注销,否则这些资源将不会被回收,造成内存泄漏。
三、总结建议
1.编码总结

日常编写代码的时候,要保持对静态成员,非静态内部类实例,匿名内部和单例、上下文对象等的敏感性,要特别留意是否可能存在内存泄露情况。
2.编码建议
涉及到Context的时候,要优先考虑ApplicationContext,当然某些情况也是不行的要视具体情况而论(如启动一个Activity或Service,显示一个Dialog)。
对于需要在静态内部类中使用外部类成员变量(如:Context,View),可以在静态内部类中使用弱引用来引用外部类变量来避免内存泄露。
四、代码库

QAndroid:https://github.com/QAndroid/QAndroid  分支:summary/memoryleak

新技术,新未来!欢迎大家关注 “1024工场”微信服务号 ,时刻关注我们的最新的技术讯息! (甭客气!尽情的扫描或者长按!)

  • 2
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值