改不完的 Bug,写不完的矫情。公众号 杨正友 现在专注移动基础开发 ,涵盖音视频和 APM,信息安全等各个知识领域;只做全网最 Geek 的公众号,欢迎您的关注!
权限组件设计背景
历史版本权限组件使用的是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);
}
}