6.0運行時權限的總結與實踐

運行時權限.png

爲什麼需要6.0運行時權限

  • 更友好

6.0以前的安裝時權限,會在應用安裝時列出所有需要的權限,當列出一些危險權限時,用戶不知每個權限的具體用途,可能因爲這些權限警告而放棄安裝應用。對於一些非裝不可的應用,用戶則不得不被迫接受所有權限,很容易安裝了一些流氓APP,體驗不佳。
6.0以後的運行時權限,可以在調用相關功能之前判斷權限授權狀態,並自定義提示彈框告知用戶權限用途,使用戶清楚瞭解之後,再授權使用。

5.0和6.0的安裝頁面.png

  • 更穩定

6.0系統的手機對於每個應用,都有個權限設置頁面,可以手動開關權限,如果用戶在設置頁面誤關了某個權限,若沒在程序運行時做判斷,則會導致相關功能的調用失敗,引起崩潰等。

5.0和6.0的權限設置頁面.png

如何實現運行時權限

  • 設置targetSdkVersion

只有targetSdkVersion>=23,且安裝在6.0以上的手機時,運行時權限機制才能正常運作

手機系統 targetSdkVersion<23 targetSdkVersion>=23
5.0+ 安裝時權限 安裝時權限
6.0+ 安裝時權限 運行時權限

* 代碼實現

在需要使用某項權限時,通過V4包的checkSelfPermission判斷權限是否授權,通過requestPermissions申請某項權限

// 需要執行某項需要權限的操作時
if (ContextCompat.checkSelfPermission(this, "某項權限") == PackageManager.PERMISSION_GRANTED) {
   // 執行操作
} else {
   // 請求權限
   ActivityCompat.requestPermissions(this, new String[]{"某項權限"}, requestCode);
}

在Activity的onRequestPermissionsResult回調方法中,處理權限授權結果

@Override
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
   if (grantResults[0] == PackageManager.PERMISSION_GRANTED) {
       // 授權成功,執行操作
   } else {
       // 權限被拒絕
       if (ActivityCompat.shouldShowRequestPermissionRationale(this, "權限名稱")) {
           // shouldShowRequestPermissionRationale=true: 表示權限被拒絕,且沒有勾選"never ask again"
           // 正常的權限被拒絕流程,可以繼續申請權限,重複以上流程
       } else {
           // shouldShowRequestPermissionRationale=false: 在權限被拒絕,且勾選"never ask again"的情況下,返回false
           // 繼續申請權限的時候,不會再彈出默認的系統彈框,需要自定義提示彈框,並引導用戶去權限設置頁面,手動開啓權限
       }
   }
}

LsPermission工具類的使用

  • 主要實現邏輯參考PermissionGen,封裝了權限判斷,請求,結果處理等通用邏輯,目前只支持context=AppCompatActivity,如果在Fragment中使用時可以調用getActivity()獲取上層AppCompatActivity。

demo演示.gif

private void normal() {
   final String[] permissions =
       new String[] {Manifest.permission.CALL_PHONE, Manifest.permission.ACCESS_FINE_LOCATION};

   PermissionUtil.request(this, permissions, new OnPermissionAdapter() {
       /**
        * @desc 申請的權限全被授權
        */
       @Override
       public void onGrant() {
           showToast("權限被同意");
           callPhone();
       }

       /**
        * @desc 權限被拒絕
        */
       @Override
       public void onDeny(List<String> permissions) {
           showToast("用戶拒絕授權" + permissions.toString());
       }

       /**
        * @desc 權限被拒絕,且勾選"never ask again"
        */
       @Override
       public void onNeverAsk(List<String> permissions) {
           showToast("用戶拒絕授權, 並勾選 never ask again " + permissions.toString());
           PermissionUtil.showNeverAskDialog(MainActivity.this, "這個權限很重要");
       }

       /**
        * @desc 無論權限授權成功還是失敗,都會回調
        */
       @Override
       public void always(List<String> grantPermissions, List<String> denyPermissions,
           List<String> foreverDenyPermissions) {
           showToast("授權: " + grantPermissions.toString() + "\n 拒絕: " + denyPermissions.toString()
               + "\n never ask: " + foreverDenyPermissions.toString());
       }
   });
}

/**
* @desc 將權限回調轉發給PermissionUtil處理
* @author listen
* @date 2017/2/24 13:47
*/
@Override
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
   PermissionUtil.onRequestPermissionsResult(this, requestCode, permissions, grantResults);
   super.onRequestPermissionsResult(requestCode, permissions, grantResults);
}
  • PermissionUtil內部通過SparseArray保存了requestCode和權限回調的鍵值對,避免了連續點擊時重複回調情況,對於不同的權限申請,推薦使用不同的requestCode,避免回調覆蓋的問題。
private static SparseArray<OnPermissionListener> mPermissionRequestList = new SparseArray<>();

public static void request(final Context context, int requestCode, String[] permissions,
                          OnPermissionListener listener) {
   /** 存在未授權的權限 */
   synchronized (mPermissionRequestList) {
       if (null != mPermissionRequestList.get(requestCode)) {
           /** 當前權限請求已經存在,不重複添加 */
           log("the same permission is requesting");
       } else {
           /** 將當前權限請求加入隊列 */
           /** 如果一個頁面中存在分別觸發A, B多個權限的情況, 則最好將不同權限申請對應不同的requestCode, 存入SparseArray分別處理 */
           mPermissionRequestList.put(requestCode, listener);

           /** 執行權限申請 */
           ActivityCompat.requestPermissions();
       }
   }
}

在權限回調的時候從SparseArray移除Listener

public static void onRequestPermissionsResult(Context context, int requestCode, String[] permissions,
                                                  int[] grantResults) {
   final OnPermissionListener listener;
   synchronized (mPermissionRequestList) {
       listener = mPermissionRequestList.get(requestCode);
       mPermissionRequestList.remove(requestCode);
   }

   if (null != listener) {
       /** 執行回調 */
       listener.onGrant();
       listener.onDeny();
       listener.onNeverAsk();
       listener.always();
   } else {
       log("request is not exists");
   }
}

代碼地址:LsPermission

除了基本的權限申請邏輯的封裝以外,還寫了類似微信,支付寶,百度地圖等在啓動頁的權限申請Demo,算是PermissionUtil的簡單運用。

參考:
Android 6.0 運行時權限處理完全解析
Android 6.0 運行時權限管理最佳實踐
聊一聊Android 6.0的運行時權限

官方:
在運行時請求權限
權限最佳做法

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章