Android 6.0 權限管理
運行時權限(Runtime permission)
android的權限系統一直是首要的安全概念,因為這些權限只在安裝的時候被詢問一次。一旦安裝了,app可以在用戶毫不知曉的情況下訪問權限內的所有東西。
這是極其危險的事情
所以,在Android M 權限請求設計改版了,有點類似iOS的權限請求

在android6.0棉花糖,app將不會在安裝的時候授予權限。取而代之的是,app不得不在運行時一個一個詢問用戶授予權限。
注意權限詢問對話框不會自己彈出來。開發者不得不自己調用。如果開發者要調用的一些函數需要某權限而用戶又拒絕授權的話,函數將拋出異常甚至導致程序崩潰.
舊版兼容
為了與舊版本兼容,比如你的 build.gradle 中的 targetSdkVersion 設置為 23 之前,比如22.
也能在Android6.0 的手機上面跑,並且權限請求機制使用6.0之前的 安裝時請求 的模式.
吐槽一下: Excuse Me? 這到底是兼容還是漏洞... targetSdkVersion 設置為23以前,不讓跑6.0不是更合理?
可能處於市場應用的API版本考慮,不兼容估計大部分應用都不能跑6.0
所以,如果你覺得運行時彈出權限框讓用戶勾選很不友好,那么就取巧使用targetSdkVersion <23 吧,但這絕對不是長久之計...(丑陋...)
- android {
- compileSdkVersion 23
- buildToolsVersion "23.0.2"
-
- defaultConfig {
- minSdkVersion 8
- targetSdkVersion 22
- versionCode 1
- versionName "1.0"
- }
- buildTypes {
- release {
- minifyEnabled false
- proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
- }
- }
- }

6.0權限彈框的兩種模式
1.初次請求,彈出對話框叫你勾選

2,第二次請求之后,彈出對話框,出現不再提醒字樣

當用戶點擊了不再提醒,你再次請求權限的時候,就不會彈出對話框,所以這時,你需要根據需求另做處理
下面會如何處理
6.0之后的權限分類
分為兩類 Normal permissions 和 Dangerous permissions
Normal permissions(普通權限)
只需要在xml中申請就可以了,與6.0之前沒什么區別
包括的權限有
- android.permission.ACCESS_LOCATION_EXTRA_COMMANDS
- android.permission.ACCESS_NETWORK_STATE
- android.permission.ACCESS_NOTIFICATION_POLICY
- android.permission.ACCESS_WIFI_STATE
- android.permission.ACCESS_WIMAX_STATE
- android.permission.BLUETOOTH
- android.permission.BLUETOOTH_ADMIN
- android.permission.BROADCAST_STICKY
- android.permission.CHANGE_NETWORK_STATE
- android.permission.CHANGE_WIFI_MULTICAST_STATE
- android.permission.CHANGE_WIFI_STATE
- android.permission.CHANGE_WIMAX_STATE
- android.permission.DISABLE_KEYGUARD
- android.permission.EXPAND_STATUS_BAR
- android.permission.FLASHLIGHT
- android.permission.GET_ACCOUNTS
- android.permission.GET_PACKAGE_SIZE
- android.permission.INTERNET
- android.permission.KILL_BACKGROUND_PROCESSES
- android.permission.MODIFY_AUDIO_SETTINGS
- android.permission.NFC
- android.permission.READ_SYNC_SETTINGS
- android.permission.READ_SYNC_STATS
- android.permission.RECEIVE_BOOT_COMPLETED
- android.permission.REORDER_TASKS
- android.permission.REQUEST_INSTALL_PACKAGES
- android.permission.SET_TIME_ZONE
- android.permission.SET_WALLPAPER
- android.permission.SET_WALLPAPER_HINTS
- android.permission.SUBSCRIBED_FEEDS_READ
- android.permission.TRANSMIT_IR
- android.permission.USE_FINGERPRINT
- android.permission.VIBRATE
- android.permission.WAKE_LOCK
- android.permission.WRITE_SYNC_SETTINGS
- com.android.alarm.permission.SET_ALARM
- com.android.launcher.permission.INSTALL_SHORTCUT
- com.android.launcher.permission.UNINSTALL_SHORTCUT
其實不需要記,記住哪些是危險權限就是了
Dangerous permissions(危險權限)
危險權限,需要在運行時請求.
注意: 危險權限是按組來分的,所以,當你申請了多個同組的危險權限時,運行時只需要申請一個就行
例如:
- <!-- 電話 -->
- <uses-permission android:name="android.permission.READ_CALL_LOG" />
- <uses-permission android:name="android.permission.READ_PHONE_STATE" />
- <uses-permission android:name="android.permission.CALL_PHONE" />
- <uses-permission android:name="android.permission.WRITE_CALL_LOG" />
- <uses-permission android:name="android.permission.USE_SIP" />
- <uses-permission android:name="android.permission.PROCESS_OUTGOING_CALLS" />
- <uses-permission android:name="com.android.voicemail.permission.ADD_VOICEMAIL" />
你申請了關於電話的那么多權限,在動態申請的時候,它只會彈出

這一個權限框
所以,這是一個權限組的概念,運行時選擇你申請的同組權限的一個就行
目前所有的危險權限組集合
- <!-- Dangerous Permissions. -->
- <!-- 聯系人 -->
- <uses-permission android:name="android.permission.WRITE_CONTACTS" />
- <uses-permission android:name="android.permission.GET_ACCOUNTS" />
- <uses-permission android:name="android.permission.READ_CONTACTS" />
-
- <!-- 錄音 -->
- <uses-permission android:name="android.permission.RECORD_AUDIO" />
-
- <!-- 電話 -->
- <uses-permission android:name="android.permission.READ_CALL_LOG" />
- <uses-permission android:name="android.permission.READ_PHONE_STATE" />
- <uses-permission android:name="android.permission.CALL_PHONE" />
- <uses-permission android:name="android.permission.WRITE_CALL_LOG" />
- <uses-permission android:name="android.permission.USE_SIP" />
- <uses-permission android:name="android.permission.PROCESS_OUTGOING_CALLS" />
- <uses-permission android:name="com.android.voicemail.permission.ADD_VOICEMAIL" />
-
- <!-- 日歷 -->
- <uses-permission android:name="android.permission.READ_CALENDAR" />
- <uses-permission android:name="android.permission.WRITE_CALENDAR" />
-
- <!-- 相機 -->
- <uses-permission android:name="android.permission.CAMERA" />
-
- <!-- 傳感器 -->
- <uses-permission android:name="android.permission.BODY_SENSORS" />
-
- <!-- 定位 -->
- <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
- <uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
-
- <!-- 存儲 -->
- <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
- <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
-
- <!-- 短信 -->
- <uses-permission android:name="android.permission.READ_SMS" />
- <uses-permission android:name="android.permission.RECEIVE_WAP_PUSH" />
- <uses-permission android:name="android.permission.RECEIVE_MMS" />
- <uses-permission android:name="android.permission.RECEIVE_SMS" />
- <uses-permission android:name="android.permission.SEND_SMS" />
對應的java code
- // 聯系人
- Manifest.permission.WRITE_CONTACTS,
- Manifest.permission.GET_ACCOUNTS,
- Manifest.permission.READ_CONTACTS,
-
- // 電話
- Manifest.permission.READ_CALL_LOG,
- Manifest.permission.READ_PHONE_STATE,
- Manifest.permission.CALL_PHONE,
- Manifest.permission.WRITE_CALL_LOG,
- Manifest.permission.USE_SIP,
- Manifest.permission.PROCESS_OUTGOING_CALLS,
- Manifest.permission.ADD_VOICEMAIL,
-
- // 日歷
- Manifest.permission.READ_CALENDAR,
- Manifest.permission.WRITE_CALENDAR,
-
- // 相機
- Manifest.permission.CAMERA,
-
- // 傳感器
- Manifest.permission.BODY_SENSORS,
-
- // 定位
- Manifest.permission.ACCESS_FINE_LOCATION,
- Manifest.permission.ACCESS_COARSE_LOCATION,
-
- // 存儲
- Manifest.permission.READ_EXTERNAL_STORAGE,
- Manifest.permission.WRITE_EXTERNAL_STORAGE,
-
- // 錄音
- Manifest.permission.RECORD_AUDIO,
-
- // 短信
- Manifest.permission.READ_SMS,
- Manifest.permission.RECEIVE_WAP_PUSH,
- Manifest.permission.RECEIVE_MMS,
- Manifest.permission.RECEIVE_SMS,
- Manifest.permission.SEND_SMS,
運行時權限請求的基本步驟
1.在xml中注冊
2. 運行時請求權限
以下是權限檢查的幫助類
- /**
- * 檢查權限是否已請求到 (6.0)
- */
- public void checkPermissions(String... permissions) {
- // 版本兼容
- if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M
- // 判斷缺失哪些必要權限
- && lacksPermissions(permissions)) {
- // 如果缺失,則申請
- requestPermissions(permissions);
- }
- }
-
- /**
- * 判斷是否缺失權限集合中的權限
- */
- private boolean lacksPermissions(String... permissions) {
- for (String permission : permissions) {
- if (lacksPermission(permission)) {
- return true;
- }
- }
- return false;
- }
-
- /**
- * 判斷是否缺少某個權限
- */
- private boolean lacksPermission(String permission) {
- return ContextCompat.checkSelfPermission(context, permission) ==
- PackageManager.PERMISSION_DENIED;
- }
-
- /**
- * 請求權限
- */
- private void requestPermissions(String... permissions) {
- ActivityCompat.requestPermissions(context, permissions, PERMISSION_REQUEST_CODE);
- }
-
- /**
- * 啟動應用的設置,進入手動配置權限頁面
- */
- private void startAppSettings() {
- Intent intent = new Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS);
- Uri uri = Uri.fromParts("package", context.getPackageName(), null);
- intent.setData(uri);
- context.startActivity(intent);
- }
注意: 其中的 requestPermissions 方法,它會彈出權限提示框( 沒有點擊不再提醒的話 ),然后調用 public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) 方法,該方法在Activity或者Fragment中回調
3.在onRequestPermissionsResult回調中處理
在 public void onRequestPermissionsResult() 方法中,你可以捕獲到用於是點擊了 不再提醒 還是 拒絕 ,然后做出不同的操作..
- @Override
- public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
- // 版本兼容
- if (android.os.Build.VERSION.SDK_INT < android.os.Build.VERSION_CODES.M ||
- requestCode != PermissionsChecker.PERMISSION_REQUEST_CODE)
- return;
-
- for (int i = 0, len = permissions.length; i < len; i++) {
- String permission = permissions[i];
- // 缺失的權限
- if (grantResults[i] == PackageManager.PERMISSION_DENIED) {
- boolean showRationale = shouldShowRequestPermissionRationale(permission);
- if (!showRationale) {
- // 用戶點擊不再提醒
- // TODO
- break;
- } else {
- // 用戶點擊了取消...
- // possibly check more permissions...
- }
- }
- }
- }
權限請求策略
下面提供一種我認為還不錯的策略
在需要某權限的Activity的 onStrart() 中去請求權限
在 onRequestPermissionsResult 回調中,如果用戶點擊了拒絕,則繼續請求權限
如果用戶點擊了不再提醒,則彈出自定義對話框,引導用戶手動去開啟權限,如果用戶不授權,則退出當前頁面
注意:適用於沒有權限就無法使用該功能的情況

Activity代碼
- public class BaseActivity extends AppCompatActivity {
- private static final String TAG = "BaseActivity";
- private PermissionsChecker checker;
-
- @Override
- protected void onCreate(@Nullable Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
- checker = new PermissionsChecker(this);
- }
-
- public PermissionsChecker getChecker() {
- return checker;
- }
-
- /**
- * 在該聲明周期,檢查權限申請情況
- */
- @Override
- protected void onStart() {
- super.onStart();
- checker.checkPermissions(PermissionsChecker.PERMISSIONS);
- }
-
- /**
- * 請求權限檢查完后回調的結果
- *
- * @param requestCode .
- * @param permissions 所請求的權限
- * @param grantResults .
- */
- @TargetApi(Build.VERSION_CODES.M)
- @Override
- public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
- if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M ||
- requestCode != PermissionsChecker.PERMISSION_REQUEST_CODE)
- return;
-
-
- for (int i = 0, len = permissions.length; i < len; i++) {
- String permission = permissions[i];
- if (grantResults[i] == PackageManager.PERMISSION_DENIED) {
- boolean showRationale = shouldShowRequestPermissionRationale(permission);
- if (!showRationale) {
- // 用戶點擊不再提醒,彈出權限框,引導其手動開啟權限
- checker.showMissingPermissionDialog();
- break;
- } else {
- // 用戶點擊取消,繼續提示
- checker.checkPermissions(PermissionsChecker.PERMISSIONS);
- break;
- }
- }
- }
- }
- }
-