詳解android6.0權限

這是一篇遲到的文章。在6.0之前,在應用安裝的時候,提示用戶所需要用到的權限列表,同意之后安裝,該app就被賦予所有的權限,我們暫且稱它為安裝時權限,安裝后,被賦予的權限也無法取消,當然,國內的一些rom會在系統級別進行額外的一層權限管理,這個不在本文敘述范圍之內;在6.0之后,google對權限進行了運行時的管理,而不是在安裝時候,危險權限需要在運行時申請,我們暫且稱它為運行時權限,非危險權限,在安裝時由用戶授予,這樣簡化了應用安裝過程,因為用戶在安裝或更新應用時不需要授予權限,也給予了用戶對app功能更多的控制

權限分組

系統權限主要分為兩類,正常權限危險權限

正常權限不會直接危及用戶的隱私,如果你的應用在它的Manifest中列出了正常權限,系統會自動授予權限

危險權限可以讓app訪問用戶的機密數據,如果你的應用在它的Manifest列出了危險權限,用戶必須明確批準你的app使用該權限

當然,不管哪個版本的android,你應用中所用到的所有權限,不管是正常權限還是危險權限,都需要在應用的Manifest中申明

如果你的設備運行Android 5.1以及5.1以下版本,或者你的應用的目標SDK是22以及22以下版本:如果你在應用的Manifest中申明了危險權限,用戶在安裝時必須授予權限,如果拒絕授予權限,那么系統就不會安裝應用,也就是所謂的“一刀切”方式,不同意所有權限,就不能安裝應用

如果你的設備運行Android 6.0以及6.0以上版本,或者你的目標SDK是23以及23以上版本:應用必須在Manifest中羅列出所有的權限,并且在程序運行時,它必須請求用戶授予每一個危險權限,此時用戶可以授予或者拒絕每一個權限,并且應用程序可以繼續運行有限的功能,即使用戶拒絕了權限請求

注意:從Android 6.0開始(API 23),用戶可以在任何時候,對任何應用撤銷權限,即使app申明的目標SDK低于23

正常權限

正常權限有以下幾個特點

  1. 就是在安裝時,由用戶授予,后續運行時無需再次申請,無需再顯示提醒用戶,用戶也不能取消這些權限(在Android 6.0及以上版本例外,用戶可以通過管理界面撤銷權限)

  2. 對用戶隱私或安全沒有較大影響

正常權限列表

  • ACCESS_LOCATION_EXTRA_COMMANDS

  • ACCESS_NETWORK_STATE

  • ACCESS_NOTIFICATION_POLICY

  • ACCESS_WIFI_STATE

  • BLUETOOTH

  • BLUETOOTH_ADMIN

  • BROADCAST_STICKY

  • CHANGE_NETWORK_STATE

  • CHANGE_WIFI_MULTICAST_STATE

  • CHANGE_WIFI_STATE

  • DISABLE_KEYGUARD

  • EXPAND_STATUS_BAR

  • GET_PACKAGE_SIZE

  • INSTALL_SHORTCUT

  • INTERNET

  • KILL_BACKGROUND_PROCESSES

  • MODIFY_AUDIO_SETTINGS

  • NFC

  • READ_SYNC_SETTINGS

  • READ_SYNC_STATS

  • RECEIVE_BOOT_COMPLETED

  • REORDER_TASKS

  • REQUEST_IGNORE_BATTERY_OPTIMIZATIONS

  • REQUEST_INSTALL_PACKAGES

  • SET_ALARM

  • SET_TIME_ZONE

  • SET_WALLPAPER

  • SET_WALLPAPER_HINTS

  • TRANSMIT_IR

  • UNINSTALL_SHORTCUT

  • USE_FINGERPRINT

  • VIBRATE

  • WAKE_LOCK

  • WRITE_SYNC_SETTINGS

我們大概看一遍過去,可以總結歸納出正常權限有幾類:藍牙,網絡狀態,NFC,指紋,鬧鈴,快捷方式,震動等常用權限

危險權限

危險權限才是運行時權限的主要處理對象,因為這些權限可能引起隱私wenti或者影響其他程序運行,android中危險權限主要以組的形式出現,可以歸納為一下幾個分組:

  • CALENDAR

  • CAMERA

  • CONTACTS

  • LOCATION

  • MICROPHONE

  • PHONE

  • SENSORS

  • SMS

  • STORAGE

具體請看下圖:

android_permissions_group.jpg

那么問題來了

Q1.我們的應用是否必須支持運行時權限?

Android為了應用兼容,實際上是可以不需要支持運行時權限的,只要設置targetSdkVersion低于23就可以了,意思是我的應用還乜有在API 23上面完全兼容,不要給我開啟運行時權限新特性,當然了,早晚你還是要支持的,時間問題而已

Q2.如果不支持運行時權限,應用會崩潰么?

可能會奔潰,具體要根據你使用運行時權限在代碼的什么地方,假設你應用啟動就開始創建SDCard文件夾,但是在Android 6.0的設備上,系統或者用戶在系統權限管理界面禁止了你的SDCard權限,如果應用代碼處理不當,此時重新啟動應用就會發生奔潰

這里我們還要注意的一點是,危險權限是以組的方式授予的,怎么理解呢?按照我們上面列出的9大分組,舉個栗子,如果你申請讀取SDCard權限,在用戶授予權限后,你自動就獲得了寫入SDCard的權限,也就是說你獲得了STORAGE分組的所有權限

運行時權限申請

API使用

申請運行時權限主要使用到的API有下面三個:


# 檢測系統當前是否被授予某個運行時權限,傳入參數是權限名稱,比如Manifest.permission.READ_EXTERNAL_STORAGE

ContextCompat.checkSelfPermission(@NonNull Context context, @NonNull String permission)

# 是否要顯示權限說明

# 1.用戶第一次被拒絕(非永久拒絕)授予某個權限后,下次再次請求該權限,這個方法會返回true,用戶有機會以某種方式對用戶進行說明該權限用處

# 2.用戶在第一次拒絕某個權限后,下次再次申請時,授權的dialog中將會出現“不再提醒”選項,一旦選中勾選了,那么下次申請將不會提示用戶。

# 3.第二次請求權限時,用戶拒絕了,并選擇了“不再提醒”的選項,調用shouldShowRequestPermissionRationale()后返回false。

# 4.設備的策略禁止當前應用獲取這個權限的授權:shouldShowRequestPermissionRationale()返回false 。

# 5.加這個提醒的好處在于,用戶拒絕過一次權限后我們再次申請時可以提醒該權限的重要性,免得再次申請時用戶勾選“不再提醒”并決絕,導致下次申請權限直接失敗。

ActivityCompat.shouldShowRequestPermissionRationale(@NonNull Activity activity,@NonNull String permission)

# 請求權限授予,當然可以傳入多個權限名稱同時申請,用戶會依次彈出多個提示框申請權限,App不能配置和修改這個對話框,如果需要提示用戶這個權限相關的信息或說明,需要在調用 requestPermissions() 之前處理,該方法有兩個參數:

# int requestCode,會在回調onRequestPermissionsResult()時返回,用來判斷是哪個授權申請的回調。

# String[] permissions,權限數組,你需要申請的的權限的數組。

# 由于該方法是異步的,所以無返回值,當用戶處理完授權操作時,會回調Activity或者Fragment的onRequestPermissionsResult()方法。

ActivityCompat.requestPermissions(final @NonNull Activity activity,final @NonNull String[] permissions, final @IntRange(from = 0) int requestCode)

# 用戶拒絕或者授予用戶權限后的回調方法,該方法在Activity/Fragment中應該被重寫,當用戶處理完授權操作時,系統會自動回調該方法,該方法有三個參數:

# int requestCode,在調用requestPermissions()時的第一個參數。

# String[] permissions,權限數組,在調用requestPermissions()時的第二個參數。

# int[] grantResults,授權結果數組,對應permissions,具體值和上方提到的PackageManager中的兩個常量做比較。

onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults)

我們以請求讀寫SDCard權限,來看一下一個標準的請求權限流程


# 判斷當前系統版本是都是6.0以上

if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {

# 檢測是否已經被用戶授予該權限

if (ContextCompat.checkSelfPermission(LaunchActivity.this, Manifest.permission.READ_EXTERNAL_STORAGE) !=           PackageManager.PERMISSION_GRANTED) {

# 沒有被授予權限

# 判斷如果已經被用戶拒絕過一次(非永久拒絕),則顯示用戶此權限的一些說明,這里可以用toast,當然也可以用自定義界面來顯示給用戶,如果是采用自定義界面來顯示給用戶,在有“確定”,“取消”等按鈕的情況下,下面的“請求權限”步驟要酌情調用

if (ActivityCompat.shouldShowRequestPermissionRationale(LaunchActivity.this, Manifest.permission.READ_EXTERNAL_STORAGE)) {

Toast.makeText(LaunchActivity.this, "request read external storage", Toast.LENGTH_LONG).show();

}

# 請求權限,數組傳入多個值,可以一次請求多個權限

ActivityCompat.requestPermissions(LaunchActivity.this,

new String[]{Manifest.permission.READ_EXTERNAL_STORAGE},

PERMISSIONS_REQUEST_EXTERNAL_STORAGE);

}

} else {

# 6.0以下版本,直接使用權限

// 做一些權限對應的操作

}

如果是在6.0及6.0以上版本時,執行 ActivityCompat.requestPermissions()方法請求權限后,如果用戶同意或者拒絕后,會回調onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults)方法,該方法的幾個參數,我們做一下解釋:

  1. requestCode 對應的是ActivityCompat.requestPermissions請求時的requestCode,這個與Activity中常用的onActivityResult方法中的requestCode類似,用來標識某一次或者某一個請求的回調對應關系

  2. permissions 對應ActivityCompat.requestPermissions()方法中請求的權限列表

  3. grantResults 對應2中permissions的每個權限的用戶應答結果

好了,接下來就是根據requestCode,遍歷permissions和grantResults中的結果,做對應的操作


@Override

public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {

super.onRequestPermissionsResult(requestCode, permissions, grantResults);

switch (requestCode) {

case PERMISSIONS_REQUEST_EXTERNAL_STORAGE: {

// If request is cancelled, the result arrays are empty.

if (grantResults.length > 0

&& grantResults[0] == PackageManager.PERMISSION_GRANTED) {

// permission was granted, yay! Do the

// contacts-related task you need to do.

} else {

// permission denied, boo! Disable the

// functionality that depends on this permission.

}

return;

}

}

}

注意事項

API問題

由于checkSelfPermission和requestPermissions從API 23才加入,低于23版本,需要在運行時判斷 或者使用Support Library v4中提供的方法

  • ContextCompat.checkSelfPermission

  • ActivityCompat.requestPermissions

  • ActivityCompat.shouldShowRequestPermissionRationale

兩個權限

運行時權限對于應用影響比較大的權限有兩個,他們分別是

  • READ_PHONE_STATE

  • WRITE_EXTERNAL_STORAGE/READ_EXTERNAL_STORAGE

其中READ_PHONE_STATE用來獲取deviceID,即IMEI號碼。這是很多統計依賴計算設備唯一ID的參考。如果新的權限導致讀取不到,避免導致統計的異常。建議在完全支持運行時權限之前,將對應的值寫入到App本地數據中,對于新安裝的,可以采取其他策略減少對統計的影響。

WRITE_EXTERNAL_STORAGE/READ_EXTERNAL_STORAGE這兩個權限和外置存儲(即sdcard)有關,對于下載相關的應用這一點還是比較重要的,我們應該盡可能的說明和引導用戶授予該權限。

建議

  • 不要使用多余的權限,新增權限時要慎重

  • 使用Intent來替代某些權限,如撥打電話,選擇圖片(和你的產品經理PK去吧)

  • 對于使用權限獲取的某些值,比如deviceId,盡量本地存儲,下次訪問直接使用本地的數據值

注意,由于用戶可以撤銷某些權限,所以不要使用應用本地的標志位來記錄是否獲取到某權限,在使用的時候要遵循流程實時判斷后使用,避免不正確的使用導致應用崩潰

注意

  • 即使支持了運行時權限,也要在Manifest聲明,因為市場應用會根據這個信息和硬件設備進行匹配,決定你的應用是否在該設備上顯示。

  • 防止一次請求太多的權限或請求次數太多,用戶可能對你的應用感到厭煩,在應用啟動的時候,最好先請求應用必須的一些權限,非必須權限在使用的時候才請求,建議整理并按照上述分類管理自己的權限

  • 權限申請彈出的對話框不能自定義

  • 解釋你的應用為什么需要這些權限:在你調用requestPermissions()之前,你為什么需要這個權限

  • 個人覺得Marshmallow的運行時權限對于用戶來說絕對是一個好東西,但是目前想要支持需要做的事情還是比較多的。

  • 對于一個有很多依賴的宿主應用,想要做到支持還是有一些工作量的,因為你的權限申請受制于依賴。

最后,通過以上使用例子的代碼我們看到,雖然原理和內容很簡單,但是流程上我們還是要寫很多代碼,判斷幾個條件,稍后我將在另外一片文章中根據請求運行時權限的特點,封裝出一個庫EPermission ,建議大家使用這個庫,簡化權限申請流程

附錄

權限結果常量

PackageManager.PERMISSION_DENIED:該權限是被拒絕的。

PackageManager.PERMISSION_GRANTED:該權限是被授權的。

權限名稱

Manifest.permission.READ_EXTERNAL_STORAGE

Manifest.permission.CAMERA

等等Manifest.permission類中對應的常量

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容