内存管理最佳做法

本文档假定您已遵循 Android 应用的内存管理最佳做法指南,例如管理应用的内存一文中所述的方法。

简介

内存泄漏是一种资源泄漏,如果系统分配给计算机程序的内存不再需要使用但没有释放,就会发生这种情况。泄漏可能会导致应用向操作系统请求的内存多于其可用内存,从而造成应用崩溃。一些不当做法可能会导致 Android 应用发生内存泄漏,例如资源处理不当或未取消注册不再需要使用的监听器。

本文档提供了一些相关最佳做法,用于帮助预防、检测和解决代码中的内存泄漏问题。如果您尝试了本文档所述的方法,但还是认为我们的 SDK 中可能存在内存泄漏问题,请参阅如何报告 Google SDK 相关问题

联系支持团队前的准备工作

向 Google 支持团队报告内存泄漏问题前,请按照本文档中提供的相关最佳做法和调试步骤操作,以确保您的代码中不存在此类错误。这些步骤可能会解决您的问题,但如果没有,也会生成 Google 支持团队为您提供帮助时所需的信息。

防止内存泄漏

遵循以下最佳做法有助于避免使用 Google SDK 的代码中出现导致内存泄漏的一些最常见问题。

Android 应用适用的最佳做法

请确认您已在 Android 应用中完成以下所有操作:

  1. 释放未使用的资源
  2. 取消注册不再需要使用的监听器
  3. 取消不再需要执行的任务
  4. 转发生命周期方法以释放资源
  5. 使用最新版本的 SDK

如需了解每种做法的具体详细信息,请参阅以下部分。

释放未使用的资源

当您的 Android 应用使用某项资源时,请务必在不再需要使用时释放该资源,否则在应用使用完资源后,该资源仍会继续占用内存。如需了解详情,请参阅 Android 文档中的 activity 生命周期

释放 GeoSDK 中过时的 GoogleMap 引用

一个常见的错误是,使用 NavigationView 或 MapView 缓存 GoogleMap 会导致内存泄漏。GoogleMap 与从中检索它的 NavigationView 或 MapView 之间是一对一的关系。您必须确保 GoogleMap 不被缓存,或者在调用 NavigationView#onDestroy 或 MapView#onDestroy 时释放引用。如果是使用 NavigationSupportFragment、MapSupportFragment 或您自己用来封装这些视图的 fragment,就必须在 Fragment#onDestroyView 中释放引用。

class NavFragment : SupportNavigationFragment() {

  var googleMap: GoogleMap?

  override fun onCreateView(
    inflater: LayoutInflater,
    parent: ViewGroup?,
    savedInstanceState: Bundle?,
  ): View  {
    super.onCreateView(inflater,parent,savedInstanceState)
    getMapAsync{map -> googleMap = map}
  }

  override fun onDestroyView() {
    googleMap = null
  }
}

取消注册不再需要使用的监听器

当您的 Android 应用为某个事件(例如按钮点击或视图状态更改)注册监听器时,请务必在应用不再需要监控该事件时取消注册监听器,否则在应用使用完监听器后,监听器仍会继续占用内存。

例如,假设您的应用使用 Navigation SDK,并调用以下监听器来监听到达事件(addArrivalListener 方法用于监听到达事件),那么,当应用不再需要监听到达事件时,还应调用 removeArrivalListener

var arrivalListener: Navigator.ArrivalListener? = null

fun registerNavigationListeners() {
  arrivalListener =
    Navigator.ArrivalListener {
      ...
    }
  navigator.addArrivalListener(arrivalListener)
}

override fun onDestroy() {
  navView.onDestroy()
  if (arrivalListener != null) {
    navigator.removeArrivalListener(arrivalListener)
  }

  ...
  super.onDestroy()
}

取消不再需要执行的任务

当 Android 应用启动异步任务(例如下载或网络请求)时,请务必在任务完成后予以取消,否则任务会继续在后台运行。

如需详细了解相关最佳做法,请参阅 Android 文档中管理应用的内存一文。

转发生命周期方法以释放资源

如果您的应用使用 Navigation SDK 或 Maps SDK,请务必通过将生命周期方法(以粗体显示)转发到 navView 来释放资源。为此,您可以在 Navigation SDK 中使用 NavigationView,或是在 Maps SDK 或 Navigation SDK 中使用 MapView。此外,您也可以使用 SupportNavigationFragmentSupportMapFragment,而不是直接分别使用 NavigationViewMapView。支持 fragment 会处理生命周期方法的转发作业。

class NavViewActivity : AppCompatActivity() {

  override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)
    ...
    navView = ...
    navView.onCreate(savedInstanceState)
    ...
  }

  override fun onSaveInstanceState(savedInstanceState: Bundle) {
    super.onSaveInstanceState(savedInstanceState)
    navView.onSaveInstanceState(savedInstanceState)
  }

  override fun onTrimMemory(level: Int) {
    super.onTrimMemory(level)
    navView.onTrimMemory(level)
  }

  /* Same with
    override fun onStart()
    override fun onResume()
    override fun onPause()
    override fun onConfigurationChanged(...)
    override fun onStop()
    override fun onDestroy()
  */
}

使用最新版本的 SDK

Google SDK 会不断更新,以加入新功能、修复 bug 或提升性能。为接收这些修复,需要使应用中的 SDK 保持最新状态。

对内存泄漏问题进行调试

如果在采纳本文档先前所述的各项适用建议后,仍然发生内存泄漏问题,请按照以下流程进行调试。

开始之前,建议您先了解 Android 管理内存的方式。如需了解相关信息,请参阅 Android 内存管理概览

如要对内存泄漏问题进行调试,请按照以下流程操作:

  1. 重现问题。这是调试时至关重要的一步。
  2. 检查内存用量是否正常。请检查看似因泄漏而增加的用量是否确实为运行应用所需的内存。
  3. 粗略调试。您可以使用多种实用程序进行调试。以下三种不同的标准工具集有助于对 Android 中的内存问题进行调试:Android Studio、Perfetto 和 Android 调试桥 (adb) 命令行实用程序。
  4. 查看应用的内存使用情况获取堆转储和分配跟踪数据,然后对其进行分析。
  5. 修正内存泄漏问题

以下部分将详细说明上述步骤。

第 1 步:重现问题

如果您无法重现问题,请先考虑可能会导致内存泄漏的情况。如果您确定问题已经重现,那么直接查看堆转储可能奏效。但是,如果您只是在应用启动时或其他任意时间点获得堆转储,则可能尚未激活触发泄漏的条件。重现问题时,建议您研究各种可能的情况:

  • 启用了哪组功能?

  • 哪些特定的用户操作顺序会触发泄漏?

    • 您是否反复多次尝试执行了这个顺序?
  • 应用经历了哪些生命周期状态?

    • 您是否反复多次尝试经历了不同的生命周期状态?

确保您可以在最新版本的 SDK 中重现该问题。以前版本的问题可能已经得到解决。

第 2 步:检查应用的内存用量是否正常

每项功能都需要使用额外的内存。针对不同的情况进行调试时,请考虑这是正常使用情况,还是确实属于内存泄漏。例如,对于不同的功能或用户任务,请考虑以下可能性:

  • 很有可能属于泄漏:反复多次重现当时情况后,内存用量长时间下来有所增加。

  • 很有可能为正常内存使用情况:情况停止后,系统回收内存。

  • 可能为正常内存使用情况:内存用量在一段时间内增加,之后逐渐减少,这可能是因为缓存有限或其他正常内存使用情况所致。

如果应用行为很有可能是正常的内存使用情况,那么可以通过管理应用的内存来解决问题。如需相关帮助,请参阅管理应用的内存

第 3 步:粗略调试

对内存泄漏问题进行调试时,请先粗略调试,等找出可能原因后再细查。您可以使用以下其中一款粗略调试工具,先分析一段时间内是否发生了泄漏情况:

Android Studio 内存分析器

该工具提供内存用量的直方图。您也可以通过这个界面获得堆转储和分配跟踪数据。这是我们默认建议使用的工具。如需了解详情,请参阅 Android Studio 内存分析器

Perfetto 内存计数器

Perfetto 可让您精准控制跟踪多个指标,并集中显示在同一个直方图中。如需了解详情,请参阅 Perfetto 内存计数器

Perfetto 界面

Android 调试桥 (adb) 命令行实用程序

Perfetto 可跟踪的许多指标也能在 adb 命令行实用程序中找到,方便您直接查询。以下是几个重要的示例:

  • Meminfo 可让您查看特定时间点的详细内存信息。

  • Procstats 可提供一段时间内的部分重要汇总统计数据。

此处需要关注的一个关键统计数据是应用在一段时间内所需的最大物理内存占用量 (maxRSS)。MaxPSS 可能不那么准确。如需了解提高准确性的方法,请参阅 adb shell dumpsys procstats --help –start-testing 标记。

分配跟踪

分配跟踪可识别已分配内存的堆栈轨迹,以及内存是否未释放。在跟踪原生代码中的泄漏问题时,此步骤尤为有用。该工具可识别堆栈轨迹,因此很适合用于快速排查根本原因或找出重现问题的方式。如需了解使用分配跟踪的步骤,请参阅使用分配跟踪对原生代码中的内存进行调试

第 4 步:使用堆转储检查应用的内存使用情况

检测内存泄漏的一种方法是获取应用的堆转储,然后检查是否有泄漏问题。堆转储是应用内存中所有对象的快照,可用于诊断内存泄漏及其他与内存相关的问题。

Android Studio 可以检测 GC 无法修正的内存泄漏问题。捕获堆转储时,Android Studio 会检查是否存在仍可访问但已被销毁的 activity 或 fragment。

  1. 捕获堆转储
  2. 分析堆转储以查找内存泄漏问题
  3. 修正内存泄漏问题

如需了解详情,请参阅以下各部分。

捕获堆转储

如要捕获堆转储,您可以使用 Android 调试桥 (adb) 或 Android Studio 内存分析器。

使用 adb 捕获堆转储

如要使用 adb 捕获堆转储,请按照以下步骤操作:

  1. 将 Android 设备连接到计算机。
  2. 打开命令提示符并导航到 adb 工具所在的目录。
  3. 如要捕获堆转储,请运行以下命令:

    adb shell am dumpheap my.app.name $PHONE_FILE_OUT

  4. 如要检索堆转储,请运行以下命令:

    adb pull $PHONE_FILE_OUT $LOCAL_FILE.

使用 Android Studio 捕获堆转储

如要使用 Android Studio 内存分析器捕获堆转储,请按照 Android 中捕获堆转储部分的相关步骤进行操作。

分析堆转储以查找内存泄漏问题

捕获堆转储后,您可以使用 Android Studio 内存分析器对其进行分析。为此,请按以下步骤操作:

  1. 在 Android Studio 中打开您的 Android 项目。

  2. 选择 Run,然后选择 Debug 配置。

  3. 打开 Android Profiler 标签页。

  4. 选择 Memory

  5. 选择 Open heap dump,然后选择您生成的堆转储文件。内存分析器会显示应用内存用量的图表。

  6. 使用此图表分析堆转储:

    • 识别不再使用的对象。

    • 识别使用大量内存的对象。

    • 查看每个对象目前使用的内存量。

  7. 根据此类信息细查或找出内存泄漏的原因,然后加以修正。

第 5 步:修正内存泄漏问题

确定内存泄漏的原因后,即可进行修正。修正 Android 应用中的内存泄漏问题有助于提升应用的性能和稳定性,具体详情因情况而异。但是,以下建议可能会有所帮助:

其他调试工具

完成上述步骤后,如果您还是找不到也无法修正内存泄漏问题,不妨尝试以下工具:

使用分配跟踪对原生代码中的内存进行调试

即使您不直接使用原生代码,一些常用的 Android 库(包括 Google SDK)也会使用。如果您认为原生代码存在内存泄漏问题,可以使用多种工具进行调试。想要找出内存泄漏的可能原因,使用 Android Studioheapprofd(也与 Perfetto 兼容)跟踪分配情况是不错的方法,通常也是最快的调试方式。

分配跟踪还有一项独特优势,就是可以让您共享结果,而无需附上可在堆中找到的敏感信息。

使用 LeakCanary 找出泄漏问题

LeakCanary 是一款强大的工具,用于识别 Android 应用中的内存泄漏。如需详细了解如何在应用中使用 LeakCanary,请访问 LeakCanary

如何报告 Google SDK 相关问题

如果您尝试了本文档所述的方法,但还是认为我们的 SDK 中可能存在内存泄漏问题,请与客户服务支持团队联系,并尽可能多地提供以下信息:

  • 重现内存泄漏的步骤。如果这些步骤需要进行复杂的编码,建议将用于重现问题的代码复制到我们的示例应用中,并提供在界面中触发泄漏所需采取的额外步骤。

  • 从已重现问题的应用中捕获的堆转储。请捕获两个不同时间点的堆转储,呈现内存用量大幅增加的情况。

  • 如果原生内存泄漏属于正常情况,请提供来自 heapprofd 的分配跟踪结果。

  • 重现泄漏情况后生成的 bug 报告

  • 任何与内存相关的崩溃的堆栈轨迹

    重要提示:堆栈轨迹本身通常不足以对内存问题进行调试,因此请务必同时提供任何一种其他形式的信息。