自從Android6.0發布以來,在權限上做出了很大的變動,不再是之前的只要在manifest設置就可以任意獲取權限,而是更加的注重用戶的隱私和體驗,不會再強迫用戶因拒絕不該擁有的權限而導致的無法安裝的事情,也不會再不征求用戶授權的情況下,就可以任意的訪問用戶隱私,而且即使在授權之后也可以及時的更改權限。這就是6.0版本做出的更擁護和注重用戶的一大體現。
1 andriod6.0權限
andriod6.0系統把權限分為兩個級別:
一個是Normal Permissions,即普通權限,這類權限不會潛藏有侵害用戶隱私和安全的問題,比如,訪問網絡的權限,訪問WIFI的權限等;
另一類是Dangerous Permissions,即危險權限,這類權限會直接的威脅到用戶的安全和隱私問題,比如說訪問短信,相冊等權限。
具體普通權限和危險權限參考:https://www.cnblogs.com/wwjldm/p/6932083.html
權限組:普通權限是單條的權限,而危險權限是以組展示的。所有危險的權限都屬於權限組。也就是說,當你接受一個危險權限時,它所在的這個組里面的其他所有訪問權限也將會被自動獲取權限。當然,這類危險權限也是需要在manifest中注冊的,否則動態申請會失敗。
實際測試:在manifest中添加讀取短信權限,屬於危險權限,在沒有做相應處理時,觀察是否起作用。
targetSdkVersion>=23 ,真機>=23 ,出現權限異常
targetSdkVersion<23 ,真機>=23 ,正常訪問
targetSdkVersion<23 ,真機<23 ,正常訪問
targetSdkVersion>=23 ,真機<23 ,正常訪問
結論:當真機系統版本小於6.0時,權限在manifest聲明了,就可以正常訪問。當真機系統版本不小於6.0且targetSdkVersion>=23時,需要動態申請權限。手機不斷的在升級,版本會越來越高,應用不可能一直只支持低版本(6.0以下)而不支持高版本,所以APP權限升級是必然的。
2 android 8.0 權限策略變化
在 Android 8.0 (targetSdkVersion 26)之前,如果應用在運行時請求權限並且被授予該權限,系統會錯誤地將屬於同一權限組並且在清單中注冊的其他權限也一起授予應用。對於針對 Android 8.0 的應用,此行為已被糾正。系統只會授予應用明確請求的權限。然而,一旦用戶為應用授予某個權限,則所有后續對該權限組中權限的請求都將被自動批准。例如,假設某個應用在其清單中列出 READ_EXTERNAL_STORAGE 和 WRITE_EXTERNAL_STORAGE。應用請求 READ_EXTERNAL_STORAGE,並且用戶授予了該權限。
如果該應用針對的是 API 級別 24 或更低級別,系統還會同時授予 WRITE_EXTERNAL_STORAGE,因為該權限也屬於同一 STORAGE 權限組並且也在清單中注冊過。
如果該應用針對的是 Android 8.0,則系統此時僅會授予 READ_EXTERNAL_STORAGE;不過,如果該應用后來又請求 WRITE_EXTERNAL_STORAGE,則系統會立即授予該權限,而不會提示用戶。如果不申請,會拋異常。
小結:
- 以前,申請一個子權限會自動獲取權限組中其他子權限。組內其他子權限可以直接使用。
- 現在,申請一個子權限,組內其他子權限不會自動獲取。使用組內其他子權限的時候。需要再次申請。(但是這種情況不會彈出系統的權限申請框)如果不申請。會FC。
- 同組權限一起申請。當我們申請權限時。申請同組的多個權限時,也只會彈出一次申請框。所以建議一起申請。
以下原因不會彈框(不僅包括)
- 6.0以下版本(系統自動申請)
- 暫時發現vivo、oppo、魅族的6.0以上版本
因為這些廠商修改了6.0系統申請機制,他們修改成系統自動申請權限了。也就是說這些系統會跟以前 6.0 以下的版本一樣,需要用到權限的時候系統會自動申請,就算我們主動申請也是沒用的
3 動態申請權限
使用步驟
1 清單配置需要的權限
2 檢查版本是否大於M
3 檢查是否擁有申請的權限
4 沒有,去申請
5 覆蓋 onRequestPermissionsResult方法,根據requestCode,permissions,grantResults判斷我們是否申請成功
6 拒絕的時候利用 ActivityCompat.shouldShowRequestPermissionRationale判斷是否勾選'不再詢問'
7 如果勾選'不再詢問',下次再申請此權限時默認返回拒絕。需自定義dialog,讓用戶去設置里面開啟權限。
public final class PermissionUtil { /** * 危險權限組 **/ public static final String[] CALENDAR; // 讀寫日歷 public static final String[] CAMERA; // 相機 public static final String[] CONTACTS; // 讀寫聯系人 public static final String[] LOCATION; // 讀位置信息 public static final String[] MICROPHONE; // 使用麥克風 public static final String[] PHONE; // 讀電話狀態、打電話、讀寫電話記錄 public static final String[] SENSORS; // 傳感器 public static final String[] SMS; // 讀寫短信、收發短信 public static final String[] STORAGE; // 讀寫存儲卡 public static final int REQUEST_STORAGE = 1000; public static final int REQUEST_CAMERA = 1001; public static final int REQUEST_CONTACTS = 1002; public static final int REQUEST_LOCATION = 1003; public static final int REQUEST_MICROPHONE = 1004; public static final int REQUEST_PHONE = 1005; static { if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M) {//當前版本小於6.0 CALENDAR = new String[]{}; CAMERA = new String[]{}; CONTACTS = new String[]{}; LOCATION = new String[]{}; MICROPHONE = new String[]{}; PHONE = new String[]{}; SENSORS = new String[]{}; SMS = new String[]{}; STORAGE = new String[]{}; } else { CALENDAR = new String[]{ Manifest.permission.READ_CALENDAR, Manifest.permission.WRITE_CALENDAR}; CAMERA = new String[]{ Manifest.permission.CAMERA}; CONTACTS = new String[]{ Manifest.permission.READ_CONTACTS, Manifest.permission.WRITE_CONTACTS, Manifest.permission.GET_ACCOUNTS}; LOCATION = new String[]{ Manifest.permission.ACCESS_FINE_LOCATION, Manifest.permission.ACCESS_COARSE_LOCATION}; MICROPHONE = new String[]{ Manifest.permission.RECORD_AUDIO}; PHONE = new String[]{ Manifest.permission.READ_PHONE_STATE, Manifest.permission.CALL_PHONE, Manifest.permission.READ_CALL_LOG, Manifest.permission.WRITE_CALL_LOG, Manifest.permission.ADD_VOICEMAIL, Manifest.permission.USE_SIP, Manifest.permission.PROCESS_OUTGOING_CALLS}; SENSORS = new String[]{ Manifest.permission.BODY_SENSORS}; SMS = new String[]{ Manifest.permission.SEND_SMS, Manifest.permission.RECEIVE_SMS, Manifest.permission.READ_SMS, Manifest.permission.RECEIVE_WAP_PUSH, Manifest.permission.RECEIVE_MMS}; STORAGE = new String[]{ Manifest.permission.READ_EXTERNAL_STORAGE, Manifest.permission.WRITE_EXTERNAL_STORAGE}; } } public static boolean hasPermission(Context context, String[] permissions) { if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M) return true; for (String permission : permissions) { return hasPermission(context, permission); } return true; } public static boolean hasPermission(Context context, String permission) { if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M) return true; if (ContextCompat.checkSelfPermission(context, permission) != PackageManager.PERMISSION_GRANTED) return false; return true; } public static void requestPermission(Activity activity, String[] permissions, int request) { ActivityCompat.requestPermissions(activity, permissions, request); } }
private void checkStoragePermission() { if (!PermissionUtil.hasPermission(this, PermissionUtil.STORAGE)) { PermissionUtil.requestPermission(this, PermissionUtil.STORAGE, PermissionUtil.REQUEST_STORAGE); } else { //granted .. to do something } } @Override public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) { super.onRequestPermissionsResult(requestCode, permissions, grantResults); switch (requestCode) { case PermissionUtil.REQUEST_STORAGE: if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) { //granted.. to do something } else { //shouldShowRequestPermissionRationale 拒絕時是否勾選了不再提示 // 沒有勾選返回true ,勾選不再提醒返回false boolean hasAlwaysDeniedPermission = !ActivityCompat.shouldShowRequestPermissionRationale(this, PermissionUtil.STORAGE[0]); //勾選不再提醒,會過濾系統的對話框,這時需要手動去設置權限 if (hasAlwaysDeniedPermission) { handlePermission(); } } break; default: break; } } private void handlePermission() { final CustomDialog dialog = new CustomDialog(this); dialog.setTitle("提示"); dialog.addPositiveListener("去添加權限", new View.OnClickListener() { @Override public void onClick(View v) { Intent intent = new Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS); Uri uri = Uri.fromParts("package", getPackageName(), null); intent.setData(uri); startActivityForResult(intent, PermissionUtil.REQUEST_STORAGE); dialog.dismiss(); } }); dialog.addNegativeListener(new View.OnClickListener() { @Override public void onClick(View v) { dialog.dismiss(); } }); TextView textView = new TextView(this); textView.setText("應用需要訪問讀取權限,不授權將無法正常工作!"); dialog.addChildView(textView); dialog.show(); }