RuntimePermission 運行時權限解析

概述

Android 6.0開始,為了更好的保護用戶隱私,引入了新的權限機制:

  • 普通權限與Android 6.0之前一樣,直接在Manifest中聲明即可
  • 危險權限在使用之前告知用戶.獲得用戶授權之后才可以獲取相應的權限
  • 用戶可以選擇不再提醒永久拒絕某個權限
危險權限列表

粗體字為權限組,權限組下面為權限組內的具體權限</br>
申請權限組中的任何一個權限即獲得了整個權限組中的所有權限

CONTACTS
  • READ_CONTACTS
  • WIRTE_CONTACTS
  • GET_CONTACTS
PHONE
  • READ_CALL_LOG
  • READ_PHONE_STATE
  • CALL_PHONE
  • WRITE_CALL_LOG
  • USE_SIP
  • PROCESS_OUTGOING_CALLS
  • ADD_VOICEMAIL
CALENDAR
  • READ_CALENDAR
  • WRITE_CALENDAR
CAMERA

CAMERA

SENSORS

BODY_SENSORS

LOCATION
  • ACCESS_FINE_LOCATION
  • ACCESS_COARSE_LOCATION
STORAGE
  • READ_EXTERNAL_STORAGE
  • WRITE_EXTERNAL_STORAGE
MICROPHONE
  • RECORD_AUDIO
SMS
  • READ_SMS
  • RECEIVE_WAP_PUSH
  • RECEIVE_MMS
  • RECEIVE_SMS
  • SEND_SMS
  • READ_CELL_BROADCASTS

使用安卓自帶Api申請運行時權限

以獲取用戶通訊錄為例
  1. 在Manifest中聲明我們需要的權限
//in Manifest
<uses-permission android:name="android.permission.READ_CONTACTS"/>
  1. 在Accitity中申請權限
    大致的邏輯:</br>
    -> 檢查用戶是否授權過該權限
    -> 已經授權過 -> 執行業務邏輯
    -> 沒有授權 -> 申請權限 -> 成功 -> 執行業務邏輯
API:
  • 檢查用戶是否授權過該權限
    int checkSelfPermission()
    返回值:</br>
    PackageManager.PERMISSION_GRANTED 表示有權限
    PackageManager.PERMISSION_DENIED 表示無權限

  • 獲取權限
    void requestPermissions()
    三個參數:
    Context
    new String[]{} 需要申請權限的字符串數組
    RequestCode 自定義的請求碼,結果回調的方法中使用,判斷是哪個權限的申請

  • 權限請求的結果回調
    void onRequestPermissionsResult()

Example:
// 申請一個讀取通訊錄的權限
private final static int PERMISSION_CONTACT = 1;

private void getPermission() {
    if (ContextCompat.checkSelfPermission(thisActivity,Manifest.permission.READ_CONTACTS)!= PackageManager.PERMISSION_GRANTED) {
        // 無權限時去請求權限
        ActivityCompat.requestPermissions(this,
                    new String[]{Manifest.permission.READ_CONTACTS},PERMISSION_CONTACT);
    
    }else{
        // 有權限直接進行業務操作
        doSomeWork();
    }
}

/**
* 直接在Activity重寫 onRequestPermissionsResult()方法即可
* requestCode requetsPermissions()方法中傳入的RequestCode
* String[] permissons  requetsPermissions()方法中傳入的permission數組
* grantResults  返回的具體結果
**/
@Override
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions,@NonNull int[] grantResults) {
    switch(requestCode) {
        case REQUEST_CONTACT:
            if (grantResults.length > 0  && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
            // 授予權限,撥打電話
            doSomeWork();
            } else {
                Toast.makeText(this, "請求權限被拒絕", Toast.LENGTH_SHORT).show();
            }
         break;
    }
}

用戶拒絕權限的處理

拒絕的邏輯:
申請權限 -> 用戶拒絕 -> END
再次申請權限 -> 權限彈框會多一個不再提醒的選項 -> 用戶勾選不再提醒選項并拒絕權限 -> END Forever

為了我們業務的正常進行,我們需要避免 END FOREVER的結果,所以在第二次申請權限的時候我們要提醒用戶不要拒絕我們的權限,安卓也為我們提供了這樣的API

  • boolean shouldShowRequestPermissionRationale() 是否給用戶提醒權限的必要性
在代碼中區分是第一次還是被拒絕后再次申請權限
// 重寫一下getPermission()方法
private void getPermission() {
    if (ContextCompat.checkSelfPermission(thisActivity,Manifest.permission.READ_CONTACTS)!= PackageManager.PERMISSION_GRANTED) {
        // 無權限時去請求權限
        if(ActivityCompat.shouldShowRequestPermissionRationale(thisActivity,
            Manifest.permission.READ_CONTACTS)) {
            // 通常是彈框告訴用戶權限的必要性,然后繼續申請權限
            
            new AlertDialog.Builder(this)
                .setCancelable(false)
                .setMessage("求求你別拒絕~")
                .setPositiveButton("好的", new DialogInterface.OnClickListener() {
                @Override
                public void onClick(DialogInterface dialog, int which) {
                    ActivityCompat.requestPermissions(this,
                    new String[]{Manifest.permission.READ_CONTACTS},PERMISSION_CONTACT);
                }
            })
            .setNegativeButton("不給", new DialogInterface.OnClickListener() {
                @Override
                public void onClick(DialogInterface dialog, int which) {
                    dialog.dismiss();
                }
            })
            .show();
        } else {
            ActivityCompat.requestPermissions(this,
                    new String[]{Manifest.permission.READ_CONTACTS},PERMISSION_CONTACT);
        }
    }else{
        // 有權限直接進行業務操作
        doSomeWork();
    }
}

使用第三方框架申請權限

由于國內的各家手機廠商都對安卓進行了魔改,安卓所提供的原生的API可能會在一些手機上報錯,或者無法正常申請權限。對種類繁多的手機Rom和安卓版本都進行適配會非常繁瑣,推薦大家使用一些成熟的第三方框架去獲取手機權限。

框架并不能解決所有的問題

  • 寫博客之前碰到的問題:
    我的App需要申請相機權限,本人用的是小米/華為的手機,當時選擇了使用AndPermission申請權限。測試了幾次之后都挺正常。然后使用同事的魅族手機測試,在用戶第一次拒絕權限之后,第二次請求權限 gradResults 返回的是Granted.所以代碼會在沒有權限的情況下繼續向后執行,相機界面變成了一片黑色.
    然后我又用原生Api嘗試了一下,結果是一樣的.所以原因應該是魅族手機的權限邏輯問題.
    接下來去找解決問題的方案. 在AndPermission的Issus里面作者是這樣解釋的

在有些國產機上,申請權限時系統返回的總是有權限,對于通訊錄、讀寫SD卡等權限,AndPermission的策略是拿到有權限的結果后,再執行一下讀取一條通訊錄或者向SD卡寫入一個文件來驗證是否真的有權限。但是對于撥打電話此類權限,AndPermission不能真的去撥打一個電話,因此直接回調了onGranted(),開發者可以在onGranted()中直接撥打電話,來配合AndPermission申請權限,如果有就自然撥打出去了,如果沒有權限,上述手機會彈出授權對話框或者拋出異常,如果拋出異常,AndPermission會重新回調到onDenied()方法,開發者即可認為是沒有權限。

簡單分析下作者所說的內容:
通常的邏輯: getPermission -> granted() -> success
嚴謹的邏輯: getPermission -> granted() -> do() -> 捕獲由于權限造成的異常 -> onDenied() -> getPermission()

針對我上面的相機權限問題: 在申請權限成功進入相機界面之后,在相機啟動得時候相機打開失敗/獲取不到相機參數時,重新彈框去告訴用戶.所以還是得具體情況具體分析.

最后總結幾個申請權限的注意事項:

  1. 在需要的時候申請權限,不要在App開屏的時候請求所有的權限。體驗非常不友好,而且容易被拒絕
  2. 對于一些必要權限(用戶拒絕概率較高),在申請權限之前彈框提示告知用戶.
  3. 有些手機可能會返回錯誤的權限信息,獲取到權限提供的數據才算結束
  4. 參考AndPermission的做法,我們可以在代碼中拋出由于沒有權限引起的異常,再次申請權限。或者顯示ErrorView,讓用戶點擊授權。
最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容