android中的内存泄露

前段时间公司项目一直出现莫名其妙的bug,看了日志发现有很多内存溢出的问题。引发的原因也不是很清晰,因为平时写代码并没有过于关注过内存泄露的问题,关于内存管理更多是很平常的那种,图片压缩啊什么的。后来查资料的时候看到内存泄露也可能造成内存溢出。就在项目中导入了LeakCanary,不测不知道,一侧吓一跳!卧槽,一个主页面就有5、6个内存泄露。虽然说项目中的内存泄露有很多地方,但是改过之后发现最长出现的也就那么几个,比如说Context引起的内存泄露,非静态内部类引起的内存泄露等。索性在这里总结一下,方便以后查看。

查看了好多文章,这个还是比较详细的,刚开始都是从这里边一个个对照着来的。

1.什么是内存泄露?

简单来说,内存泄露就是该被释放的资源由于被其他实例引用而不能够被回收。轻则导致卡顿,重则直接crash。

2.怎么检测是否有内存泄露?

常见的内存泄露分析工具有LeakCanary和MAT,两个都可以。LeakCanary用起来更简单点,看起来更直观。可以直接在手机上查看,而且报错信息也很明确。一些简单问题都可以直接定位的。MAT功能比LeakCanary更加强大,更全面,但是使用起来会麻烦一点,需要掌握一定的分析技巧。当然也不是很难。网上有很多的教程,感兴趣的可以看看,这里只介绍LeakCanary的使用方法。

3.LeakCanary的使用方法:

其实这不需要解释的,直接在github上搜一下,里边就有详细的使用说明,这里大体讲一下步骤好了。
1. 在项目的build.gradle的dependencies语句块中添加LeakCanary 的依赖

debugCompile 'com.squareup.leakcanary:leakcanary-android:1.5.1'
releaseCompile 'com.squareup.leakcanary:leakcanary-android-no-op:1.5.1'
testCompile 'com.squareup.leakcanary:leakcanary-android-no-op:1.5.1'

这里可以有三种模式下的依赖,可以把不需要的注视掉都可以。
2. 在Application中添加如下代码:

if (LeakCanary.isInAnalyzerProcess(this)) {
          return;
}
LeakCanary.install(this);//内存泄露检测

通过上述两个步骤直接启动项目就可以使用LeakCanary检测内存泄露了。出现内存泄露的时候,手机上会出现一个leak的图标,可以点开查看泄露的列表,以及一些详细的报错信息,根据这些就可以分析出造成内存泄露的原因和发生的位置。

3.常见的内存泄露:

0. 上下文环境Cotext乱用造成内存泄露

这里一定要搞明白ApplicationContext,context,Activity的区别:

  1. Activity是android的四大组件之一是一种具化的东西,而ApplicationContext,Context是一种抽象的概念,标识的是上下文环境。
  2. ApplicationContext的生命周期和app是一致的,而Activity的Context生命周期和Activity相同的,随着Activity的销毁,这个Context也就不复存在了。

因此,我们不能让长生命周期的对象引用Activity的Context。

比如说:如果我们在单例模式中需要传入Context,我们应该用哪个呢?我们可以分析一下,因为单例模式创建的实例是静态的,生命周期和app的生命周期是一样的,如果我们使用Activity的Context就会引起内存泄露。所以这种情况可以使用getApplicationContext。

1. 单例引起的内存泄露

原来我们的项目中创建Fragment一直都是用的单例模式,这是仿照原来同事的做法写的。开始觉得挺方便的,也没多想(毕竟经验有限)。现在一测。。。全是内存泄露。没办法,改吧!

//这是原来使用单例模式创建Fragment的代码,会造成内存泄露!
public static HomePageFragment instance;
public static HomePageFragment getInstance() {
    if (instance == null) {
        instance = new HomePageFragment();
    }
    return instance;
}

原因分析:
通过单例模式创建的实例,由于该实例是被static修饰的,所以它的生命周期是和app的生命周期一致的,而Fragment肯定是要持有Activity的实例的,如果在Fragment中有耗时操作,关闭掉页面的时候还没有执行完。就会导致Activity不能被正常销毁,从而造成内存泄露。

解决办法:
既然不能通过单例创建Fragment对象,那就改用直接创建好了。

//如果Fragment为空,就去new一个
if (mHomePageFragment == null) {
   mHomePageFragment = new HomePageFragment();
}

通过这样的创建方式,就解决了这个内存溢出的问题,其实这样的创建方式和使用单例创建唯一的区别就是,单例创建的是个static修饰的实例,就是生命周期不一样。

2. 非静态内部类创建静态实例引起的内存泄露
项目中很多地方都是用了内部类的方式来实现某些功能,常见的是通过内部类实现一些监听。

public class MainActivity extends AppCompatActivity {
    private static MemoryText mMemoryText;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        if (mMemoryText==null){
            mMemoryText=new MemoryText();
        }
    }

    class MemoryText{
        //非静态内部类会自动持有外部类对象
    }
}

因为非静态内部类会自动持有外部类对象,而mMemoryText是个静态的实例,那么他的生命周期就是app的生命周期了。因此会造成内存泄露。正确的做法为:将该内部类设为静态内部类或将该内部类抽取出来封装成一个单例,如果需要使用Context,请使用ApplicationContext 。

3. 匿名内部类引起的内存泄露

匿名内部类的写法在java或者android中时非常常见的方式,比如说在设置监听事件的时候,我们都习惯使用匿名内部类,然是由于匿名内部类会自动持有外部类的引用,如果进行耗时操作的话,会导致外部类不能正常销毁,从而引发内存泄露。

4. 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);

        initData();
    }

    private void initData() {
        mHandler.sendEmptyMessage(0);
    }
}

由于mHandler是Handler的非静态匿名内部类的实例,所以它持有外部类Activity的引用,我们知道消息队列是在一个Looper线程中不断轮询处理消息,那么当这个Activity退出时消息队列中还有未处理的消息或者正在处理消息,而消息队列中的Message持有mHandler实例的引用,mHandler又持有Activity的引用,所以导致该Activity的内存资源无法及时回收,

public class MainActivity extends AppCompatActivity {
    private TextView id_content;
    private MHanlder mHandler;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        initView();
        initData();
    }

    private void initView() {
        mHandler=new MHanlder(this);
        id_content= (TextView) findViewById(R.id.id_content);
    }

    private void initData() {
        mHandler.sendEmptyMessage(0);
    }
    //声明为静态内部类,就不会持有外部类对象了
    static class MHanlder extends Handler{
        private WeakReference<Context> mContext;
        public MHanlder(Context context) {
            //如果需要用到外部类对象,就通过弱引用的方式引入
            mContext=new WeakReference<Context>(context);
        }

        @Override
        public void handleMessage(Message msg) {
            MainActivity activity= (MainActivity) mContext.get();
            if (activity!=null){
                activity.id_content.setText(".....");
            }
        }
    }
     @Override
    protected void onDestroy() {
        super.onDestroy();
        //在回收Activity时,移除没有处理完成的对象
        mHandler.removeCallbacksAndMessages(null);
    }
}

声明一个静态Handler内部类,这样就不会持有外部类对象了,然后对Handler持有的对象使用弱引用,这样在回收时也可以回收Handler持有的对象,这样虽然避免了Activity泄漏,但是在回收Handler持有的外部类对象时,Looper 线程消息队列可能还有未处理完的对象,这里需要在onDestory中移除.

5. 资源未关闭造成的内存泄露
使用BoradcastReceiver,ContentResolver,File,Cursor,Stream,Bitmap的时候,一定要在Activity销毁的时候及时反注册或者关闭资源。否则这些资源不会被回收,从而造成内存泄露。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值