当前位置:   article > 正文

android 内存泄露原因以及排查和解决方案_android sutido 内存泄漏 2023

android sutido 内存泄漏 2023

Android内存泄露,大家在开发中一般都会遇到,特别是在快要发版的时候,各种泄露的问题都在等待解决。
为什么会产生内存泄漏?

当一个对象已经不需要再使用了,本该被回收时,而有另外一个正在使用的对象持有它的引用从而导致它不能被回收,这导致本该被回收的对象不能被回收而停留在堆内存中,这就产生了内存泄漏。

内存泄漏对程序的影响?

内存泄漏是造成应用程序OOM的主要原因之一!我们知道Android系统为每个应用程序分配的内存有限,而当一个应用中产生的内存泄漏比较多时,这就难免会导致应用所需要的内存超过这个系统分配的内存限额,这就造成了内存溢出而导致应用Crash。

Android中常见的内存泄露

1非静态内部类以及匿名内部类持有对外部类的引用。
Android非静态内部类造成的原因分析 该文章详细讲解了非静态内部类造成泄露的原因,以及内部类为什么会持有对外部类的引用。

Activity 泄漏

我们第一个需要修复的问题就是 Activity 泄漏,我们先来看看内存泄漏是怎么发生的。 Activity 泄漏通常是内存泄漏的一种。为什么会泄漏呢?如果你持有一个未使用的 Activity 的引用,其实也就持有了 Activity 的布局,自然也就包含了所有的 View。最棘手的是持有静态引用。别忘了,Activity 和 Fragment 都有自己的生命周期。一旦我们持有了静态引用,Activity 和 Fragment 就不会被垃圾回收器清理掉了。这就是为什么静态引用很危险。

m_staticActivity = staticFragment.getActivity()
我看过太多次这样的代码了。

另外,泄漏 Listener 也是经常会发生的事情。比如说,我有下面的代码。LeakActivity继承自 Activity,我们有一个单例:NastyManager,当我们通过 addListener(this) 将 Activity 作为 Listener 和 NastyManager 绑定起来的时候,不好的事情就发生了。

public class LeakActivity extends Activity {
  @Override
  protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    NastyManager.getInstance().addListener(this);
  }
}
想要修复这样的 Bug,其实相当简单,就是在你的 Acitivity 被销毁的时候,将他和 NastyManager 取消掉绑定就好了。

@Override
public void onDestroy() {
  super.onDestroy();

  NastyManager.getInstance().removeListener(this);
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15

相对上面的解决方案,我们自然还有更好的。比如我们真的需要用到单例吗?通常,并不需要。不过某些时候可能真的很需要。我们得权衡和设计。不过无论如何,记住,当 Activity 销毁的时候,在单例中移除掉对 Activity 的引用。下面我们讨论下: 如果是内部类,会发生什么?比如说,我们有一个在 Activity 里有一个很简短的非静态 Handler。

尽管它看起来很短,但是只要它还存活着,那么包含它的 Activity 就会存活着。如果你不信我,在 VM 里试试看。这就是另一个内存泄漏的案例:Activity 内部的 Handler。

public class MainActivity extends Activity {
  //...
  Handler handler;
  @Override
  protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    //...
    handler = new Handler() {
      @Override
      public void handleMessage(Message msg) {
              }
  }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13

Handler 是个很常用也很有用的类,异步,线程安全等等。如果有下面这样的代码,会发生什么呢?handler.postDeslayed ,假设 delay 时间是几个小时… 这意味着什么?意味着只要 handler 的消息还没有被处理结束,它就一直存活着,包含它的 Activity 就跟着活着。我们来想办法修复它,修复的方案是WeakReference,也就是所谓的弱引用。垃圾回收器在回收的时候,是会忽视掉弱引用的,所以包含它的 Activity 会被正常清理掉。大概代码如下:

private static class MyHandler extends Handler { 
  private final WeakReference<MainActivity> mActivity; 
  // ...
  public MyHandler(MainActivity activity) {
    mActivity = new WeakReference<MainActivity>(activity);
    //... 
  }

  @Override
  public void handleMessage(Message msg) { 
  }
  //...
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13

概括来说:我们有个内部类,就像 Handler,内部非静态类是不能脱离所属类而单独存活的,Android 里通常是 Activity。所以,看看你的代码里的内部类,确保他们没有出现内存泄漏。

相比非静态内部类,最好使用静态内部类。区别就是静态内部类不依赖所属类,他们拥有不同的生命周期。我经常见到类似的原因引起的内存泄露。

如何避免 Activity 泄漏?

移除掉所有的静态引用。

考虑用 EventBus 来解耦 Listener。

记着在不需要的时候,解除 Listener 的绑定。

尽量用静态内部类。

做 Code Review。个人经验:Code Review 能很早的发现内存泄漏。

了解你程序的结构。

用类似 MAT,Eclipse Analyzer,LeakCanary 这样的工具分析内存。

在 Callback 里打印 Log。
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15

资源未关闭造成的内存泄漏

对于使用了BraodcastReceiver,ContentObserver,File,Cursor,Stream,Bitmap等资源的使用,应该在Activity销毁时及时关闭或者注销,否则这些资源将不会被回收,造成内存泄漏。

内存发生了,如何排查和解决?

一、通过一些开源的库来发现如leakcanary
二. 通过adb shell dumpsys meminfo和ddms来确定泄露源,这种方法更精确,一般用于第一种方法发现具体的可疑Activity泄露,然后再通过这种方法再精确定位问题;

举个例子来说明下这个具体流程:
1 点开自己的App 然后打开ddms,找到对应的进程com.renlei.example.myapplication,进入进程对应的heap视图,然后点击右边的Cause GC来手动触发进程的gc 。

这里写图片描述

2 通过adb shell dumpsys meminfo查看自己内存使用情况

这里写图片描述

可以看到存在有7个Activities,正常情况下,我这里应该只有一个activities,所以发生了内存泄露。

3 回到ddms,Dump HPROF file
(打开hprof文件建议使用eclipse中mat工具,如果是用android studio导出的hprof文件则需要进行转换)转换的方式如下图:

这里写图片描述

转换成功后在eclipse中直接打开该文件,然后点击OOL,输入select * from instanceof android.app.Activity,点击右上角红色的执行按钮得到结果:

这里写图片描述

这里可以看到上面的几个activity都存在泄露。这里选择RxJavaBaseActivity来进行分析。
右键点击RxJavaBaseActivity→ Path to GC Roots → exclude all phontom/ weak/soft etc. references

这里写图片描述
泄露的根源是mRXBus对象抓住了RxJavaBaseActivity
查看代码
这里写图片描述
在rxjava中订阅的事件忘记在ondestory的时候与unsubscribe了
添加如下代码则可以解决该泄露问题

    @Override
    protected void onDestroy() {
        super.onDestroy();
        sb.unsubscribe();

    }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

参考文章
http://www.codexiu.cn/android/blog/39810/ Android 非静态内部类导致内存泄漏原因深入剖析
http://www.jianshu.com/p/6a362ea4dfd8
http://hanhailong.com/2015/12/27/Android%E6%80%A7%E8%83%BD%E4%BC%98%E5%8C%96%E4%B9%8B%E5%B8%B8%E8%A7%81%E7%9A%84%E5%86%85%E5%AD%98%E6%B3%84%E6%BC%8F/
https://realm.io/cn/news/droidcon-farber-improving-android-app-performance/ 10 条提升 Android 性能的建议

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/知新_RL/article/detail/240211
推荐阅读
相关标签
  

闽ICP备14008679号