申明:低级码农问题解决中的参考和解决后的小结,仅用于个人记录,能力有限,可能有些错误,缺陷不自知,欢迎在评论中指正,谢谢!
从Android 6.0(M版本,api 23)开始,要求动态申请权限。Google将权限分为两类,一类是普通权限,使用时不需要用户授权;另一类是危险权限,使用时需要判断用户是否对该权限授权。如果没有授权,需要动态申请。在没有用户授权的情况下,直接使用该权限,系统将会抛出异常。我们一般说的应用需要某权限,实际是指用户是否同意应用使用该权限。
危险权限有25种,按照权限组可划分如下,:
group:android.permission-group.CONTACTS permission:android.permission.WRITE_CONTACTS permission:android.permission.GET_ACCOUNTS permission:android.permission.READ_CONTACTS group:android.permission-group.PHONE permission:android.permission.READ_CALL_LOG permission:android.permission.READ_PHONE_STATE permission:android.permission.CALL_PHONE permission:android.permission.WRITE_CALL_LOG permission:android.permission.USE_SIP permission:android.permission.PROCESS_OUTGOING_CALLS permission:com.android.voicemail.permission.ADD_VOICEMAIL group:android.permission-group.CALENDAR permission:android.permission.READ_CALENDAR permission:android.permission.WRITE_CALENDAR group:android.permission-group.CAMERA permission:android.permission.CAMERA group:android.permission-group.SENSORS permission:android.permission.BODY_SENSORS group:android.permission-group.LOCATION permission:android.permission.ACCESS_FINE_LOCATION GPS定位 permission:android.permission.ACCESS_COARSE_LOCATION wifi,基站定位 group:android.permission-group.STORAGE permission:android.permission.READ_EXTERNAL_STORAGE permission:android.permission.WRITE_EXTERNAL_STORAGE group:android.permission-group.MICROPHONE permission:android.permission.RECORD_AUDIO group:android.permission-group.SMS permission:android.permission.READ_SMS permission:android.permission.RECEIVE_WAP_PUSH permission:android.permission.RECEIVE_MMS permission:android.permission.RECEIVE_SMS permission:android.permission.SEND_SMS permission:android.permission.READ_CELL_BROADCASTS
如果应用申请某一种权限,得到用户同意后,那么同组的其他权限,还需要重新申请并同意。拒绝了,也不印象对其他权限的同意。(在meizu X8验证)
使用权限,需要在AndroidManifest中注册。如果没有注册,那么判断权限的方法将返回DENIED,如果去申请该项权限,也不会有权限对话框弹出。
常用的方法
/** 判断是否具有某一项权限 */
int checkSelfPermission(String permission)
/** 申请多个权限 */
void requestPermissions(@NonNull String[] permissions, int requestCode)
/** 返回申请的权限的授权情况 */
void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults)
申请和返回授权中的参数requestCode是对应的,这是用来区分权限申请的场景的。一般情况下,一个Activity只有一种申请单一权限的场景,但也有一个Activity有多个申请单一权限的场景。比如Activity具有多个Fragment,B、C都需要定位权限,在Fragment中检查和申请权限更好一些,但只能有Activity接收授权情况。所以B、C申请时,传入不同的requestCode,返回授权结果方法时,可以根据requestCode判断,是哪个Fragment申请的权限。还有BrowserActivity,不同网页申请同意权限的情况更多了,如果某个网页需要特殊处理,也是用requestCode区分。
protected static boolean checkRequestMultiPermissions(Activity activity, String... permissions) { if (Build.VERSION.SDK_INT > Build.VERSION_CODES.M) { ArrayList<String> deniedPermissions = new ArrayList<>(); // 过滤出需要但是没有得到授权的权限,放入deniedPermissions for(String permission: permissions) { int permissionState = activity.checkSelfPermission(permission); if (permissionState == PackageManager.PERMISSION_DENIED) { deniedPermissions.add(permission); } } int deniedPermissionCount = deniedPermissions.size(); if (deniedPermissionCount == 0) { return true; } else { String[] deniedPermissionArray = deniedPermissions.toArray(new String[0]); int deniedPermissionCode = 0; for (String permission: deniedPermissionArray) { deniedPermissionCode |= permission2CodeMap.get(permission).intValue(); } activity.requestPermissions(deniedPermissionArray, deniedPermissionCode); return false; } } else { return true; } } /** 这个方法运行与Activity中,用户接收授权情况 */ @Override public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) { ArrayList<String> deniedPermissions = new ArrayList<>(); for (int i = 0, size = permissions.length; i < size; i++) { if (grantResults[i] == PackageManager.PERMISSION_DENIED) { deniedPermissions.add(permissions[i]); } } if (deniedPermissions.size() == 0) { // 所有权限都被同意 } else { // deniedPermissions是申请但是不被授权的权限 } return; }
个人心得
关于requestCode,以我在实际项目中遇到的,会定义很多魔鬼数字权限码,不便于项目的阅读和维护。比如两个Activity都需要定位权限,两个Activity各自定义了权限码,还是不同的数字;然后就是同一个页面需要多个权限(比如应用启动需要存储、IMEI、传感器等)的组合,可能产生很多种requestCode,多人维护的项目不敢乱动代码,导致在不同Activity可以复用的requestCode被定义了多个。所有我有一个方案,用于优化无意义,无限制的定义requestCode,虽不能根本解决,但可以极大程度的缓解。这个方案是为每个权限(指需要动态申请的危险权限,后同)申明一个对应的数字,通过做位运算组合成多种权限的场景,这就要求数字必须是2的n次方形式,即0b10,0b100,0b1000的形式。然后申请权限时,只需要传入权限字符串即可,会生成对应的权限码。
优势,不足和对不足的规避方案。
1、requestCode是int型,取值范围是-2^32 ~ 2^32 - 1,除去负数,然后0表示没有权限,则实际可用的只有30位,而危险权限有25种,不排除后续继续增加,所以用来表示的场景有2^5。因为场景不可能出现组合的情况,所以增加方式是+1,而不是位移(<)。
——适用于我自己的小项目,2^5种场景基本够用了。再就是diy的小项目肯定不会用到全部的权限,给16位到24位就满足绝大多数场景了。
2、针对Activity定义场景,在onRequestPermissionsResult方法中,尽量走默认场景的统一处理:权限被禁则弹框。如果有特殊处理,就要先判断权限,后判断场景,但这个过程跟Activity强相关,即只有极少数Activity有这样的场景。
下面是我场景的代码,这是Utils类:

public class PermissionUtils { /** 权限组下有多个权限,一个权限被获取了,则改组的所有权限都获取。有25个危险权限,我们定义24个,忽略一个,留个小坑 */ public static final int PERMISSION_CODE_CALENDAR = 0b1; public static final int PERMISSION_CODE_CAMERA = PERMISSION_CODE_CALENDAR << 1; public static final int PERMISSION_CODE_CONTACTS = PERMISSION_CODE_CAMERA << 1; public static final int PERMISSION_CODE_LOCATION = PERMISSION_CODE_CONTACTS << 1; public static final int PERMISSION_CODE_MICROPHONE = PERMISSION_CODE_LOCATION << 1; public static final int PERMISSION_CODE_READ_PHONE_STATE = PERMISSION_CODE_MICROPHONE << 1; public static final int PERMISSION_CODE_CALL_PHONE = PERMISSION_CODE_READ_PHONE_STATE << 1; public static final int PERMISSION_CODE_READ_CALL_LOG = PERMISSION_CODE_CALL_PHONE << 1; public static final int PERMISSION_CODE_SENSORS = PERMISSION_CODE_READ_CALL_LOG << 1; public static final int PERMISSION_CODE_SMS = PERMISSION_CODE_SENSORS << 1; public static final int PERMISSION_CODE_STORAGE = PERMISSION_CODE_SMS << 1; /** 有25个危险权限,我们定义24个,忽略一个,留个小坑 */ public static final int PERMISSION_CODE_ALL = (1<<24) - 1; // 24个1 private static final Map<String, Integer> permission2CodeMap = new HashMap<>(); private static final Map<String, String> permission2zhMap = new HashMap<>(); static { permission2CodeMap.put(Manifest.permission.READ_CALENDAR, PERMISSION_CODE_CALENDAR); permission2CodeMap.put(Manifest.permission.READ_PHONE_STATE, PERMISSION_CODE_READ_PHONE_STATE); permission2CodeMap.put(Manifest.permission.READ_CALL_LOG, PERMISSION_CODE_CALL_PHONE); permission2CodeMap.put(Manifest.permission.READ_EXTERNAL_STORAGE, PERMISSION_CODE_STORAGE); permission2CodeMap.put(Manifest.permission.WRITE_EXTERNAL_STORAGE, PERMISSION_CODE_STORAGE); permission2zhMap.put(Manifest.permission.READ_CALENDAR, App.getAppContext().getString(R.string.permission_read_calendar)); permission2zhMap.put(Manifest.permission.READ_PHONE_STATE, App.getAppContext().getString(R.string.permission_read_phone_state)); permission2zhMap.put(Manifest.permission.READ_CALL_LOG, App.getAppContext().getString(R.string.permission_read_call_log)); permission2zhMap.put(Manifest.permission.WRITE_EXTERNAL_STORAGE, App.getAppContext().getString(R.string.permission_write_sdcard)); } protected static int getPermissionCode(String permission) { return permission2CodeMap.get(permission); } protected static String getPermissionZh(String permission) { return permission2zhMap.get(permission); } protected static @NonNull String[] getPermissionsZh(String... permissions) { int size = permissions.length; if (size <= 0) { return null; } String[] permissionsZh = new String[size]; for (int i = 0; i < size; i++) { permissionsZh[i] = permission2zhMap.get(i); } return permissionsZh; } /** * 检查是否有权限,如果没有,则申请。没有考虑场景 * @param activity * @param permission * @return */ protected static boolean checkRequestSinglePermission(Activity activity, String permission) { if (Build.VERSION.SDK_INT > Build.VERSION_CODES.M) { int permissionState = activity.checkSelfPermission(permission); if (permissionState == PackageManager.PERMISSION_GRANTED) { return true; } else { activity.requestPermissions(new String[]{permission}, permission2CodeMap.get(permission)); return false; } } else { return true; } } protected static boolean checkRequestMultiPermissions(Activity activity, String... permissions) { return checkRequestMultiPermissions(activity, 0, permissions); } /** * @param scene 场景 */ protected static boolean checkRequestMultiPermissions(Activity activity, int scene, String... permissions) { if (Build.VERSION.SDK_INT > Build.VERSION_CODES.M) { ArrayList<String> deniedPermissions = new ArrayList<>(); for(String permission: permissions) { int permissionState = activity.checkSelfPermission(permission); if (permissionState == PackageManager.PERMISSION_DENIED) { deniedPermissions.add(permission); } } int deniedPermissionCount = deniedPermissions.size(); if (deniedPermissionCount == 0) { return true; } else { String[] deniedPermissionArray = deniedPermissions.toArray(new String[0]); int deniedPermissionCode = 0; for (String permission: deniedPermissionArray) { deniedPermissionCode |= permission2CodeMap.get(permission).intValue(); } activity.requestPermissions(deniedPermissionArray, deniedPermissionCode | scene/*拼接场景和权限码*/); return false; } } else { return true; } } public static final int SCENE_FLAG = 0b1000000000000000000000000; // 2^24,requestCode大于这个值,说明带场景了 public static final int SCENE_MAX = 0b11111111000000000000000000000000; public static boolean isDefaultScene(int requestCode) { return requestCode < SCENE_FLAG; } public static int getScene(int requestCode) { return requestCode & SCENE_MAX; } public static int getPermissionCodeIgnoreScene(int requestCode) { return requestCode & PERMISSION_CODE_ALL; } }
接收处的处理,从requestCode解析出场景和权限
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) { if (PermissionUtils.isDefaultScene(requestCode)) { /* 默认场景 */ ArrayList<String> deniedPermissions = new ArrayList<>(); for (int i = 0, size = permissions.length; i < size; i++) { if (grantResults[i] == PackageManager.PERMISSION_DENIED) { deniedPermissions.add(permissions[i]); } } if (deniedPermissions.size() == 0) { startActivity(new Intent(this, MainActivity.class)); } else { showPermissionDialog(deniedPermissions); } return; } else { /* 自定义的特殊场景,解析出场景和权限 */ int permissionCode = PermissionUtils.getPermissionCodeIgnoreScene(requestCode); int scene = PermissionUtils.getScene(requestCode); } }