Foreword
之前总是有小伙伴问 AndroidUtilCode 中有没有权限工具类,但都被我怼回去了,让先用着其他第三方的,不过,到了如今的 1.11.0 版本的 AndroidUtilCode,这个一直拖欠着的权限工具类总算要问世了,以后小伙伴们如果用 AndroidUtilCode 需要动态授权的话,就不用再多依赖一个第三方库了,下面来介绍下其功能。
Functions
2018-01-10兼容安卓各版本,包括 Android 8.0
支持任意地方申请权限,不仅限于 Activity 和 Fragment 等
支持多权限同时申请
采用链式调用,一句话解决权限申请
Achieve
首先来介绍其实现方式,关于运行时权限的介绍可以在官网查看 -> 传送门。关于危险权限列表,我封装危险权限常量类 PermissionConstants.java,代码如下所示:
import android.Manifest; import android.Manifest.permission; import android.annotation.SuppressLint; import android.support.annotation.StringDef; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; /** * <pre> * author: Blankj * blog : http://blankj.com * time : 2017/12/29 * desc : 权限相关常量 * </pre> */ @SuppressLint("InlinedApi") public final class PermissionConstants { public static final String CALENDAR = Manifest.permission_group.CALENDAR; public static final String CAMERA = Manifest.permission_group.CAMERA; public static final String CONTACTS = Manifest.permission_group.CONTACTS; public static final String LOCATION = Manifest.permission_group.LOCATION; public static final String MICROPHONE = Manifest.permission_group.MICROPHONE; public static final String PHONE = Manifest.permission_group.PHONE; public static final String SENSORS = Manifest.permission_group.SENSORS; public static final String SMS = Manifest.permission_group.SMS; public static final String STORAGE = Manifest.permission_group.STORAGE; private static final String[] GROUP_CALENDAR = { permission.READ_CALENDAR, permission.WRITE_CALENDAR }; private static final String[] GROUP_CAMERA = { permission.CAMERA }; private static final String[] GROUP_CONTACTS = { permission.READ_CONTACTS, permission.WRITE_CONTACTS, permission.GET_ACCOUNTS }; private static final String[] GROUP_LOCATION = { permission.ACCESS_FINE_LOCATION, permission.ACCESS_COARSE_LOCATION }; private static final String[] GROUP_MICROPHONE = { permission.RECORD_AUDIO }; private static final String[] GROUP_PHONE = { permission.READ_PHONE_STATE, permission.READ_PHONE_NUMBERS, permission.CALL_PHONE, permission.ANSWER_PHONE_CALLS, permission.READ_CALL_LOG, permission.WRITE_CALL_LOG, permission.ADD_VOICEMAIL, permission.USE_SIP, permission.PROCESS_OUTGOING_CALLS }; private static final String[] GROUP_SENSORS = { permission.BODY_SENSORS }; private static final String[] GROUP_SMS = { permission.SEND_SMS, permission.RECEIVE_SMS, permission.READ_SMS, permission.RECEIVE_WAP_PUSH, permission.RECEIVE_MMS, }; private static final String[] GROUP_STORAGE = { permission.READ_EXTERNAL_STORAGE, permission.WRITE_EXTERNAL_STORAGE }; @StringDef({CALENDAR, CAMERA, CONTACTS, LOCATION, MICROPHONE, PHONE, SENSORS, SMS, STORAGE,}) @Retention(RetentionPolicy.SOURCE) public @interface Permission { } public static String[] getPermissions(@Permission final String permission) { switch (permission) { case CALENDAR: return GROUP_CALENDAR; case CAMERA: return GROUP_CAMERA; case CONTACTS: return GROUP_CONTACTS; case LOCATION: return GROUP_LOCATION; case MICROPHONE: return GROUP_MICROPHONE; case PHONE: return GROUP_PHONE; case SENSORS: return GROUP_SENSORS; case SMS: return GROUP_SMS; case STORAGE: return GROUP_STORAGE; } return new String[]{permission}; } }
为了适配 Android 8.0,我在申请权限的时候,会把清单文件中使用到的同组的权限都一次性申请完毕,相关代码如下所示:
private static final List<String> PERMISSIONS = getPermissions(); /** * 获取应用权限 * * @return 清单文件中的权限列表 */ public static List<String> getPermissions() { return getPermissions(Utils.getApp().getPackageName()); } /** * 获取应用权限 * * @param packageName 包名 * @return 清单文件中的权限列表 */ public static List<String> getPermissions(final String packageName) { PackageManager pm = Utils.getApp().getPackageManager(); try { return Arrays.asList( pm.getPackageInfo(packageName, PackageManager.GET_PERMISSIONS) .requestedPermissions ); } catch (PackageManager.NameNotFoundException e) { e.printStackTrace(); return Collections.emptyList(); } } /** * 设置请求权限 * * @param permissions 要请求的权限 * @return {@link PermissionUtils} */ public static PermissionUtils permission(@Permission final String... permissions) { return new PermissionUtils(permissions); } private PermissionUtils(final String... permissions) { mPermissions = new LinkedHashSet<>(); for (String permission : permissions) { for (String aPermission : PermissionConstants.getPermissions(permission)) { if (PERMISSIONS.contains(aPermission)) { mPermissions.add(aPermission); } } } sInstance = this; }
为了支持任意地方都可以申请权限,我在 PermissionUtils.java 中封装了 PermissionActivity,源码如下所示:
@RequiresApi(api = Build.VERSION_CODES.M) public static class PermissionActivity extends Activity { public static void start(final Context context) { Intent starter = new Intent(context, PermissionActivity.class); starter.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); context.startActivity(starter); } @Override protected void onCreate(@Nullable Bundle savedInstanceState) { if (sInstance.mThemeCallback != null) { sInstance.mThemeCallback.onActivityCreate(this); } else { Window window = getWindow(); window.setBackgroundDrawable(null); int option = View.SYSTEM_UI_FLAG_LAYOUT_STABLE | View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN; window.getDecorView().setSystemUiVisibility(option); window.setStatusBarColor(Color.TRANSPARENT); } super.onCreate(savedInstanceState); if (sInstance.rationale(this)) { finish(); return; } if (sInstance.mPermissionsRequest != null) { int size = sInstance.mPermissionsRequest.size(); requestPermissions(sInstance.mPermissionsRequest.toArray(new String[size]), 1); } } @Override public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) { sInstance.onRequestPermissionsResult(this); finish(); } }
这样我们便可以自己全权处理权限请求,但启动的这个 PermissionActivity 的主题并不一定符合小伙伴们应用的 Activity 相关主题,所以我留了个设置主题的回调接口,这样便可无感知地启动一个 Activity,如果没有设置主题回调的话就默认是透明状态栏和透明背景,这个应该能适配很多应用了。
当然,如果有设置 rationale 的话,也就是设置拒绝权限后再次请求的回调接口,此时便会走 sInstance.rationale(this)
,具体代码如下所示:
@RequiresApi(api = Build.VERSION_CODES.M) private boolean rationale(final Activity activity) { boolean isRationale = false; if (mOnRationaleListener != null) { for (String permission : mPermissionsRequest) { if (activity.shouldShowRequestPermissionRationale(permission)) { getPermissionsStatus(activity); mOnRationaleListener.rationale(new ShouldRequest() { @Override public void again(boolean again) { if (again) { startPermissionActivity(); } else { requestCallback(); } } }); isRationale = true; break; } } mOnRationaleListener = null; } return isRationale; }
逻辑就是如果 rationale 回调接口 执行了 shouldRequest.again(true);
,那么就会继续申请下去,反之则不再申请,多用在弹出一个提示对话框来让用户选择是否继续请求权限。
最终就是发起请求和接受请求,并把最终状态保存到 mPermissionsGranted
、mPermissionsDenied
和 mPermissionsDeniedForever
中,最终回调 callback 的接口,相关代码如下所示:
private void getPermissionsStatus(final Activity activity) { for (String permission : mPermissionsRequest) { if (isGranted(permission)) { mPermissionsGranted.add(permission); } else { mPermissionsDenied.add(permission); if (!activity.shouldShowRequestPermissionRationale(permission)) { mPermissionsDeniedForever.add(permission); } } } } private void requestCallback() { if (mSimpleCallback != null) { if (mPermissionsRequest.size() == 0 || mPermissions.size() == mPermissionsGranted.size()) { mSimpleCallback.onGranted(); } else { if (!mPermissionsDenied.isEmpty()) { mSimpleCallback.onDenied(); } } mSimpleCallback = null; } if (mFullCallback != null) { if (mPermissionsRequest.size() == 0 || mPermissions.size() == mPermissionsGranted.size()) { mFullCallback.onGranted(mPermissionsGranted); } else { if (!mPermissionsDenied.isEmpty()) { mFullCallback.onDenied(mPermissionsDeniedForever, mPermissionsDenied); } } mFullCallback = null; } mOnRationaleListener = null; mThemeCallback = null; } private void onRequestPermissionsResult(final Activity activity) { getPermissionsStatus(activity); requestCallback(); }
Use
说了那么多,总算到使用了,其实使用起来非常方便,一句话即可,比如我们要申请 android.permission.READ_CALENDAR
权限,那么我们可以去 PermissionConstants.java 中找到其所属组,也就是 CALENDAR,而应用是全屏类型的应用,那么我们可以像下面这样发起请求。
PermissionUtils.permission(PermissionConstants.CALENDAR) .rationale(new PermissionUtils.OnRationaleListener() { @Override public void rationale(final ShouldRequest shouldRequest) { PermissionHelper.showRationaleDialog(shouldRequest); } }) .callback(new PermissionUtils.FullCallback() { @Override public void onGranted(List<String> permissionsGranted) { updateAboutPermission(); } @Override public void onDenied(List<String> permissionsDeniedForever, List<String> permissionsDenied) { if (!permissionsDeniedForever.isEmpty()) { PermissionHelper.showOpenAppSettingDialog(); } LogUtils.d(permissionsDeniedForever, permissionsDenied); } }) .theme(new PermissionUtils.ThemeCallback() { @Override public void onActivityCreate(Activity activity) { ScreenUtils.setFullScreen(activity); } }) .request();
如果还有不会的可以参考 AndroidUtilCode 中的 demo -> PermissionActivity.java
Tips:推荐小伙伴们最好把权限请求相关的操作都放在一个 helper 类中,就像我 AndroidUtilCode 中 demo 的做法,创建一个 PermissionHelper.java,毕竟有很多权限请求都是重复。
Conclusion
好了,本次的权限工具类介绍就到此结束了,在这么简洁的工具类背后都是本柯基辛勤付出的汗水,疯狂地 debug,疯狂地测试来消除内存泄漏的问题,虽然路途很艰辛,但最终还是成功地完成了该工具类,终于等到你。
作者:blankj
链接:https://juejin.im/post/5a55867af265da3e303c5fb3