Android 6.0 執行時許可權詳解

weixin_34075551發表於2016-12-13

一、概述

隨著Android 7.0的釋出,Android 6.0的普及速度很快就升上去了,目前Android 6.0的市場佔有率是15.2%(具體資料可以檢視Android資訊中心,自從Android Developer Day大會的召開,有很多網站,我們開發者可以直接訪問了,不必再爬梯子,對國內開發者來說,是很大的福音。唉,扯遠了.....言歸正傳)這時,我們就不得不對新版本SDK中的變化做一些適配,這樣才能保證應用更好的執行。對於6.0中的變化,我們可以參考官網的這篇文章:Android 6.0變更。該篇文章主要對Android 6.0 執行時許可權(Runtime Permissions)做一下介紹。

這裡推薦官網的兩篇文章,畢竟官方的文件才是最科學的:

二、執行時許可權

從Android6.0(API級別23)開始,使用者開始在應用執行時向其授權,而不是在應用安裝時授權。此方法可以簡化應用安裝過程,同時使用者可以對應用的功能進行更多的控制。對於6.0以下的,當我們安裝應用時預設就授權所有的許可權了,使用者也不瞭解這些許可權到底有什麼用,只能默默忍受。。而新的許可權機制可以很好的解決這一系列問題。Google將新的許可權分為正常許可權危險許可權

  • 正常許可權:正常許可權涵蓋應用需要訪問起沙盒外部資料或資源,但對於使用者隱私或其它應用操作風險很小的區域。例如,設定時區的許可權就是正常許可權。如果應用宣告氣需要正常的許可權,系統會自動向應用授予該許可權。這裡可以參考官網的正常許可權的列表

  • 危險許可權:危險許可權涵蓋應用需要涉及使用者隱私資訊的資料活資源,或者可能對使用者儲存的資料活其它應用的操作產生影響的區域。例如,讀取使用者的聯絡人就屬於危險許可權。如果應用宣告其需要危險許可權,則使用者必須明確嚮應用授予該許可權。其實我們在開發中,只要處理好危險許可權,正常許可權的處理方式和之前一樣。下面貼出危險許可權圖:

    1758616-80422d999c95e923.png

我們看上面的危險許可權,會發現危險許可權是分組的,那麼分組會對我們的許可權有影響嗎?的確是有影響的。如果你的APP執行在Android 6.0以上的機器上(targetSdkVersion >= 23下面會細說),授權機制是這樣的。如果你申請某個危險許可權,假設你的App早已被使用者授予了同一組中的某個危險許可權,那麼系統會立即授權,則不會彈出對話方塊讓使用者去授權。例如,你的App已經對CONTACTS許可權組中的READ_CONTACTS授權了,當你的App申請WRITE_CONTACTS許可權時,系統則會直接授權通過。此外,對於申請時彈出Dialog的文字說明也是對整個許可權組的說明,而不是單個許可權。這裡需要注意的是:彈出的Dialog是系統提供,我們是不能進行定製的。

三、許可權適配

首先我們按照之前的方式來申請撥打電話的許可權(撥打電話許可權),在Android 6.0(targetSdkVersion >= 23)手機上進行測試。

//首先在清單檔案中申請撥打電話的許可權
<uses-permission android:name="android.permission.CALL_PHONE"/>

//在Button的點選事件中,使用Intent撥打電話
Intent intent = new Intent(Intent.ACTION_CALL);
intent.setData(Uri.parse("tel:" + phoneNumber));
startActivity(intent);        //此行程式碼會報紅線。(android studio 2.2.2版本)看來AS還是挺人性化的。

執行App,點選撥打電話按鈕,你會發現App崩潰了。。。下面貼出異常原因圖:

1758616-e35d60b4431b9229.png

從圖中可以很清楚的看到是因為SecurityException許可權異常。解決這個異常有兩種方法:

  1. 在android studio中,開啟build.gradle(module:app)檔案,將targetSdkVersion的版本號修改為低於23的,即可解決該異常。那就繼續使用舊有規則:使用者在安裝的時候不得不接受所有許可權,安裝後app就有了那些許可權咯!
  2. 使用Android提供的相關API進行許可權的檢查,避免這個異常。

但是作為一個有“情懷”的程式猿,我們怎麼可能用第一種這麼low的方法去解決問題呢。下面我們使用Android提供的相關API來處理異常。

  1. 首先在清單檔案中申請撥打電話的許可權,這一步是必不可少的。

     <uses-permission android:name="android.permission.CALL_PHONE"/>
    
  2. 在Button的點選事件,撥打電話前,首先使用ActivityCompat.checkSelfPermission()方法檢查是否有撥打電話許可權(ActivityCompat和ContextCompat是子父類的關係),該方法有兩個int型別的返回值:分別為PERMISSION_GRANTED(表示應用有此許可權)和PERMISSION_DENIED(表示應用沒有許可權),如果此時返回值為PERMISSION_DENIED,那麼我們就應該手動去請求應用的許可權,看程式碼。
    
     if (ActivityCompat.checkSelfPermission(this, Manifest.permission.CALL_PHONE) != PackageManager.PERMISSION_GRANTED) {
         /**
          * 請求撥打電話許可權
          * 該方法是非同步的,第一個引數是Context;
          * 第二個引數是需要申請的許可權的字串陣列;
          * 第三個引數為requestCode,主要用於回撥的時候檢測。
          * 可以從方法名requestPermissions以及第二個引數看出,是支援一次性申請多個許可權的,系統會通過對話方塊逐一詢問使用者是否授權。
          */
         ActivityCompat.requestPermissions(this, new String[]{Manifest.permission.CALL_PHONE}, 1);
     } else {
         //有許可權,直接呼叫撥打電話的方法
         mLoginPresenter.call(this);
     }
    
  3. 在Activity中重寫onRequestPermissionsResult方法,處理請求許可權的回撥。首先驗證requestCode定位到你的申請,然後驗grantResults對應於申請的結果,這裡的陣列對應於申請時的第二個許可權字串陣列。如果你同時申請兩個許可權,那麼grantResults的length就為2,分別記錄你兩個許可權的申請結果。如果申請成功,就可以做你的事情了。

     @Override
     public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
         super.onRequestPermissionsResult(requestCode, permissions, grantResults);
         switch (requestCode) {
             case 1:
                 if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
                     mLoginPresenter.call(this);
                 } else {
                     Toast.makeText(this, "未授權撥打電話許可權", Toast.LENGTH_LONG).show();
                 }
                 break;
         }
     }
    

申請許可權的基本步驟就如上所示,沒圖沒真相。接下來我們就來看下真相吧。上圖。。。


1758616-f72da6924566fc1c.gif

如果使用者拒絕某授權。下一次彈框,使用者會有一個“不再提醒”的選項的來防止app以後繼續請求授權。如果這個選項在拒絕授權前被使用者勾選了,那麼下次你再點選撥打電話時,Dialog將不會在提示,App什麼也不幹,這對使用者來說是很差的體驗。後文會說處理的方法。

注意:不同手機上,可能提示的方式不同,下面看下下米手機上的提示。(小米4手機上即使你拒絕很多次,它的那個Dialog上也不會出現“不在詢問”的勾選框),可能是國內的手機廠商對Rom做了處理。

1758616-563971ab87c40f18.gif

四、更優雅的處理許可權提示問題

如果使用者拒絕某授權。下一次彈框,使用者會有一個“不再提醒”的選項的來防止app以後繼續請求授權。如果這個選項在拒絕授權前被使用者勾選了。下次為這個許可權請求requestPermissions時,對話方塊就不彈出來了,結果就是,app啥都不幹。這將是很差的使用者體驗,使用者做了操作卻得不到響應。這種情況需要好好處理一下。在請求requestPermissions前,我們需要檢查是否需要展示請求許可權的提示通過activity的shouldShowRequestPermissionRationale方法,如果該方法返回true,則表示使用者已經拒絕過一次許可權,此時我們應該彈一個訊息提示框,表明請求該許可權的原因,讓使用者授權該許可權。程式碼如下:

if (ActivityCompat.checkSelfPermission(this, Manifest.permission.CALL_PHONE) != PackageManager.PERMISSION_GRANTED) {
        if (ActivityCompat.shouldShowRequestPermissionRationale(this, Manifest.permission.CALL_PHONE)) {
            showSecurityMessage("是否授權撥打電話許可權,若未授權,則不能撥打電話。", new DialogInterface.OnClickListener() {
                @Override
                public void onClick(DialogInterface dialog, int which) {
                    ActivityCompat.requestPermissions(LoginActivity.this,
                            new String[]{Manifest.permission.CALL_PHONE},
                            1);
                }
            });
            return;
        }
        /**
         * 請求撥打電話許可權
         * 該方法是非同步的,第一個引數是Context;
         * 第二個引數是需要申請的許可權的字串陣列;
         * 第三個引數為requestCode,主要用於回撥的時候檢測。
         * 可以從方法名requestPermissions以及第二個引數看出,是支援一次性申請多個許可權的,系統會通過對話方塊逐一詢問使用者是否授權。
         */
        ActivityCompat.requestPermissions(this, new String[]{Manifest.permission.CALL_PHONE}, 1);
    } else {
        //有許可權,直接撥打
        mLoginPresenter.call(this);
    }   

private void showSecurityMessage(String message, DialogInterface.OnClickListener okListener) {
    new AlertDialog.Builder(this)
            .setMessage(message)
            .setPositiveButton("是", okListener)
            .setNegativeButton("否", null)
            .create()
            .show();
}

注:當你一次請求多個許可權時,不要忘了為沒個許可權新增解釋說明。

效果圖:

1758616-ca325f3e14f62b44.gif

五、使用相容庫相容舊版本

以上程式碼在android 6.0以上執行沒有問題,但是API 23之前的就不行了,因為沒有那些方法。粗暴的方法就是檢查版本:

if (Build.VERSION.SDK_INT >= 23) {
    // Marshmallow+
} else {
    // Pre-Marshmallow
}

但是太複雜,這裡我們可以使用v4相容庫,已對這個做過相容,用以下函式代替:

  • ContextCompat.checkSelfPermission() 被授權函式返回PERMISSION_GRANTED,否則返回PERMISSION_DENIED ,在所有版本都是如此。
  • ActivityCompat.requestPermissions() 這個方法在M之前版本呼叫,OnRequestPermissionsResultCallback 直接被呼叫,帶著正確的 PERMISSION_GRANTED或者 PERMISSION_DENIED結束 。
  • ActivityCompat.shouldShowRequestPermissionRationale() 如果此函式在M之前呼叫,它將永遠返回false。

用v4包的這三方法,完美相容所有版本!這個方法需要額外的引數,Context or Activity。其它的就沒什麼特別的了。上面的後兩個方法,我們也可以在Fragment中使用,用v13相容包:FragmentCompat.requestPermissions() and FragmentCompat.shouldShowRequestPermissionRationale()和activity效果一樣。

六、使用三方開源庫

以上程式碼在實際開發中寫著還是很麻煩的,只有申請的許可權是危險許可權,那麼就要去檢查。當然,你也可以自己去封裝下,方便自己使用。下面是github上star數最多的關於Permissions庫,大家在開發中可以直接使用。

首發地址:http://blog.csdn.net/listeners_gao/article/details/53606845
參考文章:https://inthecheesefactory.com/blog/things-you-need-to-know-about-android-m-permission-developer-edition/en

相關文章