自研Fragment位置权限工具类是否可行?递归实现问题咨询
Fragment位置权限工具类的递归实现问题分析
我尝试编写了一个精简的Fragment位置权限工具类,代码如下:
public class LocationPermissionUtils { private static final String LOCATION_PERMISSION = Manifest.permission.ACCESS_FINE_LOCATION; private static final String LOCATION_COARSE_PERMISSION = Manifest.permission.ACCESS_COARSE_LOCATION; public static boolean requestLocationPermission(Fragment fragment) { if (checkSelfPermission(fragment.requireContext(), LOCATION_PERMISSION) == PackageManager.PERMISSION_GRANTED) return true; if (ActivityCompat.shouldShowRequestPermissionRationale(fragment.requireActivity(), LOCATION_PERMISSION)) { showDialog(); } else { LocationUtils.permissionLauncherUtils(fragment).launch(LOCATION_PERMISSION); // <-- this } return false; } public static ActivityResultLauncher<String> permissionLauncherUtils(Fragment fragment) { return fragment .registerForActivityResult(new ActivityResultContracts.RequestPermission(), new ActivityResultCallback<Boolean>() { @Override public void onActivityResult(Boolean result) { if (result) { //is granted // one can use a callback to send result } else { //is not granted requestLocationPermission(fragment); } } }); } // codes of showDialog() .. }
请问该递归实现是否推荐?代码能否正常运行?若不可行,请告知修正方法。
一、代码能否正常运行?
不能稳定运行,存在多个致命问题:
- 重复注册Launcher导致冲突:每次调用
requestLocationPermission都会创建新的ActivityResultLauncher实例,违反Jetpack组件规范——同一个Fragment多次注册同类型权限请求Launcher会引发注册冲突,还会持有Fragment引用造成内存泄漏。 - 无限递归循环:当用户拒绝权限并勾选「不再询问」后,
shouldShowRequestPermissionRationale返回false,代码会再次发起权限请求;用户再次拒绝后,回调又会递归调用requestLocationPermission,最终导致ANR或栈溢出。 - 权限逻辑缺失:仅处理精确定位权限,未结合粗略定位权限;
showDialog()没有后续回调逻辑(比如用户确认对话框后再发起请求)。
二、递归实现是否推荐?
绝对不推荐,原因如下:
- 递归会触发无限循环,严重破坏用户体验和应用稳定性;
- 每次递归都会创建新的Launcher实例,造成不必要的内存开销;
- 权限请求是一次性交互流程,递归不符合用户交互逻辑(用户拒绝后应给出引导,而非强制重复请求)。
三、修正方法
1. 重构工具类:改用回调+单次注册Launcher
public class LocationPermissionUtils { private static final String LOCATION_FINE = Manifest.permission.ACCESS_FINE_LOCATION; private static final String LOCATION_COARSE = Manifest.permission.ACCESS_COARSE_LOCATION; private static final String[] LOCATION_PERMISSIONS = {LOCATION_FINE, LOCATION_COARSE}; // 权限结果回调接口 public interface PermissionCallback { void onGranted(); void onDenied(boolean neverAskAgain); } // 单次注册Launcher,与Fragment生命周期绑定 public static ActivityResultLauncher<String[]> registerLauncher(Fragment fragment, PermissionCallback callback) { return fragment.registerForActivityResult( new ActivityResultContracts.RequestMultiplePermissions(), result -> { boolean fineGranted = result.getOrDefault(LOCATION_FINE, false); boolean coarseGranted = result.getOrDefault(LOCATION_COARSE, false); if (fineGranted || coarseGranted) { callback.onGranted(); } else { boolean neverAskAgain = !ActivityCompat.shouldShowRequestPermissionRationale( fragment.requireActivity(), LOCATION_FINE); callback.onDenied(neverAskAgain); } } ); } // 发起权限请求 public static void request(Fragment fragment, ActivityResultLauncher<String[]> launcher, PermissionCallback callback) { // 先检查是否已有权限 if (ActivityCompat.checkSelfPermission(fragment.requireContext(), LOCATION_FINE) == PackageManager.PERMISSION_GRANTED || ActivityCompat.checkSelfPermission(fragment.requireContext(), LOCATION_COARSE) == PackageManager.PERMISSION_GRANTED) { callback.onGranted(); return; } // 需要显示权限说明对话框 if (ActivityCompat.shouldShowRequestPermissionRationale(fragment.requireActivity(), LOCATION_FINE)) { showRationaleDialog(fragment, () -> launcher.launch(LOCATION_PERMISSIONS)); } else { launcher.launch(LOCATION_PERMISSIONS); } } // 权限说明对话框实现 private static void showRationaleDialog(Fragment fragment, Runnable onConfirm) { new AlertDialog.Builder(fragment.requireContext()) .setTitle("需要定位权限") .setMessage("应用需要定位权限提供服务,请允许") .setPositiveButton("确定", (dialog, which) -> { dialog.dismiss(); onConfirm.run(); }) .setNegativeButton("取消", (dialog, which) -> dialog.dismiss()) .show(); } }
2. 在Fragment中正确使用工具类
public class MyFragment extends Fragment { private ActivityResultLauncher<String[]> locationLauncher; @Override public void onCreate(@Nullable Bundle savedInstanceState) { super.onCreate(savedInstanceState); // 仅注册一次Launcher,与Fragment生命周期绑定 locationLauncher = LocationPermissionUtils.registerLauncher(this, new LocationPermissionUtils.PermissionCallback() { @Override public void onGranted() { // 权限通过,执行定位逻辑 startLocation(); } @Override public void onDenied(boolean neverAskAgain) { if (neverAskAgain) { // 用户勾选不再询问,引导去设置页 showGoToSettingsDialog(); } else { Toast.makeText(requireContext(), "权限被拒绝", Toast.LENGTH_SHORT).show(); } } }); } // 发起权限请求的入口 private void startPermissionRequest() { LocationPermissionUtils.request(this, locationLauncher, new LocationPermissionUtils.PermissionCallback() { @Override public void onGranted() { startLocation(); } @Override public void onDenied(boolean neverAskAgain) { if (neverAskAgain) { showGoToSettingsDialog(); } else { Toast.makeText(requireContext(), "权限被拒绝", Toast.LENGTH_SHORT).show(); } } }); } // 引导去设置页的对话框 private void showGoToSettingsDialog() { new AlertDialog.Builder(requireContext()) .setTitle("权限被拒绝") .setMessage("请前往设置页开启定位权限") .setPositiveButton("去设置", (dialog, which) -> { dialog.dismiss(); Intent intent = new Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS); Uri uri = Uri.fromParts("package", requireContext().getPackageName(), null); intent.setData(uri); startActivity(intent); }) .setNegativeButton("取消", (dialog, which) -> dialog.dismiss()) .show(); } private void startLocation() { // 执行定位操作 } }
3. 关键优化点
- 单次注册Launcher:避免重复注册导致的内存泄漏和冲突,与Fragment生命周期绑定;
- 多权限请求:同时请求精确定位和粗略定位,覆盖更多场景;
- 完整交互逻辑:区分「拒绝但可再次请求」和「拒绝且不再询问」的情况,给出对应引导;
- 回调传递结果:通过回调让调用方处理权限结果,彻底避免递归循环。
内容的提问来源于stack exchange,提问作者user21193451
相关产品推荐
相关产品推荐

