溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊(cè)×
其他方式登錄
點(diǎn)擊 登錄注冊(cè) 即表示同意《億速云用戶服務(wù)條款》

為什么說(shuō)在Android中請(qǐng)求權(quán)限從來(lái)都不是一件簡(jiǎn)單的事情

發(fā)布時(shí)間:2021-10-22 16:30:42 來(lái)源:億速云 閱讀:96 作者:iii 欄目:移動(dòng)開(kāi)發(fā)

這篇文章主要講解了“為什么說(shuō)在Android中請(qǐng)求權(quán)限從來(lái)都不是一件簡(jiǎn)單的事情”,文中的講解內(nèi)容簡(jiǎn)單清晰,易于學(xué)習(xí)與理解,下面請(qǐng)大家跟著小編的思路慢慢深入,一起來(lái)研究和學(xué)習(xí)“為什么說(shuō)在Android中請(qǐng)求權(quán)限從來(lái)都不是一件簡(jiǎn)單的事情”吧!

假設(shè)我正在開(kāi)發(fā)一個(gè)拍照功能,拍照功能通常都需要用到相機(jī)權(quán)限和定位權(quán)限,也就是說(shuō),這兩個(gè)權(quán)限是我實(shí)現(xiàn)拍照功能的先決條件,一定要用戶同意了這兩個(gè)權(quán)限我才能繼續(xù)進(jìn)行拍照。

那么怎樣去申請(qǐng)這兩個(gè)權(quán)限呢?Android 提供的運(yùn)行時(shí)權(quán)限 API 相信每個(gè)人都很熟悉了,我們自然而然可以寫(xiě)出如下代碼:

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<String>, grantResults: IntArray) {         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, "您拒絕了某項(xiàng)權(quán)限,無(wú)法進(jìn)行拍照", Toast.LENGTH_SHORT).show()                 }             }         }     }      fun takePicture() {         Toast.makeText(this, "開(kāi)始拍照", Toast.LENGTH_SHORT).show()     }  }

可以看到,這里先是通過(guò)調(diào)用 requestPermissions() 方法請(qǐng)求相機(jī)權(quán)限和定位權(quán)限,然后在  onRequestPermissionsResult()  方法里監(jiān)聽(tīng)授權(quán)的結(jié)果。如果用戶同意了這兩個(gè)權(quán)限,那么我們就可以去進(jìn)行拍照了,如果用戶拒絕了任意一個(gè)權(quán)限,那么彈出一個(gè) Toast  提示,告訴用戶某項(xiàng)權(quán)限被拒絕了,從而無(wú)法進(jìn)行拍照。

這種寫(xiě)法麻煩嗎?這個(gè)就仁者見(jiàn)仁智者見(jiàn)智了,有些朋友可能覺(jué)得這也沒(méi)多少行代碼呀,有什么麻煩的。但我個(gè)人認(rèn)為還是比較麻煩的,每次需要請(qǐng)求運(yùn)行時(shí)權(quán)限時(shí),我都會(huì)覺(jué)得很心累,不想寫(xiě)這么啰嗦的代碼。

不過(guò)我們暫時(shí)不從簡(jiǎn)易性的角度考慮,從正確性的角度上來(lái)講,這種寫(xiě)法對(duì)嗎?我認(rèn)為是有問(wèn)題的,因?yàn)槲覀冊(cè)跈?quán)限被拒絕時(shí)只是彈了一個(gè) Toast  來(lái)提醒用戶,并沒(méi)有提供后續(xù)的操作方案,用戶如果真的拒絕了某個(gè)權(quán)限,應(yīng)用程序就無(wú)法繼續(xù)使用了。

因此,我們還需要提供一種機(jī)制,當(dāng)權(quán)限被用戶拒絕時(shí),可以再次重新請(qǐng)求權(quán)限。

現(xiàn)在我對(duì)代碼進(jìn)行如下修改:

class MainActivity : AppCompatActivity() {      override fun onCreate(savedInstanceState: Bundle?) {         super.onCreate(savedInstanceState)         setContentView(R.layout.activity_main)         requestPermissions()     }      override fun onRequestPermissionsResult(requestCode: Int, permissions: Array<String>, grantResults: IntArray) {         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("拍照功能需要您同意相機(jī)和定位權(quán)限")                         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, "開(kāi)始拍照", Toast.LENGTH_SHORT).show()     }  }

這里我將請(qǐng)求權(quán)限的代碼提取到了一個(gè) requestPermissions() 方法當(dāng)中,然后在 onRequestPermissionsResult()  里判斷,如果用戶拒絕了某項(xiàng)權(quán)限,那么就彈出一個(gè)對(duì)話框,告訴用戶相機(jī)和定位權(quán)限是必須的,然后在 setPositiveButton 的點(diǎn)擊事件中調(diào)用  requestPermissions() 方法重新請(qǐng)求權(quán)限。

可以看到,現(xiàn)在我們對(duì)權(quán)限被拒絕的場(chǎng)景進(jìn)行了更加充分的考慮。

那么現(xiàn)在這種寫(xiě)法,是不是就將請(qǐng)求運(yùn)行時(shí)權(quán)限的各種場(chǎng)景都考慮周全了呢?其實(shí)還沒(méi)有,因?yàn)?Android 權(quán)限系統(tǒng)還提供了一種非常 “惡心”  的機(jī)制,叫拒絕并不再詢問(wèn)。

當(dāng)某個(gè)權(quán)限被用戶拒絕了一次,下次我們?nèi)绻偕暾?qǐng)這個(gè)權(quán)限的話,界面上會(huì)多出一個(gè)拒絕并不再詢問(wèn)的選項(xiàng)。只要用戶選擇了這一項(xiàng),那么完了,我們之后都不能再去請(qǐng)求這個(gè)權(quán)限了,因?yàn)橄到y(tǒng)會(huì)直接返回我們權(quán)限被拒絕。

這種機(jī)制對(duì)于用戶來(lái)說(shuō)非常友好,因?yàn)樗梢苑乐挂恍阂廛浖髅ナ降責(zé)o限重復(fù)申請(qǐng)權(quán)限,從而嚴(yán)重騷擾用戶。但是對(duì)于開(kāi)發(fā)者來(lái)說(shuō),卻讓我們苦不堪言,如果我的某項(xiàng)功能就是必須依賴于這個(gè)權(quán)限才能運(yùn)行,現(xiàn)在用戶把它拒絕并不再詢問(wèn)了,我該怎么辦?

當(dāng)然,絕大多數(shù)的用戶都不是傻 X,當(dāng)然知道拍照功能需要用到相機(jī)權(quán)限了,相信 99% 的用戶都會(huì)點(diǎn)擊同意授權(quán)。但是我們可以不考慮那剩下 1%  的用戶嗎?不可以,因?yàn)槟銈児镜臏y(cè)試就是那 1% 的用戶,他們會(huì)進(jìn)行這種傻 X 式的操作。

也就是說(shuō),即使只為了那 1% 的用戶,為了這種不太可能會(huì)出現(xiàn)的操作方式,我們?cè)诔绦蛑羞€是得要將這種場(chǎng)景充分考慮進(jìn)去。

那么,權(quán)限被拒絕且不再詢問(wèn)了,我們?cè)撊绾翁幚砟?比較通用的處理方式就是提醒用戶手動(dòng)去設(shè)置當(dāng)中打開(kāi)權(quán)限,如果想做得再好一點(diǎn),可以提供一個(gè)自動(dòng)跳轉(zhuǎn)到當(dāng)前應(yīng)用程序設(shè)置界面的功能。

下面我們就來(lái)針對(duì)這種場(chǎng)景進(jìn)行完善,如下所示:

class MainActivity : AppCompatActivity() {      override fun onCreate(savedInstanceState: Bundle?) {         super.onCreate(savedInstanceState)         setContentView(R.layout.activity_main)         requestPermissions()     }      override fun onRequestPermissionsResult(requestCode: Int, permissions: Array<String>, grantResults: IntArray) {         super.onRequestPermissionsResult(requestCode, permissions, grantResults)         when (requestCode) {             1 -> {                 val denied = ArrayList<String>()                 val deniedAndNeverAskAgain = ArrayList<String>()                 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("拍照功能需要您同意相冊(cè)和定位權(quán)限")                             setCancelable(false)                             setPositiveButton("確定") { _, _ ->                                 requestPermissions()                             }                         }.show()                     } else {                         AlertDialog.Builder(this).apply {                             setMessage("您需要去設(shè)置當(dāng)中同意相冊(cè)和定位權(quán)限")                             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, "開(kāi)始拍照", Toast.LENGTH_SHORT).show()     }  }

現(xiàn)在代碼已經(jīng)變得比較長(zhǎng)了,我還是帶著大家來(lái)梳理一下。

這里我在 onRequestPermissionsResult() 方法中增加了 denied 和 deniedAndNeverAskAgain  兩個(gè)集合,分別用于記錄拒絕和拒絕并不再詢問(wèn)的權(quán)限。如果這兩個(gè)集合都為空,那么說(shuō)明所有權(quán)限都被授權(quán)了,這時(shí)就可以直接進(jìn)行拍照了。

而如果 denied 集合不為空,則說(shuō)明有權(quán)限被用戶拒絕了,這時(shí)候我們還是彈出一個(gè)對(duì)話框來(lái)提醒用戶,并重新申請(qǐng)權(quán)限。而如果  deniedAndNeverAskAgain 不為空,說(shuō)明有權(quán)限被用戶拒絕且不再詢問(wèn),這時(shí)就只能提示用戶去設(shè)置當(dāng)中手動(dòng)打開(kāi)權(quán)限,我們編寫(xiě)了一個(gè) Intent  來(lái)執(zhí)行跳轉(zhuǎn)邏輯,并在 onActivityResult() 方法,也就是用戶從設(shè)置回來(lái)的時(shí)候重新申請(qǐng)權(quán)限。

可以看到,當(dāng)我們第一次拒絕權(quán)限的時(shí)候,會(huì)提醒用戶,相機(jī)和定位權(quán)限是必須的。而如果用戶繼續(xù)置之不理,選擇拒絕并不再詢問(wèn),那么我們將提醒用戶,他必須手動(dòng)開(kāi)戶這些權(quán)限才能繼續(xù)運(yùn)行程序。

到現(xiàn)在為止,我們才算是把一個(gè) “簡(jiǎn)單”  的權(quán)限請(qǐng)求流程用比較完善的方式處理完畢。然而代碼寫(xiě)到這里真的還算是簡(jiǎn)單嗎?每次申請(qǐng)運(yùn)行時(shí)權(quán)限,都要寫(xiě)這么長(zhǎng)長(zhǎng)的一段代碼,你真的受得了嗎?

這也就是我編寫(xiě) PermissionX 這個(gè)開(kāi)源庫(kù)的原因,在 Android 中請(qǐng)求權(quán)限從來(lái)都不是一件簡(jiǎn)單的事情,但它不應(yīng)該如此復(fù)雜。

PermissionX  將請(qǐng)求運(yùn)行時(shí)權(quán)限時(shí)那些應(yīng)該考慮的復(fù)雜邏輯都封裝到了內(nèi)部,只暴露最簡(jiǎn)單的接口給開(kāi)發(fā)者,從而讓大家不需要考慮上面我所討論的那么多場(chǎng)景。

而我們使用 PermissionX 來(lái)實(shí)現(xiàn)和上述一模一樣的功能,只需要這樣寫(xiě)就可以了:

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 = "拍照功能需要您同意相冊(cè)和定位權(quán)限"                 val ok = "確定"                 scope.showRequestReasonDialog(deniedList, message, ok)             }             .onForwardToSettings { scope, deniedList ->                 val message = "您需要去設(shè)置當(dāng)中同意相冊(cè)和定位權(quán)限"                 val ok = "確定"                 scope.showForwardToSettingsDialog(deniedList, message, ok)             }             .request { _, _, _ ->                 takePicture()             }     }      fun takePicture() {         Toast.makeText(this, "開(kāi)始拍照", Toast.LENGTH_SHORT).show()     }  }

可以看到,請(qǐng)求權(quán)限的代碼一下子變得極其精簡(jiǎn)。

我們只需要在 permissions() 方法中傳入要請(qǐng)求的權(quán)限名,在 onExplainRequestReason() 和  onForwardToSettings() 回調(diào)中填寫(xiě)對(duì)話框上的提示信息,然后在 request() 回調(diào)中即可保證已經(jīng)得到了所有請(qǐng)求權(quán)限的授權(quán),調(diào)用  takePicture() 方法開(kāi)始拍照即可。

通過(guò)這樣的直觀對(duì)比大家應(yīng)該能感受到 PermissionX  所帶來(lái)的便利了吧?上面那段長(zhǎng)長(zhǎng)的請(qǐng)求權(quán)限的代碼我真的是為了給大家演示才寫(xiě)的,而我再也不想寫(xiě)第二遍了。

另外,本篇文章主要只是演示了一下 PermissionX 的易用性,并不涉及其中具體的諸多用法,如 Android 11  兼容性,自定義對(duì)話框樣式等等。如果大家感興趣的話,更多用法請(qǐng)參考下面的鏈接。

  • Android 運(yùn)行時(shí)權(quán)限終極方案,用 PermissionX 吧

  • PermissionX 現(xiàn)在支持 Java 了!還有 Android 11 權(quán)限變更講解

  • PermissionX 重磅更新,支持自定義權(quán)限提醒對(duì)話框

在項(xiàng)目中引入 PermissionX 也非常簡(jiǎn)單,只需要添加如下的依賴即可:

dependencies {     ...     implementation 'com.permissionx.guolindev:permissionx:1.3.1' }

感謝各位的閱讀,以上就是“為什么說(shuō)在Android中請(qǐng)求權(quán)限從來(lái)都不是一件簡(jiǎn)單的事情”的內(nèi)容了,經(jīng)過(guò)本文的學(xué)習(xí)后,相信大家對(duì)為什么說(shuō)在Android中請(qǐng)求權(quán)限從來(lái)都不是一件簡(jiǎn)單的事情這一問(wèn)題有了更深刻的體會(huì),具體使用情況還需要大家實(shí)踐驗(yàn)證。這里是億速云,小編將為大家推送更多相關(guān)知識(shí)點(diǎn)的文章,歡迎關(guān)注!

向AI問(wèn)一下細(xì)節(jié)

免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點(diǎn)不代表本網(wǎng)站立場(chǎng),如果涉及侵權(quán)請(qǐng)聯(lián)系站長(zhǎng)郵箱:is@yisu.com進(jìn)行舉報(bào),并提供相關(guān)證據(jù),一經(jīng)查實(shí),將立刻刪除涉嫌侵權(quán)內(nèi)容。

AI