您好,登錄后才能下訂單哦!
這篇文章主要講解了“為什么說(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)注!
免責(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)容。