Android常见内存泄漏场景总结

时间:2024-06-10 07:24:43

一、非静态内部类造成的内存泄漏

造成原因:非静态内部类默认会持有外部类的引用,如果内部类的生命周期超过了外部类就会造成内存泄漏。

场景:当Activity销毁后,由于内部类中存在异步耗时任务还在执行,导致Activity实例一直被内部类持有无法被回收,造成内存泄漏

例如:

//TestActivity
button.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                new Thread(new Runnable() {
                    @Override
                    public void run() {
                        try {
                            //模拟耗时
                            Thread.sleep(1000);
                        } catch (InterruptedException e) {
                            throw new RuntimeException(e);
                        }
                    }
                }).start();
            }
        });

此时点击返回键关闭当前Activity,页面正常回退,但匿名内部类中还有耗时任务执行,如果耗时任务执行完之后要刷新页面还会造成异常导致app闪退。

解决办法:不使用匿名内部类,并且使用static关键字修饰内部类(static修饰的内部类不持有外部类的引用,也自然不会造成上面的内存泄漏),如果内部类中需要使用外部类的资源,可以使用弱引用的方式持有外部类。

优化后的代码如下:

button.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                new Thread(new MyRunnable()).start();
            }
        });

static class MyRunnable implements Runnable{
        @Override
        public void run() {
            try {
                //模拟耗时
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
        }
    }

二、静态成员变量造成的内存泄漏

造成原因:静态成员变量的生命周期 = 应用程序的生命周期,如果该静态成员应用的变量生命周期 < 改静态变量则会造成内存泄漏

场景:静态成员变量持有了一个耗费资源过多的实例(Activity,Fragment)

例如:

public class Person {
    private static Activity mActivity;

    Person(Activity activity){
        mActivity = activity;
    }
}

此时点击返回键关闭当前Activity,但Person类中的mActivity静态变量持有当前Activity的引用,导致改Activity对象本该被回收而没被回收,导致内存泄漏

解决办法:

  1. 尽量避免 Static 成员变量引用资源耗费过多的实例(如Context)
  2. 如果必须使用Context,可以使用Application的Context
  3. 使用弱引用代替强引用持有

三、单例模式造成的内存泄漏

造成原因:单例模式由于其具有静态特性,导致其生命周期 = 应用程序生命周期,如果单例中持有别的类的实例,就会造成内存泄漏

场景:单例模式中持有一个耗费资源过多的实例(Context)

例如:

public class SingleInstance{
    private static SingleInstance instance;
    private Context mContext;
    private SingleInstance(Context context) {
        this.mContext = context; // 传递的是Activity的context
    }
    public SingleInstance getInstance(Context context) {
        if (instance == null) {
            instance = new SingleInstance(context);
        }
        return instance;
    }
}

此时由于单例中持有传入的Activity实例,倒是该Activity关闭时,资源得不到回收,从而造成内存泄漏

解决办法:

使用Application的Context代替Activity的Contex

四、Handler造成的内存泄漏

造成原因:当使用非静态内部类(包含匿名内部类)创建Handler时,Handler会持有外部类的对象,如果Handler中还有消息没执行完,此时创建Handler的Activity关闭就会造成内存泄漏。通常由于子线程持有handler的引用(因为要发消息给handler来更新界面),handler又持有activity的引用,从而导致activity不能正常被回收,造成内存泄漏

场景:Activity中通过一个子线程异步请求网络数据,请求成功后更新当前页面。

例如:

//MainActivity.java

private Handler mHandler = new Handler(Looper.getMainLooper()){
        @Override
        public void handleMessage(Message msg) {
            //更新页面
        }
    };
 //成功获取到网络数据更新页面
 private void handleData(String data){
       Message message = Message.obtain();
       message.obj = data;
       mHandler.sendMessage(message);
}

解决办法:

1、静态内部类+弱引用

static class MyHandler extends Handler {
    WeakReference<Activity > mReference;
    MyHandler(Activity activity) {
        mReference= new WeakReference<Activity>(activity);
    }
    @Override
    public void handleMessage(Message msg) {
        final Activity activity = mReference.get();
        if (activity != null) {
            //更新页面
        }
    }
}

2、activity销毁时,及时清理消息

@Override
protected void onDestroy() {
    super.onDestroy();
    if (mHandler != null)  {
        mHandler.removeCallbacksAndMessages(null);
    }
}

五、多线程造成的内存泄漏

造成原因:上面一和四其实也是多线程造成内存泄漏的场景,主要是子线程中持有外部类(例如Activity)的引用,让后子线程的生命周期又和Activity不同步,从而造成activity被销毁时子线程中的任务还在执行,从而导致activity的资源迟迟得不到回收造成内存泄漏。

六、集合类造成的内存泄漏

造成原因:当我们把一些对象的引用加入到集合对象(例如常用的ArrayList),当我们不需要改对象时,没有从集合中清理掉改对象的引用,这样集合就会越来越大,如果集合时static的那问题就更严重了。

场景:学生管理系统中需要统计学生信息,使用一个集合存储学生信息,信息统计完成,把信息存入数据库后就不需要学生对象信息了,但是没及时清理掉集合,导致学生对象得不到回收,造成内存泄漏

例如:

// 通过 循环申请Person 对象并放入集合
List<Person> personList = new ArrayList<>();
     for (int i = 0; i < 10; i++) {
         Person p = new Person();
         personList.add(p);
// 虽释放了集合元素引用的本身:p=null
// 但集合List仍然吃药后该对象引用,所以依然不可回收该对象
         p = null;
}

解决办法:在不使用改集合时,清理集合并把集合置为空

// 释放personList
personList.clear();
personList=null;

七、WebView造成的内存泄漏

造成原因:WebView中可能会进行大量的网络请求,加载大量的资源,从而使得内存占用过高,当我们推出承载Webview的Activity时,没有正确的释放WebView中的资源,导致内存泄漏。

例如:新闻列表页面使用WebView加载一个h5页面,此时会进行大量网络请求加载新闻图片内容,当我们回退该页面时,加载的资源没正确释放导致内存泄漏

解决方案:不使用xml定义webview,通过代码的形式,传入Application的Context然后在承载webview的页面销毁时,释放webview的资源

//代码初始化Webview
mWebView=new WebView(getApplicationContext());
LinearLayout linearLayout  = findViewById(R.id.webview);
linearLayout.addView(mWebView);

@Override
protected void onDestroy() {
    if( mWebView!=null) {

        // 如果先调用destroy()方法,则会命中if (isDestroyed()) return;这一行代码,需要先onDetachedFromWindow(),再
        // destory()
        ViewParent parent = mWebView.getParent();
        if (parent != null) {
            ((ViewGroup) parent).removeView(mWebView);
        }

        mWebView.stopLoading();
        // 退出时调用此方法,移除绑定的服务,否则某些特定系统会报错
        mWebView.getSettings().setJavaScriptEnabled(false);
        mWebView.clearHistory();
        mWebView.clearView();
        mWebView.removeAllViews();
        mWebView.destroy();

    }
    super.on Destroy();
}

八、资源未释放造成的内存泄漏

造成原因:对于资源的使用(如 广播BraodcastReceiver、文件流File、数据库游标Cursor、图片资源Bitmap等),若在Activity销毁时无及时关闭 / 注销这些资源,则这些资源将不会被回收,从而造成内存泄漏

解决方案: 在Activity销毁时 及时关闭 / 注销资源

关闭资源代码如下:

// 对于 广播BraodcastReceiver:注销注册
unregisterReceiver()

// 对于 文件流File:关闭流
InputStream / OutputStream.close()

// 对于数据库游标cursor:使用后关闭游标
cursor.close()

// 对于 图片资源Bitmap:Android分配给图片的内存只有8M,若1个Bitmap对象占内存较多,当它不再被使用时,应调用recycle()回收此对象的像素所占用的内存;最后再赋为null 
Bitmap.recycle()Bitmap = null;

// 对于动画(属性动画)
// 将动画设置成无限循环播放repeatCount = “infinite”后
// 在Activity退出时记得停止动画

参考连接:WebView内存泄漏–解决方法小结 - 简书 (jianshu.com)

https://juejin.cn/post/6844904067534159880