DialogFragment中registerForActivityResult申请权限回调异常
问题说明
实现退出页面前提示用户保存数据的功能时,采用registerForActivityResult申请存储权限,出现以下异常:
- 首次触发流程、权限未授予时,系统权限弹窗正常弹出,但用户点击同意授权后,权限回调完全不执行
- 再次触发退出流程时,权限已经处于授予状态,不需要弹出系统权限弹窗,回调反而能正常返回授权成功结果
原实现代码如下:SaveCalibrationDialogFragment 代码:
class SaveCalibrationDialogFragment(val data: String) : DialogFragment() { private val activityResultLauncher = registerForActivityResult( ActivityResultContracts.RequestPermission()) { isGranted -> println("Received CallBack") if (isGranted) { println("Permission granted!") } else { println("Permission needed!") } } override fun onCreateDialog(savedInstanceState: Bundle?): Dialog { val builder = AlertDialog.Builder(this.requireContext()) val dialog = builder.setTitle("Notice") .setMessage("Data is not saved. Do you want to save before exiting?") .setNegativeButton("No") { dialog, _ -> }.setPositiveButton("Yes") { _, _ -> activityResultLauncher?.launch(WRITE_EXTERNAL_STORAGE) } .create() return dialog } }
宿主Activity代码:
cancel.setOnClickListener { if (viewModel.isDataSave && viewModel.isLastPage) { setResult(Activity.RESULT_OK, intent) finish() } else if (!viewModel.isDataSave && viewModel.isLastPage){ SaveCalibrationDialogFragment(viewModel.dataToSave!!).show(this.supportFragmentManager, "costumeSaveDialog") } else { setResult(Activity.RESULT_CANCELED, intent) finish() } }
故障原因
问题出在AlertDialog的默认按钮行为和registerForActivityResult的生命周期要求冲突:
- AlertDialog的PositiveButton默认点击逻辑会直接dismiss当前对话框,外层DialogFragment会同步进入销毁流程,和Fragment绑定的ActivityResultLauncher随之退出活跃状态
- 当用户在系统权限弹窗点击同意后,系统找不到处于活跃状态的launcher来分发权限结果,自然不会触发预设的回调逻辑
- 第二次触发流程时,权限已经授予,系统不会弹出权限弹窗,会直接同步返回授权结果,此时新创建的DialogFragment刚完成初始化,launcher处于活跃状态,所以能正常收到回调
修复方案
- 覆写AlertDialog按钮的默认点击逻辑,不要在点击确认按钮时立刻关闭对话框,等权限结果返回后再手动控制对话框和页面的关闭逻辑
- 发起权限申请前先做权限状态检查,已经授权的场景直接走写入逻辑,不需要重复发起申请
- 不要在Activity弹出Dialog后立刻执行finish操作,所有页面关闭逻辑挪到DialogFragment的权限回调/按钮点击逻辑中处理,避免宿主提前销毁导致launcher失效
修复后的DialogFragment代码:
class SaveCalibrationDialogFragment(val data: String) : DialogFragment() { private val activityResultLauncher = registerForActivityResult( ActivityResultContracts.RequestPermission() ) { isGranted -> if (isGranted) { // 执行文件写入逻辑 // writeDataToFile(data) dismiss() requireActivity().setResult(Activity.RESULT_OK) requireActivity().finish() } else { Toast.makeText(requireContext(), "存储权限被拒绝,无法保存数据", Toast.LENGTH_SHORT).show() } } override fun onCreateDialog(savedInstanceState: Bundle?): Dialog { val builder = AlertDialog.Builder(requireContext()) val dialog = builder.setTitle("Notice") .setMessage("Data is not saved. Do you want to save before exiting?") .setNegativeButton("No") { _, _ -> dismiss() requireActivity().setResult(Activity.RESULT_CANCELED) requireActivity().finish() } // 先传空监听,绕过默认点击即dismiss的逻辑 .setPositiveButton("Yes", null) .create() // 对话框显示后再手动绑定确认按钮点击事件 dialog.setOnShowListener { dialog.getButton(AlertDialog.BUTTON_POSITIVE).setOnClickListener { // 先检查权限状态,已授权直接写入 if (ContextCompat.checkSelfPermission( requireContext(), Manifest.permission.WRITE_EXTERNAL_STORAGE ) == PackageManager.PERMISSION_GRANTED ) { // writeDataToFile(data) dismiss() requireActivity().setResult(Activity.RESULT_OK) requireActivity().finish() return@setOnClickListener } // 未授权才发起权限申请 activityResultLauncher.launch(Manifest.permission.WRITE_EXTERNAL_STORAGE) } } return dialog } }
补充适配提示:Android 13(API 33)及以上版本已废弃
WRITE_EXTERNAL_STORAGE权限,写入应用私有目录不需要申请任何存储权限,写入公共媒体目录需要申请对应细分的媒体读取权限,无需申请写权限。
内容的提问来源于stack exchange,提问作者msc87
相关产品推荐
相关产品推荐

