面试收集:Android 中关于内存泄露有哪些注意点

内存泄露概述

内存泄漏的根本原因是一个长生命周期的对象持有了一个短生命周期的对象,造成短生命周期对象没有办法被回收所导致的

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

内存泄露的危害:只有一个,那就是虚拟机占用内存过高,导致OOM(内存溢出),程序出错。对于Android应用来说,就是你的用户打开一个Activity,使用完之后关闭它,内存泄露;又打开,又关闭,又泄露;几次之后,程序占用内存超过系统限制。

该问题重在积累,不需要死记硬背,自己多总结即可。

1. 长生命周期对象持有 Activity

这基本是最常见的内存泄漏了,比如

  • 单例作为最长生命周期的对象,自然不应该持有 Activity 从而导致内存泄漏发生;
  • 非静态内部类创建静态实例造成的内存泄漏;
  • Handler造成的内存泄漏;
  • 线程造成的内存泄漏;
  • 内部类形式使用handler同时发了延迟消息,这时候退出activity会造成activity内存泄漏。AsyncTask同理会造成内存泄漏

我们分析内存泄漏的时候一定要想清楚整体的引用链关系,根据根节点可达性算法一个GCROOT持有activty那么就会造成其内存泄漏,比如上述handler的情况,handler内部类持有外部类引用,sendmessage的时候message会持有handler,enqueueMessage会造成messageQueue持有message,而如果发的是延迟消息那么message并不会立即的遍历出来处理而是阻塞到对应的message触发时间以后再处理,那么阻塞的这段时间中activity关闭就会造成内存泄漏。

整体的引用链关系就是activity->handler->message->messagequeue(GCROOT)

同理asyncTask也是,由于asyncTask中任务都是串行执行的,如果某一个任务比较耗时或有太多的任务需要处理,那么就有可能发生内存泄漏。

整体的引用链关系就是activity->asynctask->Thread(GCROOT)

解决的方法如上就是将内部类定义为静态内部类,但是静态内部类引用不到外部类的非静态属性和方法,所以可以用弱引用持有外部类,通过弱引用去调用外部类的属性和方法,同时由于弱引用自身如果只有一个对象持有它则在GC时会被直接回收的特性,所以不用担心有内存泄漏的风险。实际上,使用 Kotlin 或者 Java 8 的 Lambda 表达式同样不会导致内存泄漏的发生,这是因为实际上它也是使用的静态内部类,没有持有外部引用。

2. 各种注册操作没有对应的反注册

这一点基本不必多说,相信大家刚刚开始学习广播和 Service 的时候一定对此有所接触,然后就是比如我们常用的第三方框架 EventBus 也是一样的。平时使用的时候注意在对应的生命周期方法中进行反注册。

3. Bitmap 使用完没有注意 recycle()

Bitmap 作为大对象,在使用完毕一定要注意调用 recycle() 进行回收。TypedArrayCursor、各种流同理,一定要在最后调用自己的回收关闭方法处理。

4. WebView 使用不当

WebView 是非常常用的控件,但稍有不注意也会导致内存泄漏。内存泄漏的场景: 很多人使用 Webview 都喜欢采用布局引用方式, 这其实也是作为内存泄漏的一个隐患。当 Activity 被关闭时,Webview 不会被 GC 马上回收,而是提交给事务,进行队列处理,这样就造成了内存泄漏, 导致 Webview 无法及时回收。

目前所知的比较安全的方案是:

  • 在布局中动态添加 WebView。
  • 采用下面的方法。
override fun onDestroy() {
    webView?.apply {
        val parent = parent
        if (parent is ViewGroup) {
            parent.removeView(this)
        }
        stopLoading()
        // 退出时调用此方法,移除绑定的服务,否则某些特定系统会报错
        settings.javaScriptEnabled = false
        clearHistory()
        removeAllViews()
        destroy()
    }
}

5. 循环引用

循环引用导致内存泄漏比较少见,正常来讲不会有人写出 A 持有 B,B 持有 C,C 又持有A 这样的代码,不过总还是需要注意。

总的来说,内存泄漏很常见,但检测方式也很多。我们的 Android Studio 自带的 Monitors 就可以帮我们找到大部分内存问题,当然我们也可以采用譬如 LeakCanary 这样的库去做检测。

常见场景:

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

2. 静态的View

3. Handler

4. 监听器(各种需要注册的Listener,Watcher等)

5. 资源对象没关闭造成内存泄漏

6. 属性动画没移除引起的

7. RxJava引起

8. WebView没关闭

9. 其他的系统控件以及自定义View

解决方式:

1.单例模式引发的内存泄漏:

原因:单例模式里的静态实例持有对象的引用,导致对象无法被回收,常见为持有Activity的引用

优化:改为持有Application的引用,或者不持有使用的时候传递。

2.集合操作不当引发的内存泄漏:

原因:集合只增不减

优化:有对应的删除或卸载操作

3.线程的操作不当引发的内存泄漏:

原因:线程持有对象的引用在后台执行,与对象的生命周期不一致

优化:静态实例+弱引用(WeakReference)方式,使其生命周期一致

4.匿名内部类/非静态内部类操作不当引发的内存泄漏:

原因:内部类持有对象引用,导致无法释放,比如各种回调

优化:保持生命周期一致,改为静态实例+对象的弱引用方式(WeakReference)

5.常用的资源未关闭回收引发的内存泄漏:

原因:BroadcastReceiver,File,Cursor,IO流,Bitmap等资源使用未关闭

优化:使用后有对应的关闭和卸载机制

6.Handler使用不当造成的内存泄漏:

原因:Handler持有Activity的引用,其发送的Message中持有Handler的引用,当队列处理Message的时间过长会导致Handler无法被回收

优化:静态实例+弱引用(WeakReference)方式

检测方案:

1.LeakCanary.

2.Lint

Lint 是 Android studio 自带的静态代码分析工具,使用起来也很方便,选中需要扫描的 module,然后点击顶部菜单栏 Analyze -> Inspect Code ,选择需要扫描的地方即可3.StrictMode,StrictMode 是 Android 系统提供的 API,在开发环境下引入可以更早的暴露发现问题给开发者,于开发阶段解决它,StrictMode 最常被使用来检测在主线程中进行读写磁盘或者网络操作等耗时任务,把这些耗时任务放置于主线程会造成主线程阻塞卡顿甚至可能出现 ANR.

3.adb shell && Memory Usage

可以通过命令 adb shell dumpsys meminfo [package name][-d] 来将指定 package name 的内存信息打印出来,-d 选项会额外打印信息,数据单位为 KB,这种模式可以通过 Activities 选项非常直观地看到 Activity 未释放导致的内存泄漏:

4.Android Memory Profiler

Memory Profiler 是 Android Studio 自带的一个监控内存使用状态的工具

5.MAT

MAT(Memory Analyzer Tools)是一个 Eclipse 插件,它是一个快速、功能丰富的 JAVA heap 分析工具,它可以帮助我们查找内存泄漏和减少内存消耗,MAT 插件的下载地址:Eclipse Memory Analyzer Open Source Project,上面通过 Android studio 生成的 .hprof 文件因为格式稍有不同,所以我们需要现将 AS 生成的 .hprof 文件保存到本地之后,通过 hprof-conv heap-original.hprof heap-converted.hprof 命令进行转换。通过 MAT 去打开转换之后的这个文件

猜你喜欢

转载自blog.csdn.net/cpcpcp123/article/details/113422303