权限组件设计背景
历史版本权限组件使用的是AndPermission,长期无人维护,历史代码臃肿,不便拓展,考虑使用PermissionsDispatcher,但是PermissionsDispatcher APT插件会影响编译效率,easypermissions侵入性太强,会影响整个工程,RxPermissions 貌似是最佳选择,但是RxPermissions需要高度自定义符合自己项目特色的UI,所以干脆自己写个权限组件好了,希望大家喜欢
权限组件设计需求
自定义炫酷权限弹框
不入侵工程Activity
权限注入符合自定义规则
权限组件设计原理
检查所提供的权限是否被申请
- 检查此设备上是否可能缺少权限
- 如果 Activity 或 Fragment 有权访问所有给定的权限,不处理
- 如果API在23以下,检测到了,你也没有主动的触发手段,就不处理,否则检测到了,否则弹窗
- 如果当前权限被拒绝了,那么触发了权限申请就进入setting页面
- 在构建DialogFragment 的 onRequestPermissionsResult 方法里面去做权限申请工作,申请完毕需要移除Fragment
- 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); } }