Android許可權最佳實踐

weixin_34124651發表於2016-05-20

提示:這篇文章寫的比較早了,一直沒時間“翻新”,現在看來存在一些問題,等後面會重新寫一篇。讀者簡單參考即可。

前言

大家好我是光源

從 Android6.0 開始Android的許可權模式有了一番更改,從安裝時一股腦列給使用者,到執行時動態申請許可權。對於 Android開發者而言,這是一個重要的變更。

討論這個問題之前,我們得先檢查一下專案的 target version。如果是 23及以上,則必須得適配新許可權模式;如果是 23之下,則還是統一在安裝時全部申請許可權——即便如此,使用Android 6.0及以上系統的使用者還是可以在設定中去關閉你的某些許可權。當許可權被關閉時,不會導致你的應用直接崩潰,但是會導致你獲取到的返回值為null 或者 0,這個是需要注意的地方。

以下是 Android 官網給出的最佳實踐,草草翻譯,有不妥之處還請斧正。

正文

app很容易用海量的許可權請求淹沒使用者。如果使用者發現 app 影響使用或者擔憂 app 濫用使用者個人資訊,他們可能不再使用或者完全刪除這個app。以下最佳實踐能幫助你避免這種糟糕的使用者體驗。

優先使用 Intent

在很多情況下,你都可以選擇兩種方式來為你的app完成任務。你可以讓你的app去請求許可權來自己完成操作,或者你也可以讓 app 利用 intent 讓其他 app 進行這項工作。

舉個例子假如你的 app 需要用裝置中的相機來拍照,你的 app 可以請求 CAMERA 許可權 以直接使用相機元件,然後你的 app 將使用相機元件介面去控制相機並拍照。這個方法使你的 app 有完整的攝影程式控制權,讓你將相機UI納入app中【譯者注:即可以完全自定義相機UI】。

然而,如果你不需要這麼完整的控制,你可以使用 ACTION_IMAGE_CAPTURE intent 去請求圖片。當你傳送這個intent,系統讓使用者選擇一個相機 app(如果還沒有預設的相機 app)。 使用者用選中的相機 app 拍照後,它將照片反回給你的 app 的 onActivityResult() 方法。

同樣得,如果你需要打電話、讀寫使用者的通訊錄等等,你可以通過建立合適的 intent 來實現或者請求許可權後直接訪問合適的元件。二者各有利弊。

如果你使用許可權:

  • 你的 app 在執行該操作時對使用者體驗有完整的掌控。但是這麼寬泛的控制增加了任務的複雜度,因為你需要自行設計合適的UI。
  • 不管在執行時還是剛安裝時,使用者傾向於只進行一次許可權操作,此後,你的 app 就可以進行操作無需使用者額外授權。但是,如果使用者沒有允許許可權或者後來取消了,你的 app 將一直無法進行該操作。

如果你使用 intent:

  • 你不需要為該操作設計UI。處理 intent 的 app 提供了UI。然而,這意味著你無法掌控這部分使用者體驗。使用者可能跟你一無所知的 app 打交道。
  • 如果使用者沒有處理該操作的預設應用,系統會讓使用者選擇一個。如果使用者沒有指定一個預設處理,每次進行該操作都會彈出額外的選擇對話方塊。

只�申請必須的許可權

你每次申請許可權都在迫使使用者做決定,所以你應該減少發出這些請求的次數。如果使用者使用的是 Android 6.0 及之後的系統,每次使用者使用某些 app 中需要許可權的特性,app 就必須因申請許可權而打斷使用者。如果使用者使用 6.0之前的系統,在安裝 app 時使用者必須得允許每一個許可權。如果許可權清單太長或者看起來不合適,使用者可能會決定停止安裝你的 app。因為以上種種,你最好減少申請的許可權數量。

不要壓垮使用者

如果使用者使用 Android6.0及之後的系統,在執行 app時需要授權。如果你把一大堆許可權申請一次性拋給使用者,你會把他們壓垮進而刪除你的應用。因而你應該只在必要時才去請求許可權。

在某些情況下,對你的 app 而言一個或多個許可權是完全必要的,在 app 啟動時就去請求這些許可權是有意義的。比如你做了一個攝影 app,它需要使用相機。當使用者第一次啟動該 app 時,他們自然不會驚訝請求相機的許可權。但是如果這個app還有通過使用者的通訊錄分享圖片的功能的話,你就不應該在第一次啟動時獲取 READ_CONTACTS 許可權。而應該等到使用者使用”分享“功能時再請求許可權。

解釋你為何需要許可權

當你呼叫[requestPermissions()](https://developer.android.com/reference/android/support/v4/app/ActivityCompat.html#requestPermissions(android.app.Activity, java.lang.String[], int)) 表明你的 app 需要什麼許可權時系統會顯示許可權對話方塊,但是沒有說為什麼。在某些情況下,使用者會感到困惑。在呼叫[requestPermissions()](https://developer.android.com/reference/android/support/v4/app/ActivityCompat.html#requestPermissions(android.app.Activity, java.lang.String[], int)) 前向使用者解釋你的 app 為何需要這些許可權是個好主意。

比如,一個拍照 app 可能會需要定位服務以便給圖片打上地理位置標籤。一個普通使用者可能無法理解一張圖片可以包含位置資訊,然後會困惑為什麼拍照 app 會需要知道他的位置。所以在這種情況下,app 在呼叫[requestPermissions()](https://developer.android.com/reference/android/support/v4/app/ActivityCompat.html#requestPermissions(android.app.Activity, java.lang.String[], int)) 前告訴使用者這個特性是個好主意。

告知使用者的一個不錯的方法是把這些許可權請求合併進 app 引導中。app 引導可以依次介紹 app 的特性,在做這些的同時,可以解釋需要哪些許可權。例如,拍照 app的引導可以示範”通過通訊錄分享照片“的功能,告知使用者他們需要授權 app 檢視使用者的通訊錄。然後該 app 可以呼叫[requestPermissions()](https://developer.android.com/reference/android/support/v4/app/ActivityCompat.html#requestPermissions(android.app.Activity, java.lang.String[], int)) 請求讀取通訊錄。當然,不是所有使用者都會跟從引導,所以你依然需要檢查確認並且在 app 的日常使用中請求許可權。

測試兩種許可權模式

從 Android 6.0 開始,使用者在應用執行時允許或者拒絕許可權而不是在應用安裝時,這導致你需要測試各種條件下app 的表現。

在 Android 6.0之前,你可以合理假定你的 app 一直可執行,它擁有所有在 app manifest中宣告的許可權。在新的許可權模式下,你不能這麼認為了。

以下提示將幫助你驗證 Android 6.0及以上系統的許可權相關程式碼問題。

  • 驗證 app 當前許可權以及相關程式碼路徑。

  • 測試使用者通過許可權保護服務和資料。

  • 測試多種許可權分別被允許、拒絕的組合情況。比如,相機 app 可能在 manifest 檔案中宣告瞭CAMERAREAD_CONTACTSACCESS_FINE_LOCATION 許可權。你應該測試每個許可權開啟和關閉的情況來確保 app 可以優雅地處理所有許可權配置情況。記住,從 Android 6.0開始使用者可以開啟或關閉任意一個app的許可權,即便是 targets API 在 22及以下的。

  • 使用 adb 工具通過命令列管理許可權:

    • 以組的形式列出許可權和狀態:

      $ adb shell pm list permissions -d -g
      
    • 允許或拒絕一個或多個許可權:

      $ adb shell pm [grant|revoke] <permission-name> 
      
  • 為使用許可權的服務分析 app

後記

以上是 Android 官網給出的最佳實踐。個人覺得這篇文章主要從使用者體驗角度來闡述許可權的最佳實踐,還缺乏程式碼層面的實踐,因此擴充套件一下。

許可權相關程式碼最佳實踐

第零步,在manifest 檔案中宣告所需的許可權。

第一步,檢測是否擁有許可權。

API 23 or above all version
checkSelfPermission ContextCompat.checkSelfPermission()

被授權函式返回PERMISSION_GRANTED,否則返回PERMISSION_DENIED

第二步,檢測是否需要顯示申請許可權對話方塊。如果第一步返回沒有許可權的話,則我們需要去申請許可權,但是申請許可權之前,需要顯示檢視使用者是否已經禁止了申請許可權對話方塊,如果禁止的話,我們用第三步的程式碼將沒有任何效果。

API 23 or above all version
shouldShowRequestPermissionRationale ActivityCompat.shouldShowRequestPermissionRationale / FragmentCompat.shouldShowRequestPermissionRationale

返回為 truefalse ,在 6.0以下恆定為 false ,這個不用多解釋了吧,在 6.0以下還沒有系統預設對話方塊存在,肯定不彈了。

第三步,顯示提示框去申請許可權。

API 23 or above all version
requestPermissions ActivityCompat.requestPermissions/ FragmentCompat.requestPermissions()

發出請求後,會在 onRequestPermissionsResult 方法中獲得回撥結果。

以目前 Android系統的佔有率分佈,最低建議支援4.0,所以我們可以直接使用相容庫中的三個方法以相容所有版本。下面是一個簡單的需求,向SD卡中寫入資料:

    /**
     * 向SD卡插入資料
     */
    private void insertDataToSDCard() {
        if (ContextCompat.checkSelfPermission(this, Manifest.permission.WRITE_EXTERNAL_STORAGE) == PackageManager.PERMISSION_GRANTED) {
            //擁有許可權
            //TODO 向SD卡寫資料
        } else {
            //沒有許可權,判斷是否會彈許可權申請對話方塊
            boolean shouldShow = ActivityCompat.shouldShowRequestPermissionRationale(this, Manifest.permission.WRITE_EXTERNAL_STORAGE);
            if (shouldShow) {
                //申請許可權
                ActivityCompat.requestPermissions(this, new String[] {Manifest.permission.WRITE_EXTERNAL_STORAGE}, REQUEST_CODE_REQUEST_PERMISSION);
            } else {
                //被禁止顯示彈窗
                //TODO 顯示對話方塊告知使用者必須開啟許可權
            }
        }
    }

    @Override
    public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
        if (requestCode == REQUEST_CODE_REQUEST_PERMISSION) {
            if (grantResults[0] == PackageManager.PERMISSION_GRANTED) {
                //permission granted
                //TODO 向SD卡寫資料
            } else {
                //permission denied
                //TODO 顯示對話方塊告知使用者必須開啟許可權
            }
        }
    }

可見,通過相容庫去做許可權模式相容還是挺簡單明瞭的。考慮到擴充套件性,完全可以�把許可權判斷邏輯寫成一個通用的工具類。

最後,推薦一個第三方庫hotchemi’s PermissionsDispatcher,這個也是參考資料中看到的一個不錯的第三方庫,有興趣可以看看。

參考資料

  1. Permissions Best Practices
  2. Android M 新的執行時許可權開發者需要知道的一切

相關文章