阅读 596

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

改不完的 Bug,写不完的矫情。公众号 杨正友 现在专注移动基础开发 ,涵盖音视频和 APM,信息安全等各个知识领域;只做全网最 Geek 的公众号,欢迎您的关注!

权限组件设计背景

历史版本权限组件使用的是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);

    }
}
复制代码