组件化系列(一)Android动态权限

简介: 历史版本权限组件使用的是AndPermission,长期无人维护,历史代码臃肿,不便拓展,考虑使用PermissionsDispatcher,但是PermissionsDispatcher APT插件会影响编译效率,easypermissions侵入性太强,会影响整个工程,RxPermissions 貌似是最佳选择,但是RxPermissions需要高度自定义符合自己项目特色的UI,所以干脆自己写个权限组件好了,希望大家喜欢

权限组件设计背景

历史版本权限组件使用的是AndPermission,长期无人维护,历史代码臃肿,不便拓展,考虑使用PermissionsDispatcher,但是PermissionsDispatcher APT插件会影响编译效率,easypermissions侵入性太强,会影响整个工程,RxPermissions 貌似是最佳选择,但是RxPermissions需要高度自定义符合自己项目特色的UI,所以干脆自己写个权限组件好了,希望大家喜欢

权限组件设计需求

自定义炫酷权限弹框


不入侵工程Activity


权限注入符合自定义规则

权限组件设计原理

检查所提供的权限是否被申请

  1. 检查此设备上是否可能缺少权限
  2. 如果 Activity 或 Fragment 有权访问所有给定的权限,不处理
  3. 如果API在23以下,检测到了,你也没有主动的触发手段,就不处理,否则检测到了,否则弹窗
  4. 如果当前权限被拒绝了,那么触发了权限申请就进入setting页面
  5. 在构建DialogFragment 的 onRequestPermissionsResult 方法里面去做权限申请工作,申请完毕需要移除Fragment
  6. requestCode 回调 交给  DialogFragment 处理, DialogFragment 负责权限赋权工作

权限组件兼容问题

未全方面测试,出现的问题未能及时定位,希望各位朋友能帮忙定位

权限组件实现步骤

1. 将权限接口化管理

public interface oMKPermissionListener {
    /**
     * 权限申请回调
     *
     * @param requestCode Activity 回调返回码
     * @param permissions 权限列表
     */
    void onMKGranted(int requestCode, String[] permissions);
    /**
     * 权限拒绝回调
     *
     * @param requestCode Activity 回调返回码
     * @param result      权限拒绝结果集
     */
    void onMKDenied(int requestCode, Map<String, Integer> result);
    /**
     * 权限不再询问回调
     *
     * @param requestCode Activity 回调返回码
     * @param result      权限不再询问结果集
     */
    void onMKNeverAsk(int requestCode, Map<String, Integer> result);
}

2. 检查所提供的权限是否被申请,如果 Activity 或 Fragment 有权访问所有给定的权限,则返回 true。

public class MkPermissionUtil {
    private static final SimpleArrayMap<String, Integer> MIN_SDK_PERMISSIONS;
    private static volatile int targetSdkVersion = -1;
    static {
        MIN_SDK_PERMISSIONS = new SimpleArrayMap<>(8);
        MIN_SDK_PERMISSIONS.put(permission.ADD_VOICEMAIL, VERSION_CODES.ICE_CREAM_SANDWICH);
        MIN_SDK_PERMISSIONS.put(permission.BODY_SENSORS, VERSION_CODES.KITKAT_WATCH);
        MIN_SDK_PERMISSIONS.put(permission.READ_CALL_LOG, VERSION_CODES.JELLY_BEAN);
        MIN_SDK_PERMISSIONS.put(permission.READ_EXTERNAL_STORAGE, VERSION_CODES.JELLY_BEAN);
        MIN_SDK_PERMISSIONS.put(permission.USE_SIP, VERSION_CODES.GINGERBREAD);
        MIN_SDK_PERMISSIONS.put(permission.WRITE_CALL_LOG, VERSION_CODES.JELLY_BEAN);
        MIN_SDK_PERMISSIONS.put(permission.SYSTEM_ALERT_WINDOW, VERSION_CODES.M);
        MIN_SDK_PERMISSIONS.put(permission.WRITE_SETTINGS, VERSION_CODES.M);
    }
    public static void requestPermission(FragmentActivity activity,
                                         int requestCode, OnMkPermissionListener listener, String... permission) {
        if (activity == null) {
            return;
        }
        if (checkPermissionsMarshmallow(activity, permission)) {
            listener.onMkGranted(requestCode, permission);
            return;
        }
        MkDialogFragment fragment = MkDialogFragment.newInstance();
        fragment.requestAllPermissions(activity, requestCode, listener, permission);
    }
    private static boolean checkPermissionsMarshmallow(FragmentActivity activity,
                                                       String... permissions) {
        return VERSION.SDK_INT < 23 || hasSelfPermissions(activity, permissions);
    }
    @Deprecated
    @TargetApi(VERSION_CODES.KITKAT)
    public static boolean checkPermissionKitkat(Context context, int op) {
        if (context == null || context.getPackageName() == null) {
            return false;
        }
        AppOpsManager mAppOps = (AppOpsManager) context.getSystemService(Context.APP_OPS_SERVICE);
        ApplicationInfo appInfo = context.getApplicationInfo();
        if (appInfo == null || mAppOps == null) {
            return false;
        }
        try {
            Class sCheckClass = Class.forName(AppOpsManager.class.getName());
            Method sCheckMethod = sCheckClass.getDeclaredMethod("checkOpNoThrow", Integer.TYPE, Integer.TYPE, String.class);
            sCheckMethod.setAccessible(true);
            return ((Integer) sCheckMethod.invoke(mAppOps, op, appInfo.uid, context.getPackageName()) == AppOpsManager.MODE_ALLOWED);
        } catch (Exception e) {
            Log.e("checkPermissionKitkat", e.getMessage() + "");
        }
        return true;
    }
    public static boolean verifyPermissions(int... grantResults) {
        if (grantResults.length == 0) {
            return false;
        }
        for (int result : grantResults) {
            if (result != PackageManager.PERMISSION_GRANTED) {
                return false;
            }
        }
        return true;
    }
    private static boolean permissionExists(String permission) {
        // 检查此设备上是否可能缺少权限
        Integer minVersion = MIN_SDK_PERMISSIONS.get(permission);
        // 如果从上述调用返回 null,则无需检查设备 API 级别的权限;
        // 否则,我们检查其最低 API 级别要求是否得到满足
        return minVersion == null || VERSION.SDK_INT >= minVersion;
    }
    public static boolean hasSelfPermissions(Context context, String... permissions) {
        for (String permission : permissions) {
            if (permissionExists(permission) && !hasSelfPermission(context, permission)) {
                return false;
            }
        }
        return true;
    }
    @SuppressLint("WrongConstant")
    private static boolean hasSelfPermission(Context context, String permission) {
        try {
            return PermissionChecker.checkSelfPermission(context, permission) == PackageManager.PERMISSION_GRANTED;
        } catch (RuntimeException t) {
            return false;
        }
    }
    public static boolean shouldShowRequestPermissionRationale(Activity activity, String... permissions) {
        for (String permission : permissions) {
            if (ActivityCompat.shouldShowRequestPermissionRationale(activity, permission)) {
                return true;
            }
        }
        return false;
    }
    public static int getTargetSdkVersion(Context context) {
        if (targetSdkVersion != -1) {
            return targetSdkVersion;
        }
        try {
            PackageInfo packageInfo = context.getPackageManager().getPackageInfo(context.getPackageName(), 0);
            targetSdkVersion = packageInfo.applicationInfo.targetSdkVersion;
        } catch (PackageManager.NameNotFoundException ignored) {
            Log.e("getTargetSdkVersion", ignored.getMessage() + "");
        }
        return targetSdkVersion;
    }
    public static void runOnUiThread(final Runnable runnable) {
        if (Looper.myLooper() == Looper.getMainLooper()) {
            runnable.run();
        } else {
            new Handler(Looper.getMainLooper()).post(runnable);
        }
    }
    private static boolean isIntentAvailable(Context context, final Intent intent) {
        return context.getPackageManager()
                .queryIntentActivities(intent, PackageManager.MATCH_DEFAULT_ONLY)
                .size() > 0;
    }
    public static void launchAppDetailsSettings(Context context) {
        Intent intent = new Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS);
        intent.setData(Uri.parse("package:" + context.getPackageName()));
        if (!isIntentAvailable(context, intent)) {
            return;
        }
        context.startActivity(intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK));
    }
}

3. 自定义权限申请Dialog

public class MkToGoSettingDialog extends AlertDialog {
    protected MkToGoSettingDialog(@NonNull Context context) {
        super(context, R.style.permission_dialog_style);
    }
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        initDialogStyle();
        View rootView = LayoutInflater.from(getContext()).inflate(R.layout.dialog_mk_permission_layout, null);
        setContentView(rootView);
        initView(rootView);
    }
    private void initDialogStyle() {
        Window window = getWindow();
        if (window != null) {
            window.clearFlags(WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM);
            window.setFeatureDrawableAlpha(Window.FEATURE_OPTIONS_PANEL, 0);
            WindowManager.LayoutParams params = window.getAttributes();
            // 设置宽度为全屏
            params.width = ViewGroup.LayoutParams.WRAP_CONTENT;
            params.height = ViewGroup.LayoutParams.WRAP_CONTENT;
            window.setGravity(Gravity.CENTER);
            window.setAttributes(params);
            setCancelable(true);
            setCanceledOnTouchOutside(true);
            // 设置窗口弹出动画
            window.setWindowAnimations(R.style.mk_bottom_inout_animation);
        }
    }
    private void initView(View rootView) {
        Button btnCancel = rootView.findViewById(R.id.btn_cancel);
        Button btnSure = rootView.findViewById(R.id.btn_sure);
        btnCancel.setText(R.string.permission_sanity_dialog_cancel);
        btnSure.setText(R.string.permission_sanity_dialog_open);
        TextView titleTextContent = rootView.findViewById(R.id.title_text_content);
        TextView titleTextNotice = rootView.findViewById(R.id.title_text_notice);
        titleTextNotice.setText(R.string.permission_sanity_dialog_title);
        titleTextContent.setText(R.string.permission_sanity_dialog_description);
        btnCancel.setOnClickListener(view -> dismiss());
        btnSure.setOnClickListener(view -> {
           MkPermissionUtil.launchAppDetailsSettings(getContext());
            dismiss();
        });
    }
}

4. 权限申请帮助类

public class MkPermissionHelper {
    public static void requestPermission(Activity context, OnMkPermissionListener permissionListener, String... permissions) {
        MkPermissionUtil.requestPermission((FragmentActivity) context, 0, new OnMkPermissionListener() {
                @Override
                public void onMkGranted(int requestCode, String... permissions) {
                    permissionListener.onMkGranted(requestCode, permissions);
                }
                @Override
                public void onMkDenied(int requestCode, Map<String, Integer> result) {
                    permissionListener.oMkDenied(requestCode, result);
                }
                @Override
                public void onMkNeverAsk(int requestCode, Map<String, Integer> result) {
                    MkToGoSettingDialog mkPermissionDialog = new MkToGoSettingDialog(context);
                    mkPermissionDialog.show();
                    permissionListener.onMkNeverAsk(requestCode, result);
                }
            }, permissions);
    }
}


相关文章
|
7天前
|
存储 安全 Android开发
"解锁Android权限迷宫:一场惊心动魄的动态权限请求之旅,让你的应用从平凡跃升至用户心尖的宠儿!"
【8月更文挑战第13天】随着Android系统的更新,权限管理变得至关重要。尤其从Android 6.0起,引入了动态权限请求,增强了用户隐私保护并要求开发者实现更精细的权限控制。本文采用问答形式,深入探讨动态权限请求机制与最佳实践,并提供示例代码。首先解释了动态权限的概念及其重要性;接着详述实现步骤:定义、检查、请求权限及处理结果;最后总结了六大最佳实践,包括适时请求、解释原因、提供替代方案、妥善处理拒绝情况、适应权限变更及兼容旧版系统,帮助开发者打造安全易用的应用。
17 0
|
2月前
|
Android开发
Android中如何动态的调整Dialog的背景深暗
在Android开发中,Dialog和DialogFragment可通过设置`Window`的`backgroundDimAmount`来控制背景变暗,突出对话框。在DialogFragment的`onCreateDialog`或`onViewCreated`中,获取`Dialog`的`Window`,设置`LayoutParams.dimAmount`(例如0.5f)并添加`FLAG_DIM_BEHIND`标志。要动态调整,可保存`LayoutParams`并在需要时更新。对于Dialog,创建时直接设置同样属性。还可以通过定义主题样式设置背景模糊程度。
42 7
|
2月前
|
XML Java Android开发
Android RecyclerView用代码动态设置item的selector
Android RecyclerView用代码动态设置item的selector
27 0
|
3月前
|
Java Shell Android开发
android 权限申请
android 权限申请
85 5
|
3月前
|
XML Java Android开发
Android控件动态使用 (转)
Android控件动态使用 (转)
22 1
|
3月前
|
存储 Java API
Android系统 文件访问权限笔记
Android系统 文件访问权限笔记
352 1
|
3月前
|
测试技术 Android开发 开发者
RK3568 Android系统客制化动态替换ro任意属性
RK3568 Android系统客制化动态替换ro任意属性
126 1
|
3月前
|
Android开发
Android Jetpack架构开发组件化应用实战,字节跳动+阿里+华为+腾讯等大厂Android面试题
Android Jetpack架构开发组件化应用实战,字节跳动+阿里+华为+腾讯等大厂Android面试题
|
3月前
|
Android开发
Android热补丁动态修复实践,腾讯&字节&网易&华为Android面试题分享
Android热补丁动态修复实践,腾讯&字节&网易&华为Android面试题分享
|
3月前
|
XML API 数据库
Android权限
Android权限 【5月更文挑战第3天】
46 0