Android内存泄漏相关

一、概念

内存泄漏是指一个不再被使用的对象被一个还存活着的对象引用,此时垃圾回收器会跳过它,不去回收它。比如常见的Activity泄漏,Activity组件在Android中扮演着重要的作用,如果它泄漏,很可能导致Activity所引用的其他对象也泄漏。Activity在生命周期中,因为某些原因(配置改变或手机内存不足)可能会被多次销毁再创建,如果每个泄漏的Activity都驻留在内存中会导致可用的堆内存越来越小,堆内存的变小更会引发GC的频繁调度,程序的性能可想而知。

二、准备

这里我采用 LeakCanary来检测Activity的泄漏,LeakCanary使用很简单,分两步。

第一步:build.gradle

dependencies {
    ...
    debugImplementation 'com.squareup.leakcanary:leakcanary-android:1.5.4'
    releaseImplementation 'com.squareup.leakcanary:leakcanary-android-no-op:1.5.4'
}

第二步:在自己的Application中

public class MyApplication extends Application {
    @Override
    public void onCreate() {
        super.onCreate();
        if (LeakCanary.isInAnalyzerProcess(this)) {//1
            // This process is dedicated to LeakCanary for heap analysis.
            // You should not init your app in this process.
            return;
        }
        LeakCanary.install(this);
    }
}

这要就搞定了。

二、常见的泄漏原因

1、Toast工具类的错误使用

很多时候我们在写Toast工具类时,可能是这样写的。

public class ToastUtil {
    private static Toast toast;
    public static void show(Context context,String text){
        if (toast==null){
            toast=Toast.makeText(context,text,Toast.LENGTH_SHORT);
            }
        else {
            toast.setText(text);
        }
        toast.show();
    }
}

这样写必然会带来Activity的泄漏。这里我们尝试着点击Button让他弹出土司,并将Activity返回。注意必须将Activity返回,这样LeakCanary才能检测出是否有Activity泄漏产生。不一会就可以发现在通知栏出现。

 

 

很明显MainActivity泄漏了1,8kb点击进去查看具体细节。

Toast的引用是静态的因此Toast对象会一直存在内存中,而Toast对象又持有Activity的引用就是Toast.makeText(context,text,Toast.LENGTH_SHORT)中的context,这就导致即使我们返回了Activity而Toast一直持有Activity的引用,垃圾收集器就会跳过Activity不去回收它。

补充:判断一个对象是否可以被回收主要是看它和GC Roots之间是否存在引用链,如果有则该对象是可达的,系统不会回收它。反之,若是不可达则会回收。

解决方法:

解决办法就是我们让Toast持有应用上下文,而不是Activity。这样当Activity被返回就没有存活着的对象引用它,自然可以被回收

public class ToastUtil {
    private static Toast toast;
    public static void show(Context context,String text){
        if (toast==null){
            toast=Toast.makeText(context.getApplicationContext(),text,Toast.LENGTH_SHORT);
            }
        else {
            toast.setText(text);
        }
        toast.show();
    }
}

2、非静态内部类的错误使用

很时候我们会用到内部类,因为它可以访问到外部类的成员,非常方便。但是我们需要知道,其实非静态内部类是具有外部类的一个引用。我们可能像下面这样写代码。

public class MainActivity extends AppCompatActivity {

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

        new MyThread().start();

    }

    class MyThread extends Thread{
        @Override
        public void run() {
            //这里模拟处理一些耗时任务
            try {
                Thread.sleep(20000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }

}

当线程中处理一些耗时的任务,还没结束,我们就退出Activity了,这时由于MyThread还在执行,并且具有外部类MainActivity的一个引用就会导致MainActivity无法被回收,从而产生泄漏。LeakCanary检测如下:可以看到只是一个简单的Activity就有可能泄漏较大的内存。

解决方法:

可以采用静态内部类的方式,静态内部类不会持有外部类的引用。因此只要给MyThread加上static关键字就行了。

3、单例的错误使用

public class Singleton {
    private static Singleton singleton;
    private Callback callback;
    private Singleton(){}
    public static Singleton getInstance(){
        if (singleton==null){
            synchronized (Singleton.class){
                if (singleton==null){
                    singleton=new Singleton();
                }
            }
        }
        return singleton;
    }
    public void setCallback(Callback callback){
        this.callback=callback;
    }

    interface Callback{
        void callback();
    }
}

然后再MainActivity实现Callback接口

public class Main2Activity extends AppCompatActivity implements Singleton.Callback{
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main2);

        Singleton.getInstance().setCallback(this);
    }

    @Override
    public void callback() {
        //其他操作
    }
}

这样写也会导致Activity的内存泄漏

原因很简单,单例的生命周期和应用程序是一致的,这会导致它一直驻留在内存中,而该单例持有的Callback的引用,肯定就导致Callback对象无法被垃圾回收器回收。

解决方法:

解决方法很简单可以结合弱引用来实现,不了解弱引用的可以看Java的四种引用。Singleton修改如下

public class Singleton {
    private static Singleton singleton;
    private WeakReference<Callback> callback;
    private Singleton(){}
    public static Singleton getInstance(){
        if (singleton==null){
            synchronized (Singleton.class){
                if (singleton==null){
                    singleton=new Singleton();
                }
            }
        }
        return singleton;
    }

    public void setCallback(Callback c){
        this.callback=new WeakReference<>(c);
    }

    interface Callback{
        void callback();
    }
}

当Activity返回之后,Activity就只有弱引用了,当垃圾收集器只要扫描到具有弱引用的对象,不管内存是否充足,都会回收该对象,因此弱引用不能让对象豁免被垃圾收集器回收的可能。

4、匿名内部类的错误使用

对于上面单例产生的泄漏,也许有人不用弱引用,也不让Activity实现Callback,而是直接new Callback(),如下:

@Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main2);
        Singleton.getInstance().setCallback(new Singleton.Callback() {
            @Override
            public void callback() {
                //其他操作
            }
        });
    }

这样看起来好像单例不再持有Main2Activity的引用了,其实不然。匿名内部类持有当前外部类的一个隐式引用,而匿名内部类又被单例所持有,这任然是一个引用链,GC Roots到Main2Activity对象之间任然可达,因此GC还是不会回收,内存泄漏依旧存在。

5、Handler的错误引用

public class Main2Activity extends AppCompatActivity {

    private Handler handler=new Handler();
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main2);


        handler.postDelayed(new Runnable() {
            @Override
            public void run() {
                //其他操作
            }
        },10000);
    }

}

上面代码也会产生内存泄漏。原因:我们把任务发送出去并且延迟10s执行,在任务还没结束时我们退出了Activity,但是由于匿名内部类Runnable持有外部类的引用,而Runnable被Message所持有,Message被MessageQueue所持有。这是一条完整的引用链,消息队列一直存活于内存,因此Main2Activity到GC Roots之间还是可达的,GC不会回收它。这样就导致了内存泄漏。

解决方法:可以将Runnable直接申明为static这样就不会持有外部类Main2Activity的引用了。

public class Main2Activity extends AppCompatActivity {

    private Handler handler=new Handler();
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main2);

        handler.postDelayed(runnable,10000);
    }

    static Runnable runnable=new Runnable() {
        @Override
        public void run() {
            //其他操作
        }
    };
}

此外我们也可以这样写:在Activity被销毁时,移除所有的消息。这样也能防止内存泄漏。

public class Main2Activity extends AppCompatActivity {

    private Handler handler=new Handler();
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main2);

        handler.postDelayed(new Runnable() {
            @Override
            public void run() {
                //其他操作
            }
        },10000);
    }

    @Override
    protected void onDestroy() {
        super.onDestroy();
        //在Activity销毁时移除队列中所有的消息
        handler.removeCallbacksAndMessages(null);
    }
}

6、静态字段导致的内存泄漏

public class Main2Activity extends AppCompatActivity {

    private static TextView textView;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main2);

        textView= (TextView) findViewById(R.id.textView);
    }

}

这个代码看起来很简单,但它一样导致了内存泄漏。因为TextView被申明为静态的,并且TextView在实例化时持有了Main2Activity对象。

 ...
public TextView(Context context) {
        this(context, null);
    }
   
public TextView(Context context, @Nullable AttributeSet attrs) {
        this(context, attrs, com.android.internal.R.attr.textViewStyle);
    }

public TextView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
        this(context, attrs, defStyleAttr, 0);
    }
...

虽然还有很多导致内存泄漏的错误代码,其实原因都是类似的:直白点说就是一个不再被使用的对象被一个还存活着的对象引用,此时垃圾回收器会跳过它,不去回收它。不直白的说,是因为Java采用可达性分析算法来判断一个对象是否存活,从起始点也就是一系列的GC Roots向下搜索,搜索走过的路径称为引用链,当一个对象到GC Roots之间有引用链相连是可达的,那么就证明这个对象是有用的GC不会回收它,反之没有引用链、不可达就认为对象是不可用的,GC会回收它。

大四狗,知识浅薄。如有错误,欢迎指正。

参考资料:深入理解Java虚拟机、Android高性能编程。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值