1. 程式人生 > >安卓6.0許可權申請詳解

安卓6.0許可權申請詳解

安卓6.0的一大變化就是對於許可權的限制,首次安裝應用時會產生一個許可權請求列表,需要使用者手動逐個確認每個許可權,應用才能獲取該許可權。而在6.0之前預設開啟的,因此會產生一些應用會讀取使用者的一些隱私資訊,影響使用者體驗。本文根據實際專案開發經驗,簡述基於安卓6.0開發的動態許可權呼叫相關的API使用,方便開發者快速呼叫。

一、安卓6.0許可權相關介紹

Google將許可權分為兩類,一類是Normal Permissions,該類許可權不涉及使用者隱私,不需要使用者進行授權,比如藍芽、訪問網路等;另一類是Dangerous Permission,一般是涉及使用者隱私,需要使用者手動授權,比如攝像機、獲取手機狀態和讀寫SD卡等。

  • Normal Permissions:
ACCESS_LOCATION_EXTRA_COMMANDS
ACCESS_NETWORK_STATE
ACCESS_NOTIFICATION_POLICY
ACCESS_WIFI_STATE
BLUETOOTH
BLUETOOTH_ADMIN
BROADCAST_STICKY
CHANGE_NETWORK_STATE
CHANGE_WIFI_MULTICAST_STATE
CHANGE_WIFI_STATE
DISABLE_KEYGUARD
EXPAND_STATUS_BAR
GET_PACKAGE_SIZE
INSTALL_SHORTCUT
INTERNET
KILL_BACKGROUND_PROCESSES
MODIFY_AUDIO_SETTINGS
NFC
READ_SYNC_SETTINGS
READ_SYNC_STATS
RECEIVE_BOOT_COMPLETED
REORDER_TASKS
REQUEST_INSTALL_PACKAGES
SET_ALARM
SET_TIME_ZONE
SET_WALLPAPER
SET_WALLPAPER_HINTS
TRANSMIT_IR
UNINSTALL_SHORTCUT
USE_FINGERPRINT
VIBRATE
WAKE_LOCK
WRITE_SYNC_SETTINGS
  • Dangerous Permissions:
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 permission:android.permission.ACCESS_COARSE_LOCATION 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

可以通過命令列檢視手機的Dangerous Permissions許可權列表。

adb shell pm list permissions -d -g   

細心的讀者會發現Dangerous Permissions的許可權都是一組一組的,確實,Google就是這樣設計的,但是對於分組機制也會給我的應用帶你一定的風險,因為6.0及以上機器對於危險授權的機制是這樣的:假如應用已被使用者授權了同一組的某個危險許可權,那麼系統會立即授權同組的其他許可權,而不需使用者手動授權。例如你的應用對WRITE_CONTACTS授權了,那麼對於CONTACTS該組內的其他許可權也是授權的。在彈出許可權授權對話方塊的授權請求,也是指對該組許可權的授權申請。

二、安卓6.0動態申請許可權實現

安卓6.0(23)動態許可權的申請包括:AndroidManifest新增、檢查許可權、申請授權和許可權處理回撥四個過程。

  • AndroidManifest新增
    在AndroidManifest新增相關請求的許可權,該操作與之前的許可權新增類似,這裡要宣告一下:即使動態申請許可權,也需要在此處新增相關的許可權,否則無法動態申請。因為程式碼邏輯處理的許可權申請的檢查以及申請回調。
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
<uses-permission android:name="android.permission.CAMERA"/>
<uses-permission android:name="android.permission.XXX"/>   //XXX:需要申請的許可權名稱
  • 檢查許可權
    檢查許可權的邏輯處理一般是在應用啟動的Activity的onCreate()中實現。
 ContextCompat.checkSelfPermission(this, Manifest.permission.CAMERA) != PackageManager.PERMISSION_GRANTED

ContextCompat.checkSelfPermissionf()方法是檢查某個許可權是否已經被授權,第一個引數為我們當前的Activity的引用,第二個引數為我們申請的許可權,該方法的返回值為:
PackageManager.PERMISSION_DENIED:拒絕
PackageManager.PERMISSION_GRANTED:授權
一般我們都是判斷其返回值是否為GRANTED,如不是則判斷未授權,申請授權;否則為已經授權。

  • 申請授權
 MainActivity.this.requestPermissions(permissions, requestCode);

其中permissions為申請許可權的字串陣列;requestCode用於回撥的監測。在申請授權時我們可以一次請求申請多個許可權。
一般使用時,許可權的請求和申請授權的操作是一起處理的。監測我們需要申請的許可權是否已經授權,如果授權,則呼叫授權後的邏輯處理;否則,呼叫許可權申請的邏輯。如果之前已經開啟並授權成功,那麼不會顯示許可權請求的對話方塊,邏輯進入“已經授權”的處理中。簡單的邏輯處理如下。

String[] permissions = {Manifest.permission.CAMERA, Manifest.permission.WRITE_EXTERNAL_STORAGE};
        if (Build.VERSION.SDK_INT >= 23) {
            if (ContextCompat.checkSelfPermission(this, Manifest.permission.CAMERA) != PackageManager.PERMISSION_GRANTED) {
                //申請攝像頭許可權
                if (Manifest.permission.CAMERA.equals(Manifest.permission.CAMERA)) {
                    Log.i(TAG, "requestPermission: 請求攝像頭許可權");
                    this.requestPermissions(permissions, CAMERA_CODE);
                }
            } else {
                Log.i(TAG, "requestPermission:已經申請了攝像頭");
            }

            //申請讀寫許可權
            if (ContextCompat.checkSelfPermission(this, Manifest.permission.WRITE_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED) {
                if (Manifest.permission.WRITE_EXTERNAL_STORAGE.equals(Manifest.permission.WRITE_EXTERNAL_STORAGE)) {
                    Log.i(TAG, "requestPermission: 請求外部讀寫許可權");
                    this.requestPermissions(permissions, WRITE_EXTERNAL_STORAGE_CODE);
                }
            } else {
                Log.i(TAG, "requestPermission: 已經申請了外部讀寫許可權");
            }
        }
  • 許可權處理回撥
@Override
    public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
        Log.i(TAG, "onRequestPermissionsResult: " + grantResults.length);
        switch (requestCode) {
            case CAMERA_CODE:
                if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
                    Log.i(TAG, "onRequestPermissionsResult: 攝像頭許可權請求成功");
                } else {
                    Log.i(TAG, "onRequestPermissionsResult: 攝像頭許可權請求失敗");
                }
                break;
            case WRITE_EXTERNAL_STORAGE_CODE:
                if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
                    Log.i(TAG, "onRequestPermissionsResult: 讀寫許可權請求成功");
                } else {
                    Log.i(TAG, "onRequestPermissionsResult: 讀寫許可權請求失敗");
                }
                break;
            default:
                super.onRequestPermissionsResult(requestCode, permissions, grantResults);
        }
    }

許可權請求的回撥,只需重寫onRequestPermissionsResult()方法,根據我們在許可權請求的requestCode作為區分不同許可權請求的回撥判斷。例如本例中針對CAMERA_CODE和WRITE_EXTERNAL_STORAGE_CODE分別處理。grantResults對於申請的請求結果,在請求陣列包含幾個請求字串,這裡的回撥數字就是多少。通過判斷grantResults[i]對應的結果是否為PackageManager.PERMISSION_GRANTED來判決許可權請求是否成功,如果我們在應用請求時,選擇拒絕,則此處回撥顯示請求失敗。
由於此處包含授權成功的回撥,因此我們在應用開發時,要注意在應用回撥成功和失敗時的分別邏輯處理。
至此,安卓6.0動態請求許可權的邏輯處理講解結束,總結一下:
1、AndroidManifest新增:該新增過程與此前的許可權新增一樣,根據我們需要新增相應許可權(注意:動態申請的也要新增);
2、檢查許可權和申請授權:監測SDK是否大於23,如大於則動態申請許可權;包括兩個重要方法監測許可權(ContextCompat.checkSelfPermission())和請求許可權(MainActivity.this.requestPermissions())。如果監測已經請求了許可權,則直接處理相應邏輯;
3、許可權處理回撥:根據requestCode判斷相應的許可權請求回撥,根據grantResults[i]判斷許可權請求結果,然後做相應的邏輯處理。

除了我們自己編寫外,我們還可以藉助網上一些開源框架,例如PermissionGen,我們可直接呼叫即可,詳情可以在GitHub上看相關的文件使用。