Android内存泄漏

一.内存泄漏。

1.什么是android内存泄露? 

  内存泄露是指一个无用对象持续占有内存或无用对象的内存得不到及时的释放,从而造成的内存控件的浪费称为内存泄露。也就是说一个你不需要的对象竟然还占着内存,还不释放,GC也不顶用。

2.内存泄露会导致什么后果?

   内存泄露最终就将会导致内存溢出。App崩溃。

二.Android中常见的内存泄漏.

1.单例导致内存泄露。

单例的静态特性使得它的生命周期同应用的生命周期一样长,如果一个对象已经没有用处了,但是单例还持有它的引用,那么在整个应用程序的生命周期它都不能正常被回收,从而导致内存泄露。

public class Singleton {
    private static Singleton singleton = null;
    private Context mContext;

    public Singleton(Context mContext) {
        this.mContext = mContext;
    }

    public static Singleton getSingleton(Context context){
        if (null == singleton){
            singleton = new Singleton(context);
        }
        return singleton;
    }
}

如果我们在调用getInstance(Context context)方法的时候传入的context参数是Activity、Service等上下文,就会导致内存泄露。当我们退出Activity时,该Activity就没有用了,但是因为singleton作为静态单例(在应用程序的整个生命周期中存在)会继续持有这个Activity的引用,导致这个Activity对象无法被回收释放,这就造成了内存泄露。

为了避免这样单例导致内存泄露,我们可以将context参数改为全局的上下文:

public Singleton(Context mContext) {
        this.mContext = mContext.getApplicationContext();
    }

全局的上下文Application Context就是应用程序的上下文,和单例的生命周期一样长,这样就避免了内存泄漏。单例模式对应应用程序的生命周期,所以我们在构造单例的时候尽量避免使用Activity的上下文,而是使用Application的上下文。

2. 非静态内部类、匿名内部类

非静态内部类: 成员内部类, 局部内部类、 匿名内部类。 都会持有外部类的一个引用。这样内部类中耗时操作在用户频繁退出重启APP相关Activity时很容易导致内存泄漏。

protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        test();
    }
private void test() {
        new Thread(new Runnable() {
            @Override
            public void run() {
                while (true) {
                    try {
                        Thread.sleep(1000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }
        }).start();
    }

解决办法: 
将非静态内部类、匿名内部类 改成静态内部类,或者直接抽离成一个外部类。 
 

 protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        test();
    }

    private void test() {
       new Thread(new MyRunnable());
    }
    private static class MyRunnable implements Runnable {

        @Override
        public void run() {
            try {
                Thread.sleep(15000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }

如果在静态内部类中,需要引用外部类对象,那么可以将这个引用封装在一个WeakReference中。

private Handler handler;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        //创建Handler
        handler = new MyHandler(this);
    }

    //避免Handler引用activity造成的内存泄漏:使用静态内部类+ 使用弱引用
    private static class MyHandler extends Handler {
        WeakReference<Activity> weakReference;

        MyHandler(Activity activity) {
            weakReference = new WeakReference<>(activity);
        }

        @Override
        public void handleMessage(Message msg) {
            super.handleMessage(msg);
            if (weakReference.get() != null) {
                Toast.makeText(weakReference.get(), "测试", Toast.LENGTH_LONG).show();
            }
        }
    }

    @Override
    protected void onDestroy() {
        super.onDestroy();
        //如果参数为null的话,会将所有的Callbacks和Messages全部清除掉。
        handler.removeCallbacksAndMessages(null);
    }

 

3.未取消注册或回调导致内存泄露。

(1.)Activity中注册广播,如果在Activity销毁后不取消注册,那么这个广播会一直存在系统中,导致内存泄露。因此注册广播后在Activity销毁后一定要取消注册。

public class MainActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        this.registerReceiver(mReceiver, new IntentFilter());
    }

    private BroadcastReceiver mReceiver = new BroadcastReceiver() {
        @Override
        public void onReceive(Context context, Intent intent) {
            // 接收到广播需要做的逻辑
        }
    };

    @Override
    protected void onDestroy() {
        super.onDestroy();
        this.unregisterReceiver(mReceiver);
    }
}

(2.)Activity中使用EventBus的时候也需要在activity关闭的时候取消。

@Override
protected void onCreate(Bundle savedInstanceState) {           
     super.onCreate(savedInstanceState);
     setContentView(R.layout.activity_main);
     EventBus.getDefault().register(this);
} 
@Override
protected void onDestroy() {
    super.onDestroy();
    EventBus.getDefault().unregister(this);
}

(3.)在监听器中。持有 activity 的引用,如果没有在 activity 销毁时取消注册,那就会导致 activity 泄漏了。 

@Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        textWatcher = new TextWatcher() {
            @Override
            public void beforeTextChanged(CharSequence s, int start, int count, int after) {

            }

            @Override
            public void onTextChanged(CharSequence s, int start, int before, int count) {

            }

            @Override
            public void afterTextChanged(Editable s) {

            }
        };
        editText.addTextChangedListener(textWatcher);
    }

    @Override
    protected void onDestroy() {
        super.onDestroy();
        editText.removeTextChangedListener(textWatcher);
    }

(4.) 资源对象没关闭造成内存泄漏

在使用IO、File流或者Sqlite、Cursor等资源时要及时关闭。这些资源在进行读写操作时通常都使用了缓冲,如果及时不关闭,这些缓冲对象就会一直被占用而得不到释放,以致发生内存泄露。因此我们在不需要使用它们的时候就及时关闭,以便缓冲能及时得到释放,从而避免内存泄露。

(5.)属性动画造成内存泄露。

动画同样是一个耗时任务,比如在Activity中启动了属性动画(ObjectAnimator),但是在销毁的时候,没有调用cancle方法,虽然我们看不到动画了,但是这个动画依然会不断地播放下去,动画引用所在的控件,所在的控件引用Activity,这就造成Activity无法正常释放。因此同样要在Activity销毁的时候cancel掉属性动画,避免发生内存泄漏。
 

@Override
protected void onDestroy() {
    super.onDestroy();
    mAnimator.cancel();
}

三.leakcannay的集成和使用

  • 在module的build.gradle里面配置如下:
 debugCompile 'com.squareup.leakcanary:leakcanary-android:x.x'
    releaseCompile 'com.squareup.leakcanary:leakcanary-android-no-op:x.x'
    testCompile 'com.squareup.leakcanary:leakcanary-android-no-op:x.x'
  • 在Baseapplication里面进行初始化:
 LeakCanary.install(this);
  • 进行测试.
public class LeakTest {
    private Context mCtx;
    private TextView mTextView;
    private static LeakTest ourInstance = null;
    public static LeakTest getInstance(Context context) {
        if (ourInstance == null) {
            ourInstance = new LeakTest(context);
        }
        return ourInstance;
    }
    public void setRetainedTextView(TextView tv) {
        this.mTextView = tv;
        mTextView.setText(mCtx.getString(android.R.string.ok));
    }
    private LeakTest() {
    }
    private LeakTest(Context context) {
        this.mCtx = context;
    }
}

然后在activity里面进行使用

LeakTest.getInstance(this).setRetainedTextView(mTxtMain);
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值