Android 12原生系统居然有内存泄露隐患?

文章揭示了Android 12中由于使用`shouldShowRequestPermissionRationale`方法可能导致的系统级内存泄漏问题。在Android S上,当调用该方法时,Activity不会被正确回收,而是被AppOpsManager持有,从而引发内存泄漏。作者通过内存分析工具展示了内存泄漏的过程,并详细解释了泄漏的原因和调用链。解决方案建议在应用退出时主动移除监听以防止内存泄漏。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

作者:作者:努比亚技术团队

Android里面内存泄漏问题最突出的就是Activity的泄漏,而泄漏的根源大多在于因为生命周期较长的对象去引用生命周期较短的Activity实例,也就会造成在Activity生命周期结束后,还被引用导致无法被系统回收释放。

Activity导致内存泄漏有两种情况:

  1. 应用级:应用程序代码实现的activity没有很好的管理其生命周期,导致Activity退出后仍然被引用。
  2. 系统级:Android系统级实现的对activity管理不太友好,被应用调用导致内存泄漏。

本文主要讲的是最近发现的系统级shouldShowRequestPermissionRationale方法使用导致的内存泄漏问题。

一、背景

Android 6.0 (API 23) 之前应用的权限在安装时全部授予,运行时应用不再需要询问用户。在 Android 6.0 或更高版本对权限进行了分类,对某些涉及到用户隐私的权限可在运行时根据用户的需要动态获取。主要流程如下:

权限申请流程

这个过程中会遇到这么几个方法:

  • ContextCompat.checkSelfPermission,检查应用是否有权限
  • ActivityCompat.requestPermissions,请求某个或某几个权限
  • onRequestPermissionsResult,请求权限之后的授权结果回调
  • shouldShowRequestPermissionRationale

前三个方法的用途都非常清楚,使用也很简单,这里不做过多解释,今天主要看下shouldShowRequestPermissionRationale,看下它是干什么用的:

当APP调用一个需要权限的函数时,如果用户拒绝某个授权,下一次弹框时将会有一个“禁止后不再询问”的选项,来防止APP以后继续请求授权。如果这个选项在拒绝授权前被用户勾选了,下次为这个权限请求requestPermissions时,对话框就不弹出来了,结果就是app啥都不干。遇到这种情况需要在请求requestPermissions前,检查是否需要展示请求权限的提示,这时候用的就是shouldShowRequestPermissionRationale方法。

shouldShowRequestPermissionRationale字面解释是“应不应该解释下请求这个权限的目的”,下面列举了此方法使用时的4种情况及相应情况下的返回值:

  1. 都没有请求过这个权限,用户不一定会拒绝你,所以你不用解释,故返回false;
  2. 请求了但是被用户拒绝了,此时返回true,意思是你该向用户好好解释下了;
  3. 用户选择了拒绝并且不再提示,也不给你弹窗提醒了,所以你也不用解释了,故返回fasle;
  4. 已经允许了,不需要申请也不需要提示,故返回false。

二、调用案例及内存泄漏隐患

2.1正常权限申请流程

通常我们申请权限时先调用checkSelfPermission方法检验应用是否有需要使用的权限,没有相应权限时调用shouldShowRequestPermissionRationale方法检查是否需要展示请求权限的提示。不需要展示提示时再调用requestPermissions方法进行权限请求。代码如下:

Demo源码

2.2内存泄漏隐患发现

在Android S上,我们使用上述方式进行权限获取时会发现只要你调用了shouldShowRequestPermissionRationale方法,当MainActivity生命周期结束后MainActivity都不会被回收。我们可以不给予所需权限多次进入退出此应用运行一段时间(保证每次都会调用到shouldShowRequestPermissionRationale方法)。使用adb命令dump meminfo查看内存情况。可以看到activity实例数为25,表明acticity虽然被销毁但是因为被其他对象持有所以并没有被GC。 注意:此处测试是通过返回键退出activity的,我们的Demo在返回键的监听有调用finish方法确保结束activity。每次重新进入都会重新执行onCreate方法。因为Android S上使用返回键退出应用并不会直接销毁activity,而只有当应用主动调用finish或者非启动类型的activity才会去销毁。

Demo内存占用情况

此时我们已经发现此Demo存在明显的内存泄露问题。下面我们使用Memory Profiler工具抓取hprof文件进行内存分析,看看是哪里发生了内存泄漏。

使用Memory Profiler工具进行内存泄漏分析

  1. 可以看到Memory Profiler工具已经提示我们有com.nubia.application包下MainActivity有24个对象发生了内存泄露。

  2. 可以看到当前MainActivity总共实例有25个和adb命令查询出来吻合。其他24个实例没有被GC导致内存泄露。

  3. References标签页可以看到其他MainActivity实例被AppOpsManager持有导致无法被GC。

我们可以看到Memory Profiler工具提示共有48个对象产生内存泄露那么其他24个是哪里产生的呢?点击Leaks进行查看如下图。

使用Memory Profiler工具进行内存泄漏分析

  1. 可以看到除了MainActivity产生了内存泄露,ReportFragment也产生了内存泄露。查看ReportFragment的Instance Details标签页可以看到ReportFragment的实例被MainActivity持有而MainActivity被AppOpsManager持有所以产生了内存泄露。

  2. 至此Demo中所有内存泄露问题分析完成。

2.3内存泄漏隐患分析

在上一节中我们已经发现Demo在使用过程中存在内存泄漏问题。只要我们调用shouldShowRequestPermissionRationale方法,当Activity生命周期结束时就会发生Activity内存泄漏。那么这一节我们来具体分析下为什么调用shouldShowRequestPermissionRationale方法会发生内存泄漏。

我们先来看一下shouldShowRequestPermissionRationale方法调用整个过程的时序图:

shouldShowRequestPermissionRationale方法调用时序图

看完调用流程图后,我们再来一步一步分析shouldShowRequestPermissionRationale具体是怎么调用的,以及为什么会产生内存泄漏?

先从Activity的shouldShowRequestPermissionRationale方法开始,Activity调用的是packageManager的shouldShowRequestPermissionRationale方法。

public boolean shouldShowRequestPermissionRationale(@NonNull String permission) {
   
    //调用ContextImpl的getPackageManager()方法获取PackageManager实例
    //然后调用PackageManager的shouldShowRequestPermissionRationale方法
    return getPackageManager().shouldShowRequestPermissionRationale(permission);
}

我们知道Activity是从ContextWrapper继承而来的,ContextWrapper中持有一个mBase实例,这个实例指向一个contextImpl对象,Activity的getPackageManager这个方法调用的就是contextImpl的getPackageManager方法。

private PackageManager mPackageManager;
...
public PackageManager getPackageManager() 
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值