为啥进程死亡后Fragment中没有触发onRequestPermissionsResult?

Posted

技术标签:

【中文标题】为啥进程死亡后Fragment中没有触发onRequestPermissionsResult?【英文标题】:Why onRequestPermissionsResult is not triggered in the Fragment after process death?为什么进程死亡后Fragment中没有触发onRequestPermissionsResult? 【发布时间】:2021-03-28 11:00:07 【问题描述】:

我在片段中遇到了一个关于 onRequestPermissionsResult 的奇怪问题。基本上,fragment 在 onCreate 中要求相机许可:

override fun onCreate(savedInstanceState: Bundle?) 
    AppCompatDelegate.setCompatVectorFromResourcesEnabled(true)
    super.onCreate(savedInstanceState)
    if(!permissionsGranted)
        requestPermissions(arrayOf(Manifest.permission.CAMERA),
                PermissionsDelegateUtil.REQUEST_PERMISSIONS_CAMERA
        )
    

然后我在 onRequestPermissionsResult 中处理权限:

 override fun onRequestPermissionsResult(requestCode: Int, permissions: Array<out String>, grantResults: IntArray) 
    super.onRequestPermissionsResult(requestCode, permissions, grantResults)
    val resultResponse = permissionsDelegateUtil.resultGranted(requestCode=requestCode, permissions = permissions, grantResults = grantResults)
    when(resultResponse)
        PermissionResult.PermissionGranted -> 
            setupCameraX()
        
        PermissionResult.PermissionNotGrantedRetryAuto -> 
            //retrying again
            requestPermissions(arrayOf(Manifest.permission.CAMERA),
                PermissionsDelegateUtil.REQUEST_PERMISSIONS_CAMERA
        )
        
        PermissionResult.PermissionNotGrantedCantRetry -> 
                //show a screen that user must enabled permission
        
       PermissionResult.PermissionNotGrantedDontAsk -> 
           //Don't do anything, because you can't retry here, otherwise it will cause infinite loop.
        
    

然后权限委托类处理权限逻辑:

 fun resultGranted(requestCode: Int,
                  permissions: Array<out String>,
                  grantResults: IntArray): PermissionResult 
    if (requestCode != REQUEST_PERMISSIONS_CAMERA) 
        return PermissionResult.PermissionNotGrantedDontAsk
    
    if (grantResults.isEmpty()) 
        return PermissionResult.PermissionNotGrantedDontAsk
    
    if(permissions[0] != Manifest.permission.CAMERA)
        return PermissionResult.PermissionNotGrantedDontAsk
    
    return if (grantResults[0] == PackageManager.PERMISSION_GRANTED) 
        PermissionResult.PermissionGranted

     else 
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) 
            if (fragment.shouldShowRequestPermissionRationale(Manifest.permission.CAMERA)) 
                PermissionResult.PermissionNotGrantedRetryAuto
             else
                ///User selected don't show again checkbox.
                PermissionResult.PermissionNotGrantedCantRetry
            
         else
            PermissionResult.PermissionNotGrantedRetryAuto
        
    

在我强制进程死亡(我在开发人员工具中选择不保留活动复选框)之前,此代码运行良好。之后,我从后台返回,权限对话框仍然存在(因为再次触发 onCreate 并再次执行权限检查)。

问题是,在我按下权限对话框中的任何按钮后,onRequestPermissionsResult 方法 没有在片段中触发。我查看了logcat,发现权限结果应该是传递的,因为这些日志:

2020-12-17 18:48:17.816 22496-22496/? V/GrantPermissionsActivity: Logged buttons presented and clicked permissionGroupName=android.permission-group.CAMERA uid=10135 package=com.test.app  presentedButtons=25 clickedButton=8
2020-12-17 18:48:17.821 22496-22496/? V/GrantPermissionsActivity: Permission grant result requestId=-2584100455717644829 callingUid=10135 callingPackage=com.test.app permission=android.permission.CAMERA isImplicit=false result=6

我还尝试在 PermissionResult.PermissionNotGrantedDontAsk 返回后重试调用权限。它会起作用,但它会导致权限请求和响应的无限循环触发,从而导致应用程序崩溃。

编辑

我在不使用 backstack 的情况下添加了一个片段:

fun addCameraSessionFragment(supportFragmentManager: FragmentManager) 
    val fragment = supportFragmentManager.findFragmentByTag(CAMERA_SESSION_FRAGMENT_TAG)
    if (fragment == null) 
        val cameraSessionFragment =
                CameraSessionFragment()
        val fragmentTransaction = supportFragmentManager.beginTransaction()
        fragmentTransaction.replace(getTranscationRootId(), cameraSessionFragment, CAMERA_SESSION_FRAGMENT_TAG).commitNow()
    

问题是这个添加逻辑是由 ViewModel 作为 LiveData 事件触发的。进程死亡发生后,加法实际上发生了两次。第一个是因为它是最后一个片段,它在进程死亡后恢复。第二个是由 ViewModel 动作触发的。

编辑

我创建了一个新项目,即使没有使用片段,仅使用活动,也可以再次重现此问题。这是存储库: https://github.com/wellbranding/AndroidPermissionIssue 尝试在权限对话框打开时强制进程死亡。返回应用程序后,对话框存在,但如果您按下任何按钮,onRequestPermissionsResult 仍然不会被触发,但应该会。

【问题讨论】:

片段是怎么添加的 可能是内存泄漏,因此您在内存中有多个主要活动或片段的实例。如果您可以提供演示项目,我可以帮助调试。 @HarisDautović 谢谢。我创建了一个新的 android 项目,粘贴了此代码,并且可以重现该问题。 gist.github.com/wellbranding/a2cb6f31130ffc19d6e93a8177781ad5 @ViktorVostrikov。我做了研究并对为什么会发生这种情况有一些假设,但还没有任何解决方案。以防万一,你能检查一下你是否有同样的行为吗?这是一个视频:gofile.io/d/34Cg4o(我正在使用:Android API 29,Android Studio 4.4.1) @HarisDautović 是的,我有同样的行为。通过在 Android Studio 中按下终止按钮,即使拒绝响应,它也可以正常工作。但是,如果我在设备设置中启用不保留活动,并在它之后转到后台/返回,问题仍然存在。 【参考方案1】:

与用户的权限请求交互可能被中断。在这种情况下,您将收到空的权限和结果数组,这应该被视为取消。

解决方案:使用android:launchMode="singleInstance"作为主要活动。

<application
    android:allowBackup="true"
    android:icon="@mipmap/ic_launcher"
    android:label="@string/app_name"
    android:roundIcon="@mipmap/ic_launcher_round"
    android:supportsRtl="true"
    android:theme="@style/Theme.***Solutions">
    <activity android:name="dh.sos.MainActivity" android:launchMode="singleInstance">>
        <intent-filter>
            <action android:name="android.intent.action.MAIN" />
            <category android:name="android.intent.category.LAUNCHER" />
        </intent-filter>
    </activity>
</application>

默认模式是standardstandardsingleTop 启动模式的 Activity 可以被实例化多次。

default 模式与 Android 权限对话框 +“不保留活动”结合使用时,我们会失去警报与我们的应用程序之间的连接(案例 1)。

案例一:android:launchMode="default"

启用“不保留活动” 运行应用并请求许可 将应用置于后台 打开应用 权限对话框将再次显示 在与 Android 权限对话框交互后,我们在 onRequestPermissionsResult 函数内没有收到任何回调

案例2android:launchMode="singleInstance"

启用“不保留活动” 运行应用并请求许可 将应用置于后台 打开应用 onRequestPermissionsResult 被调用,但我们没有看到权限对话框。这种情况被认定为权限被取消,属于正常行为。

更新:发现了一个新案例,上面的解决方案还不够。更具体地说:当用户从最近的活动屏幕返回到应用程序时

添加android:excludeFromRecents="true" 是部分解决方案,但如果用户直接从应用程序转到最近的屏幕,然后返回应用程序,则此方法不起作用。

解决方法:当返回到之前savedInstanceState 中可见权限对话框的应用时,重新创建 MainActivity。

源码:https://github.com/dautovicharis/sos_android/tree/q_65346039

【讨论】:

评论不适用于扩展讨论或调试会话;这个对话是moved to chat。请确保使用所有相关信息更新答案。【参考方案2】:

您提供的代码不完整(即使在要点中),因为没有提供 Fragment。 但也没有什么奇怪的。如果我理解正确,您正在请求活动级别的权限,而不是片段级别的权限。因此触发的方法是来自活动的 onRequestPermissionsResult,而不是片段。 看看这个关于如何实现它的答案: https://***.com/a/35996955/1827254

摘录:

我认为您混淆了片段和活动的方法。上个月我的项目也有类似的问题。请检查您是否最终有以下内容:

在 AppCompatActivity 中使用方法 ActivityCompat.requestpermissions 在 v4 支持片段中,您应该使用 requestpermissions 问题是,如果您在片段中调用 AppcompatActivity.requestpermissions 则回调将进入活动而不是片段 确保从 Activity 的 onRequestPermissionsResult 调用 super.onRequestPermissionsResult。

【讨论】:

不,我认为您理解错误:) 我提供了一个带有活动的示例,因为它更容易重现。此问题发生在活动和片段上。我将编辑我的问题。 @ViktorVostrikov 我刚刚试过你的要点例子。我使用的是运行 Android 7.0 的三星 Galaxy S7 (SM-G930F),它运行良好。当我从后台返回时,活动按预期创建,“保持活动”为假,显示对话框,按钮触发 onRequestPermissionsResult 这确实很奇怪。我和 Haris Dautović 都可以重现这个问题。我使用 API 29 的 Pixel XL 4 模拟器和 API 29 的 Xioami mi A3。我在 Android 7.0 中没有看到这个问题。 你能分享整个演示项目,包括 gradle 文件和所有内容,以便我们可以运行相同的代码吗? 好的,就是这样 :) github.com/wellbranding/AndroidPermissionIssue/tree/master

以上是关于为啥进程死亡后Fragment中没有触发onRequestPermissionsResult?的主要内容,如果未能解决你的问题,请参考以下文章

pig 为啥它运行多个mapreduce 进程?

关闭飞行模式后,为啥 Android 应用程序会通过 Activity 和 Fragment 生命周期方法

将 Psyco 与 django 一起使用是不是有意义?

为啥 Fragment SupportMapFragment 还没有附加

什么决定了进程外COM服务器注意到客户端已经死亡的时间?

Android进程经常产生logcat“已死亡”消息。