Android 8.0 運行時權限策略變化和適配方案

Android8.0也就是Android O即將要發佈了,有不少新特性,目前咱們能夠經過AndroidStudio3.0 Canary版本下載Android O最新的系統映像的Developer Preview 4版本,Developer Preview 4是Android O正式版推出前的最後一個預覽版本,因此它是Android O的候選版本,咱們可使用它來完成開發和測試,讓咱們的應用平穩過分到Android O。html

後期會計劃出一篇Android O行爲變化和兼容方案的文章,本篇文章主要講Android O行爲變化的其中一點——系統運行時權限的策略變化和適配方案java

Android系統的運行時權限是從Android 6.0(Android M)開始加入的,若是你還不知道Android運行時權限,你能夠看我在掘金的另外一篇文章Android 6.0 運行時權限管理最佳實踐
juejin.im/post/57d5de… android

針對運行時權限管理,有不少開源的管理庫,去年這個時候本人也開源了一個運行權限管理方案,它最大程度上兼容了國產機,固然也兼容了Android 8.0:
github.com/yanzhenjie/… git

在正式開始以前,先糾正一個問題,在網上看到有項目能夠作到自定義申請受權的系統Dialog,首先要糾正就目前來看是絕對不行的,最多在調用申請的代碼以前彈一個本身的Dialog提示用戶要申請受權了。我快速拜讀了下那個項目源碼,果真如我想象的同樣,在繞了一個圈子後最終仍是調用了系統申請受權的代碼。github


Android O的運行時權限策略變化

若是你喜歡看Google官網的文章,你能夠看這裏:
developer.android.com/preview/beh…數組

在 Android O 以前,若是應用在運行時請求權限而且被授予該權限,系統會錯誤地將屬於同一權限組而且在清單中註冊的其餘權限也一塊兒授予應用。ide

對於針對Android O的應用,此行爲已被糾正。系統只會授予應用明確請求的權限。然而一旦用戶爲應用授予某個權限,則全部後續對該權限組中權限的請求都將被自動批准。post

例如,假設某個應用在其清單中列出READ_EXTERNAL_STORAGEWRITE_EXTERNAL_STORAGE。應用請求READ_EXTERNAL_STORAGE,而且用戶授予了該權限,若是該應用針對的是API級別24或更低級別,系統還會同時授予WRITE_EXTERNAL_STORAGE,由於該權限也屬於STORAGE權限組而且也在清單中註冊過。若是該應用針對的是Android O,則系統此時僅會授予READ_EXTERNAL_STORAGE,不過在該應用之後申請WRITE_EXTERNAL_STORAGE權限時,系統會當即授予該權限,而不會提示用戶。測試

下面咱們仍是以READ_EXTERNAL_STORAGEWRITE_EXTERNAL_STORAGE爲例來具體分析一下,這對咱們現有的代碼有什麼影響。ui

正式開始以前,咱們先約定兩個方法:

/** * 拿到沒有被受權的權限。 */
getDeinedPermission(String... permissions);
/** * 請求幾個權限。 */
requestPermission(String... deinedPermissions);複製代碼

權限的常量在Manifest.permission類中,而READ_EXTERNAL_STORAGE權限是在API 16以後才添加的,因此在在Android M出來後爲了適配更低版本的系統,咱們通常是這樣申請權限的(僞代碼):

// 須要申請的權限。
String[] permissions = {
    Manifest.permission.WRITE_EXTERNAL_STORAGE,
    Manifest.permission.READ_SMS,
    ...
};

String[] deniedPermissions = getDeinedPermission(permissions);

if(deniedPermissions.length <= 0) {
    // TODO do something...
} else {
    requestPermission(deniedPermissions, callback);
}複製代碼

邏輯很是簡單清晰,其中的callback是申請權限的回調,這裏咱們申請了WRITE_EXTERNAL_STORAGE權限,在Android O以前,咱們同時會獲得READ_EXTERNAL_STORAGE權限,咱們在其它地方涉及到讀取存儲卡的操做時只須要判斷有WRITE_EXTERNAL_STORAGE權限就去讀取了。

霸特,此時應用若是安裝在Android O的系統中咱們會發現,判斷了有WRITE_EXTERNAL_STORAGE權限後去讀取存儲卡內容時應用崩潰了,緣由就是咱們沒有申請READ_EXTERNAL_STORAGE權限。

對Android O運行時權限策略變化的應對方案

針對Android O的運行時權限策略的特色,爲了適配各個版本的系統,咱們的代碼會變成以下方式(僞代碼):

// 須要申請的權限。
String[] permissions = {
    Manifest.permission.WRITE_EXTERNAL_STORAGE,
    Manifest.permission.READ_EXTERNAL_STORAGE,
    Manifest.permission.READ_SMS,
    ...
};

String[] deniedPermissions = getDeinedPermission(permissions);

if(deniedPermissions.length <= 0) {
    // TODO do something...
} else {
    requestPermission(deniedPermissions, callback);
}複製代碼

可是這樣會存在兩個問題,一是有的權限組權限比較多,開發者難易所有記住;二是READ_EXTERNAL_STORAGE這個權限常量是在API 16時才被添加到SDK中,相似這樣的權限常量還有好幾個,有的甚至在Android M時才被添加到SDK中。若是咱們強制寫了,當APP運行在低版本的系統中時,仍是會崩潰。有人就說了,咱們在申請以前判斷系統版本不就好啦?固然,若是你不嫌麻煩,這是徹底能夠的。

升級方案

所以咱們總結出一個更優的方案,歸根結底就是申請權限時要申請權限組,而不是單一的某個權限。因此咱們按照系統權限組分類,把一個組的常量放到一個數組中,並根據系統版本爲這個數組賦值,因而乎產生了這樣一個類:

public final class Permission {

    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;    // 讀寫存儲卡。

    static {
        if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M) {
            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.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};
        }
    }

}複製代碼

在Android M之前使用某權限是不須要用戶受權的,只要在Manifest中註冊便可,在Android M以後須要註冊並申請用戶受權,因此咱們根據系統版本在Android M之前用一個空數組做爲權限組,在Android M之後用真實數組權限。

由於要傳入多個權限組,因此咱們約定的兩個方法就不夠用了,因此咱們加兩個方法:

/** * 拿到沒有被受權的權限。 */
String[] getDeinedPermission(String... permissions);
/** * 請求幾個權限。 */
void requestPermission(String... deinedPermissions);
/** * 拿到沒有被受權的權限。 */
String[] getDeinedPermission(String[]... permissions);
/** * 請求幾個權限。 */
void requestPermission(String[]... deinedPermissions);複製代碼

因而咱們申請權限的代碼就簡化成這樣了:

// 這方法裏面判斷版本,返回空數組或者沒有權限的數組。
String[] deniedPermissions = getDeinedPermission(Permission.STORAGE, Permission.SMS);

if(deniedPermissions.length <= 0) {
    // TODO do something...
} else {
    requestPermission(deniedPermissions, callback);
}複製代碼

固然這不是最簡化的,可是已經足以兼容到Android O的權限策略的變化了。

若是是AndPermission如何作到最簡

這裏只是介紹下AndPermisison也兼容了Android O的權限變化,若是你以爲這個項目不適合你,你能夠自行封裝一個,我比較鼓勵開發者本身動手,下面是開源地址:
github.com/yanzhenjie/…

它的一些簡單的特色:

  1. 鏈式調用,一句話申請權限,省去複雜的邏輯判斷。
  2. 支持註解回調結果、支持Listener回調結果。
  3. 拒絕一次某權限後,再次申請該權限時可以使用Rationale向用戶說明申請該權限的目的,在用戶贊成後再繼續申請,避免用戶勾選再也不提示而致使不能再次申請該權限。
  4. 就算用戶拒絕權限並勾選再也不提示,可以使用SettingDialog提示用戶去設置中受權。
  5. RationaleDialog和SettingDialog容許開發者自定義。
  6. AndPermission自帶默認對話框除可自定義外,也支持國際化。
  7. 支持在任何地方申請權限,不只限於Activity和Fragment等。
  8. 支持申請權限組、兼容Android8.0。

申請多個權限組示例:

AndPermission.with(this)
    .permission(Permission.CAMERA, Permission.SMS) // 多個權限組。
    .callback(new PermissionListener() {
        @Override
        public void onSucceed(int i, @NonNull List<String> list) {
            // TODO do something...
        }

        @Override
        public void onFailed(int i, @NonNull List<String> list) {
            // TODO 用戶沒有贊成受權,通常彈出Dialog讓用戶去Setting中受權。
        }
    })
    .start();複製代碼

申請單個或者某幾個權限示例,由於Android O的出現,如今不鼓勵這樣使用了,可是在Android O正式發佈前沒有問題:

AndPermission.with(this)
    .permission(
        // 多個不一樣權限組權限,如今不鼓勵這樣使用了,可是在Android O正式發佈前沒有問題。
        Manifest.permission.WRITE_EXTERNAL_STORAGE,
        Manifest.permission.READ_SMS
    ) 
    .callback(new PermissionListener() {
        @Override
        public void onSucceed(int i, @NonNull List<String> list) {
            // TODO do something...
        }

        @Override
        public void onFailed(int i, @NonNull List<String> list) {
            // TODO 用戶沒有贊成受權,通常彈出Dialog讓用戶去Setting中受權。
        }
    })
    .start();複製代碼

關於Android O的運行時權限策略變化和應對方案的介紹到這裏就結束了,若是還不理解的能夠在文章下方留言。

相關文章
相關標籤/搜索