You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

自研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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.11 11:27:35