申明:低級碼農問題解決中的參考和解決后的小結,僅用於個人記錄,能力有限,可能有些錯誤,缺陷不自知,歡迎在評論中指正,謝謝!
從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); } }
