為什麼說在 Android 中請求許可權從來都不是一件簡單的事情?
週末時間參加了東莞和深圳的兩場 GDG,因為都是線上參與,所以時間上並不趕,我只需要坐在家裡等活動開始就行了。
等待的時間一時興起,突然想寫一篇原創,聊一聊我自己在寫 Android 許可權請求程式碼時的一些技術心得。
正如這篇文章標題所描述的一樣,在 Android 中請求許可權從來都不是一件簡單的事情。為什麼?我認為 Google 在設計執行時許可權這塊功能時,充分考慮了使用者的使用體驗,但是卻沒能充分考慮開發者的編碼體驗。
之前在公眾號的留言區和大家討論時,有朋友說:我覺得 Android 提供的執行時許可權 API 很好用呀,並沒有覺得哪裡使用起來麻煩。
一
真的是這樣嗎?我們來看一個具體的例子。
假設我正在開發一個拍照功能,拍照功能通常都需要用到相機許可權和定位許可權,也就是說,這兩個許可權是我實現拍照功能的先決條件,一定要使用者同意了這兩個許可權我才能繼續進行拍照。
那麼怎樣去申請這兩個許可權呢?Android 提供的執行時許可權 API 相信每個人都很熟悉了,我們自然而然可以寫出如下程式碼:
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
ActivityCompat.requestPermissions(this,
arrayOf(Manifest.permission.CAMERA, Manifest.permission.ACCESS_FINE_LOCATION), 1)
}
override fun onRequestPermissionsResult(requestCode: Int, permissions: Array
super.onRequestPermissionsResult(requestCode, permissions, grantResults)
when (requestCode) {
1 -> {
var allGranted = true
for (result in grantResults) {
if (result != PackageManager.PERMISSION_GRANTED) {
allGranted = false
}
}
if (allGranted) {
takePicture()
} else {
Toast.makeText(this, "您拒絕了某項許可權,無法進行拍照", Toast.LENGTH_SHORT).show()
}
}
}
}
fun takePicture() {
Toast.makeText(this, "開始拍照", Toast.LENGTH_SHORT).show()
}
}
可以看到,這裡先是透過呼叫 requestPermissions() 方法請求相機許可權和定位許可權,然後在 onRequestPermissionsResult() 方法裡監聽授權的結果。如果使用者同意了這兩個許可權,那麼我們就可以去進行拍照了,如果使用者拒絕了任意一個許可權,那麼彈出一個 Toast 提示,告訴使用者某項許可權被拒絕了,從而無法進行拍照。
這種寫法麻煩嗎?這個就仁者見仁智者見智了,有些朋友可能覺得這也沒多少行程式碼呀,有什麼麻煩的。但我個人認為還是比較麻煩的,每次需要請求執行時許可權時,我都會覺得很心累,不想寫這麼囉嗦的程式碼。
不過我們暫時不從簡易性的角度考慮,從正確性的角度上來講,這種寫法對嗎?我認為是有問題的,因為我們在許可權被拒絕時只是彈了一個 Toast 來提醒使用者,並沒有提供後續的操作方案,使用者如果真的拒絕了某個許可權,應用程式就無法繼續使用了。
因此,我們還需要提供一種機制,當許可權被使用者拒絕時,可以再次重新請求許可權。
現在我對程式碼進行如下修改:
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
requestPermissions()
}
override fun onRequestPermissionsResult(requestCode: Int, permissions: Array
super.onRequestPermissionsResult(requestCode, permissions, grantResults)
when (requestCode) {
1 -> {
var allGranted = true
for (result in grantResults) {
if (result != PackageManager.PERMISSION_GRANTED) {
allGranted = false
}
}
if (allGranted) {
takePicture()
} else {
AlertDialog.Builder(this).apply {
setMessage("拍照功能需要您同意相機和定位許可權")
setCancelable(false)
setPositiveButton("確定") { _, _ ->
requestPermissions()
}
}.show()
}
}
}
}
fun requestPermissions() {
ActivityCompat.requestPermissions(this,
arrayOf(Manifest.permission.CAMERA, Manifest.permission.ACCESS_FINE_LOCATION), 1)
}
fun takePicture() {
Toast.makeText(this, "開始拍照", Toast.LENGTH_SHORT).show()
}
}
這裡我將請求許可權的程式碼提取到了一個 requestPermissions() 方法當中,然後在 onRequestPermissionsResult() 裡判斷,如果使用者拒絕了某項許可權,那麼就彈出一個對話方塊,告訴使用者相機和定位許可權是必須的,然後在 setPositiveButton 的點選事件中呼叫 requestPermissions() 方法重新請求許可權。
我們來看一下現在的執行效果:
可以看到,現在我們對許可權被拒絕的場景進行了更加充分的考慮。
那麼現在這種寫法,是不是就將請求執行時許可權的各種場景都考慮周全了呢?其實還沒有,因為 Android 許可權系統還提供了一種非常 “噁心” 的機制,叫拒絕並不再詢問。
當某個許可權被使用者拒絕了一次,下次我們如果再申請這個許可權的話,介面上會多出一個拒絕並不再詢問的選項。只要使用者選擇了這一項,那麼完了,我們之後都不能再去請求這個許可權了,因為系統會直接返回我們許可權被拒絕。
這種機制對於使用者來說非常友好,因為它可以防止一些惡意軟體流氓式地無限重複申請許可權,從而嚴重騷擾使用者。但是對於開發者來說,卻讓我們苦不堪言,如果我的某項功能就是必須依賴於這個許可權才能執行,現在使用者把它拒絕並不再詢問了,我該怎麼辦?
當然,絕大多數的使用者都不是傻 X,當然知道拍照功能需要用到相機許可權了,相信 99% 的使用者都會點選同意授權。但是我們可以不考慮那剩下 1% 的使用者嗎?不可以,因為你們公司的測試就是那 1% 的使用者,他們會進行這種傻 X 式的操作。
也就是說,即使只為了那 1% 的使用者,為了這種不太可能會出現的操作方式,我們在程式中還是得要將這種場景充分考慮進去。
二
那麼,許可權被拒絕且不再詢問了,我們該如何處理呢?比較通用的處理方式就是提醒使用者手動去設定當中開啟許可權,如果想做得再好一點,可以提供一個自動跳轉到當前應用程式設定介面的功能。
下面我們就來針對這種場景進行完善,如下所示:
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
requestPermissions()
}
override fun onRequestPermissionsResult(requestCode: Int, permissions: Array
super.onRequestPermissionsResult(requestCode, permissions, grantResults)
when (requestCode) {
1 -> {
val denied = ArrayList
val deniedAndNeverAskAgain = ArrayList
grantResults.forEachIndexed { index, result ->
if (result != PackageManager.PERMISSION_GRANTED) {
if (ActivityCompat.shouldShowRequestPermissionRationale(this, permissions[index])) {
denied.add(permissions[index])
} else {
deniedAndNeverAskAgain.add(permissions[index])
}
}
}
if (denied.isEmpty() && deniedAndNeverAskAgain.isEmpty()) {
takePicture()
} else {
if (denied.isNotEmpty()) {
AlertDialog.Builder(this).apply {
setMessage("拍照功能需要您同意相簿和定位許可權")
setCancelable(false)
setPositiveButton("確定") { _, _ ->
requestPermissions()
}
}.show()
} else {
AlertDialog.Builder(this).apply {
setMessage("您需要去設定當中同意相簿和定位許可權")
setCancelable(false)
setPositiveButton("確定") { _, _ ->
val intent = Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS)
val uri = Uri.fromParts("package", packageName, null)
intent.data = uri
startActivityForResult(intent, 1)
}
}.show()
}
}
}
}
}
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
super.onActivityResult(requestCode, resultCode, data)
when (requestCode) {
1 -> {
requestPermissions()
}
}
}
fun requestPermissions() {
ActivityCompat.requestPermissions(this,
arrayOf(Manifest.permission.CAMERA, Manifest.permission.ACCESS_FINE_LOCATION), 1)
}
fun takePicture() {
Toast.makeText(this, "開始拍照", Toast.LENGTH_SHORT).show()
}
}
現在程式碼已經變得比較長了,我還是帶著大家來梳理一下。
這裡我在 onRequestPermissionsResult() 方法中增加了 denied 和 deniedAndNeverAskAgain 兩個集合,分別用於記錄拒絕和拒絕並不再詢問的許可權。如果這兩個集合都為空,那麼說明所有許可權都被授權了,這時就可以直接進行拍照了。
而如果 denied 集合不為空,則說明有許可權被使用者拒絕了,這時候我們還是彈出一個對話方塊來提醒使用者,並重新申請許可權。而如果 deniedAndNeverAskAgain 不為空,說明有許可權被使用者拒絕且不再詢問,這時就只能提示使用者去設定當中手動開啟許可權,我們編寫了一個 Intent 來執行跳轉邏輯,並在 onActivityResult() 方法,也就是使用者從設定回來的時候重新申請許可權。
那麼現在執行一下程式,效果如下圖所示:
可以看到,當我們第一次拒絕許可權的時候,會提醒使用者,相機和定位許可權是必須的。而如果使用者繼續置之不理,選擇拒絕並不再詢問,那麼我們將提醒使用者,他必須手動開戶這些許可權才能繼續執行程式。
到現在為止,我們才算是把一個 “簡單” 的許可權請求流程用比較完善的方式處理完畢。然而程式碼寫到這裡真的還算是簡單嗎?每次申請執行時許可權,都要寫這麼長長的一段程式碼,你真的受得了嗎?
這也就是我編寫 PermissionX 這個開源庫的原因,在 Android 中請求許可權從來都不是一件簡單的事情,但它不應該如此複雜。
三
PermissionX 將請求執行時許可權時那些應該考慮的複雜邏輯都封裝到了內部,只暴露最簡單的介面給開發者,從而讓大家不需要考慮上面我所討論的那麼多場景。
而我們使用 PermissionX 來實現和上述一模一樣的功能,只需要這樣寫就可以了:
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
PermissionX.init(this)
.permissions(Manifest.permission.CAMERA, Manifest.permission.ACCESS_FINE_LOCATION)
.onExplainRequestReason { scope, deniedList ->
val message = "拍照功能需要您同意相簿和定位許可權"
val ok = "確定"
scope.showRequestReasonDialog(deniedList, message, ok)
}
.onForwardToSettings { scope, deniedList ->
val message = "您需要去設定當中同意相簿和定位許可權"
val ok = "確定"
scope.showForwardToSettingsDialog(deniedList, message, ok)
}
.request { _, _, _ ->
takePicture()
}
}
fun takePicture() {
Toast.makeText(this, "開始拍照", Toast.LENGTH_SHORT).show()
}
}
可以看到,請求許可權的程式碼一下子變得極其精簡。
我們只需要在 permissions() 方法中傳入要請求的許可權名,在 onExplainRequestReason() 和 onForwardToSettings() 回撥中填寫對話方塊上的提示資訊,然後在 request() 回撥中即可保證已經得到了所有請求許可權的授權,呼叫 takePicture() 方法開始拍照即可。
透過這樣的直觀對比大家應該能感受到 PermissionX 所帶來的便利了吧?上面那段長長的請求許可權的程式碼我真的是為了給大家演示才寫的,而我再也不想寫第二遍了。
另外,本篇文章主要只是演示了一下 PermissionX 的易用性,並不涉及其中具體的諸多用法,如 Android 11 相容性,自定義對話方塊樣式等等。如果大家感興趣的話,更多用法請參考下面的連結。
在專案中引入 PermissionX 也非常簡單,只需要新增如下的依賴即可:
dependencies {
...
implementation 'com.permissionx.guolindev:permissionx:1.3.1'
}
作者:郭霖
連結:
來源:掘金
著作權歸作者所有。商業轉載請聯絡作者獲得授權,非商業轉載請註明出處。
來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/4692/viewspace-2796825/,如需轉載,請註明出處,否則將追究法律責任。
相關文章
- Xamarin Android許可權請求Android
- Android 6.0 在執行時請求許可權Android
- android 6.0許可權機制的簡單封裝(支援批量申請許可權)Android封裝
- Android6.0------許可權申請管理(單個許可權和多個許可權申請)Android
- android 6.0許可權申請機制(簡單案例)Android
- 程式請求管理員許可權
- 如何用 Vue 實現前端許可權控制(路由許可權 + 檢視許可權 + 請求許可權)Vue前端路由
- 在Android上優雅的申請許可權Android
- 簡單幾行程式碼申請許可權行程
- 為什麼許可權授權很難?- osohq
- Android動態請求許可權的工具類(轉載、非原創)Android
- android強制申請許可權Android
- Android 許可權清單大全Android
- 許可權之選單許可權
- Android 中的危險許可權Android
- 系統許可權UNLIMITED TABLESPACE為什麼如此特殊?MIT
- Android元件化開發實戰:封裝許可權管理請求框架Android元件化封裝框架
- 請教,什麼樣的許可權算業務,要不要領域模型中完成模型
- linux許可權簡單介紹Linux
- 在Linux中,什麼是檔案許可權?它們是如何工作的?Linux
- Android6.0動態許可權最簡單的解決方法Android
- Android系統許可權和root許可權Android
- django中介軟體中判斷前端是不是ajax請求,從而判斷是否有許可權Django前端
- 為什麼空密碼能夠取得你的ROOT許可權?密碼
- 超級簡單:在asp.net中限制下載檔案的訪問許可權ASP.NET訪問許可權
- 基於註解的6.0許可權動態請求框架——JPermission框架
- Linux系統中501代表什麼許可權?Linux
- Android優雅地申請動態許可權Android
- 原生Android之(6.0及以上)許可權申請Android
- 【framework】framework中為systemserver新增許可權FrameworkServer
- Docker 映象倉庫為什麼要分庫分許可權?Docker
- android許可權大全Android
- Android permission許可權Android
- Android開發-更”聰明”的申請許可權方式Android
- 選單許可權和按鈕許可權設定
- android動態許可權到自定義許可權框架Android框架
- NODE + JWT + Mongo(簡單實現許可權管理)JWTGo
- JEECG 簡單例項講解許可權控制單例