Android動態許可權總結

tinycoder發表於2019-03-04

從Android6.0開始,Android系統提供動態申請許可權的機制, APP在使用危險許可權時,需要使用者的授權才可進一步操作。

許可權申請方式

Android系統中許可權申請的方式有兩種,如下圖所示:

Android動態許可權總結

靜態申請

Android6.0以前的系統(API < 23)採用的這種方式,只要使用者在AndroidManifest.xml中註冊了許可權,安裝APP後預設就獲取了這些許可權。這種授權方式安全性極低,如果使用者安裝後沒有關閉相應的許可權,使用者的私密資料很容易被哪些垃圾APP竊取。為了解決這種問題,國內的各大手機廠商為Android5.0以下的系統,針對某些許可權做了一定的限制,即便在Android5.0以下,也需要使用者進行手動授權才可使用,這在某種程度上提高了安全性,但也因沒有統一的標準,從而出現了各種相容問題。

動態申請

隨著系統的升級,Google也意識到靜態申請許可權的弊端,所以在Android6.0中,對許可權進行了重新梳理,將許可權分為普通許可權和危險許可權:

  • 正常許可權:不會給使用者隱私帶來危險的許可權,只要開發者在AndroidManifest.xml中註冊了,系統將自動授權;
  • 危險許可權:可以訪問使用者隱私資料的許可權,必須獲取使用者的同意才可獲得授權;
危險許可權彙總
Android動態許可權總結

在Android開發中我們應該儘可能使用隔離式申請許可權,使用者沒有授權時遮蔽相應功能即可。現在很多內容性APP,使用者進去什麼都沒看到,就需要各種許可權,沒有許可權還不能使用,這可能會引起使用者的反感,當然,也是對使用者安全意識的一種衝擊,時間久了,使用者可能會覺得使用APP就應該授權,從而給一些惡意APP作惡的餘地。

動態申請許可權的過程

Android動態許可權總結

核心API介紹

檢查許可權是否已獲取:

// ContextCompat.java
public static int checkSelfPermission(@NonNull Context context, @NonNull String permission)

// PermissionChecker.java
public static int checkSelfPermission(@NonNull Context context,
            @NonNull String permission)
複製程式碼

這兩個方法都是檢查許可權是否獲取的方法,但ContextCompat.checkSelfPermission在某些系統上(如基於Android8.0的MIUI10檢查簡訊許可權時)有bug, 不能準確判斷許可權是否已獲取,此時可結合PermissionChecker.checkSelfPermission進行判斷, 所以判斷許可權是否已獲取可採用以下實現:

public static boolean hasPermission(@NonNull Context context, @NonNull String permission) {
    if (ContextCompat.checkSelfPermission(context, permission) != PackageManager.PERMISSION_GRANTED
            || PermissionChecker.checkSelfPermission(context, permission) != PackageManager.PERMISSION_GRANTED) {
        return false;
    }

    return true;
}
複製程式碼

申請許可權

當未獲取許可權時,需要向系統請求,請求時使用requestPermissions方法:

// ActivityCompat.java
// 在Activity中申請許可權
public static void requestPermissions(final @NonNull Activity activity,
            final @NonNull String[] permissions, final @IntRange(from = 0) int requestCode)
        

// Fragment.java
// 在Fragment中申請許可權
public final void requestPermissions(@NonNull String[] permissions, int requestCode)
複製程式碼

在Fragment使用ActivityCompat.requestPermissions申請許可權時,如果使用者拒絕了(且勾線了不再提示)請求,Fragment中的onRequestPermissionsResult不會被回撥,也就不能引導使用者開啟許可權。所以在Fragment中應該使用Fragment的成員方法requestPermissions來請求許可權。

檢查APP是否應該向用於展示申請許可權的解釋

// ActivtyCompat.java
// 檢查APP是否應該向用於展示申請許可權的解釋
public static boolean shouldShowRequestPermissionRationale(@NonNull Activity activity, @NonNull String permission);
複製程式碼

此方法的返回值解釋如下:

  • 當APP從未申請過指定的許可權或申請了指定許可權,但被使用者拒絕,且勾選了【不再提示】,返回false;
  • APP申請指定許可權時被使用者拒絕,但未勾選【不再提示】,返回true

所以在使用此方法時,我們要先判斷APP是否已申請過許可權,否則難以判斷返回false的兩種情況。

申請許可權的結果回撥

public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults)
複製程式碼

這是Activity和Fragment中申請許可權的結果回撥方法,其中permissions表示申請的許可權陣列,grantResults表示每個許可權的請求結果。取值為:

// 獲得授權
public static final int PERMISSION_GRANTED = 0;
// 未獲授權
public static final int PERMISSION_DENIED = -1;
複製程式碼

通常申請許可權後的處理邏輯都是在該方法中實現。

動態許可權申請的實現

動態申請許可權的條件:

  • targetSdkVersion >= 23;
  • Android系統版本在6.0及以上;

對於不能同時滿足以上條件的情況,預設使用的靜態申請許可權的方式,但不同的ROM為了安全性,可能對其機制進行了修改,所以可能因ROM不同而有所差異。

瞭解了申請許可權的核心API,接下來就介紹一下在Activity中申請許可權的實現過程,下面以點選申請拍照許可權為例:

private void startPhoto() {
    if (hasPermission(this, new String[]{Manifest.permission.CAMERA})) {
        // 執行拍照的邏輯
    } else {
         ActivityCompat.requestPermissions(context, rnew String[]{Manifest.permission.ACCESS_FINE_LOCATION}),
                        PERMISSION_REQUEST_CODE);
    }
}
複製程式碼

然後在onRequestPermissionsResult中監聽許可權申請的結果:

@Override
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
	super.onRequestPermissionsResult(requestCode, permissions, grantResults);
	
    if (hasPermission(permissions)) {
        // 執行拍照的邏輯
    } else {
        if (ActivityCompat.shouldShowRequestPermissionRationale(context, permissions[refusedPermissionIndex])) {
            // 向使用者展示申請許可權的理由
        } else {
            // 引用使用者去開啟許可權
        }
    }
}
複製程式碼

上面只是一個申請許可權的基本流程,真正實現時還要考慮多許可權問題,版本的相容問題,ROM的相容問題等。

當然,在開發中也不會這樣在每個需要申請許可權的Activity/Fragment中寫這一段程式碼,即便封裝成工具類也需要在每個Activity/Fragment引用,耦合性太高。通常可將許可權申請在一個透明的Activity中實現,這樣在申請許可權時,直接跳轉到該Activity即可。下面推薦一個動態申請許可權的庫供參考。

PermissionManager

PermissionManager是一個基於AOP實現的動態申請許可權的開源庫,目的是讓申請許可權的過程更簡單。當然,也可當做學習Aspectj的的參考專案。具有以下優點:

  • 支援多許可權申請;
  • 簡單易用,一行註解實現許可權申請
  • 提供了可擴充套件的許可權說明和引導設定方式;
  • 提供了阻塞/非阻塞的申請方式;

具體的介紹可參考其原始碼地址:PermissionManager

總結

由於Android碎片化嚴重,國內的各大廠商對許可權這塊也做了不同的處理,所以在動態申請時肯定存在失敗的情況,如果在使用PermissionManager的時候發現問題,歡迎隨時提issue & pull request。

相關文章