關(guān)于Android 6.0權(quán)限的動(dòng)態(tài)適配詳解
前言
Android6.0代號(hào)棉花糖。盡管是在15年I/O大會(huì)上Google被正式發(fā)布的了。但是看看大多數(shù)人的項(xiàng)目中大家的 targetSdkVersion 是不是還都用的22。大家都認(rèn)為6.0+的市場(chǎng)占有率還沒那么高。那么就請(qǐng)看谷歌2017年9月份公布的版本分布圖。
從數(shù)據(jù)來(lái)看確實(shí)沒那么高O(∩_∩)O。6.0+的市場(chǎng)占有率僅為50% ̄□ ̄||。只因安卓用戶的基數(shù)太大了吧。延伸至各種人群。雖然說(shuō)占比才一半但時(shí)基數(shù)大總的用戶數(shù)量還是蠻多的。這兩天剛做完6.0權(quán)限的適配。那么請(qǐng)說(shuō)一下自己測(cè)試的時(shí)候踩的坑吧(*╹▽╹*)
權(quán)限管理系統(tǒng)的變化
在Android6.0(M)之前,在用戶安裝應(yīng)用的時(shí)候會(huì)產(chǎn)生一個(gè)權(quán)限列表,只有用戶允許這些權(quán)限后,應(yīng)用才可以正常的安裝,這就會(huì)產(chǎn)生一個(gè)問題,這些權(quán)限對(duì)用戶是不具有感知性的,也就是說(shuō)用戶都不知道你要這些權(quán)限干什么,我明明裝的是一個(gè)閱讀類型的應(yīng)用,你卻要我撥打電話的權(quán)限,你想干嘛呢?當(dāng)然絕大部分的開發(fā)者是善意的,但也避免不了一些特殊人群利用這些“漏洞”做一些不好的事情。
而在Android6.0(M)之后,用戶是可以不管權(quán)限直接安裝應(yīng)用的,當(dāng)應(yīng)用需要調(diào)用某些權(quán)限的時(shí)候,會(huì)給予用戶一個(gè)通知與說(shuō)明,我要這些權(quán)限干什么,這樣下來(lái)可以讓用戶有更加清醒的權(quán)限分配意識(shí),也在一定程度上更加人性化的保護(hù)了用戶的隱私,避免了“權(quán)限一刀切”。
權(quán)限的分組
在Android6.0(M)之后,對(duì)權(quán)限進(jìn)行了分類,大致有這三種:
- 普通權(quán)限
- 危險(xiǎn)權(quán)限
- 特殊權(quán)限
普通權(quán)限:也就是正常權(quán)限,是對(duì)手機(jī)的一些正常操作,對(duì)用戶的隱私?jīng)]有太大影響的權(quán)限,比如手機(jī)的震動(dòng),網(wǎng)絡(luò)訪問,藍(lán)牙等權(quán)限,這些權(quán)限會(huì)在應(yīng)用被安裝的時(shí)候默認(rèn)授予,用戶不能拒絕,也不能取消。
普通權(quán)限列表:
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 INTERNET KILL_BACKGROUND_PROCESSES MODIFY_AUDIO_SETTINGS NFC READ_SYNC_SETTINGS READ_SYNC_STATS RECEIVE_BOOT_COMPLETED REORDER_TASKS REQUEST_INSTALL_PACKAGES SET_TIME_ZONE SET_WALLPAPER SET_WALLPAPER_HINTS TRANSMIT_IR USE_FINGERPRINT VIBRATE WAKE_LOCK WRITE_SYNC_SETTINGS SET_ALARM INSTALL_SHORTCUT UNINSTALL_SHORTCUT
對(duì)于上面這些普通權(quán)限 在Android6.0以前我們只需要在清單文件中聲明該權(quán)限即可。
危險(xiǎn)權(quán)限:其實(shí)就是運(yùn)行中需要處理的權(quán)限,也是我們最需要注意的權(quán)限,這些權(quán)限會(huì)關(guān)系到用戶的隱私或影響到其他應(yīng)用的運(yùn)行,這些危險(xiǎn)權(quán)限,谷歌還做了一個(gè)權(quán)限組,以分組的形式來(lái)呈現(xiàn):
由于運(yùn)行權(quán)限機(jī)制的出現(xiàn),我們需要對(duì)新開發(fā)的應(yīng)用去做適配。
當(dāng)你的應(yīng)用targetSdkVersion小于23的時(shí)候,當(dāng)應(yīng)用用于6.0以上的系統(tǒng)時(shí)候,它也會(huì)默認(rèn)采用以前的權(quán)限管理機(jī)制。當(dāng)你的targetSdkVersion大于等于23的時(shí)候且在Andorid6.0(M)系統(tǒng)上,它才會(huì)采用新的這套權(quán)限管理機(jī)制。
所以如果你想逃開這個(gè)“麻煩”,只要把targetSdkVersion的版本設(shè)置為低于23就可以了,不過不建議采用這種方案,該來(lái)的總是要來(lái)的,隨著國(guó)產(chǎn)手機(jī)ROM的更新,比如小米,華為等也開始有部分機(jī)型進(jìn)行了系統(tǒng)升級(jí),所以這是種趨勢(shì)。
說(shuō)了這么多,那么來(lái)看下怎么進(jìn)行Android6.0(M)的權(quán)限管理適配吧,其實(shí)很簡(jiǎn)單,只需要記住下面幾個(gè)API方法就可以:(API23之后提供)
int checkSelfPermission(String permission)
用來(lái)檢測(cè)應(yīng)用是否已經(jīng)具有權(quán)限void requestPermissions(String[] permissions, int requestCode)
進(jìn)行請(qǐng)求單個(gè)或多個(gè)權(quán)限void onRequestPermissionsResult(int requestCode, String[] permissions, int[] grantResults)
請(qǐng)求權(quán)限結(jié)果回調(diào)
checkSelfPermission(String permission) 方法返回值有兩個(gè):
- PERMISSION_DENIED = -1:代表當(dāng)前檢查的權(quán)限沒有被授權(quán)
- PERMISSION_GRANTED = 0;代表當(dāng)前的檢查的權(quán)限已經(jīng)被授權(quán)
requestPermissions(String[] permissions, int requestCode)
參數(shù)一:要請(qǐng)求的權(quán)限組 權(quán)限2請(qǐng)求碼
onRequestPermissionsResult(int requestCode, String[] permissions, int[] grantResults)
請(qǐng)求的回調(diào)。
參數(shù)3對(duì)應(yīng) 對(duì)應(yīng)permissions的權(quán)限請(qǐng)求結(jié)果(PERMISSION_GRANTED或者PERMISSION_DENIED)
看完關(guān)鍵的三個(gè)方法接下來(lái)上我的油條:
object MQPermissionUtil { private var mRequestCode = -1 private val isOverMarshmallow: Boolean = Build.VERSION.SDK_INT >= Build.VERSION_CODES.M private var mOnPermissionListener: OnPermissionListener? = null fun requestPermissions(activity: Activity, requestCode: Int, permissions: Array<String>, isCancelFinish: Boolean, onPermissionGrantedListener: OnPermissionGrantedListener) { requestPermissionsResult(activity, requestCode, permissions, object : OnPermissionListener { override fun onPermissionGranted() { onPermissionGrantedListener.onPermissionGranted() } override fun onPermissionDenied() { if (isCancelFinish) { showTipsDialogWel(activity) } else { showTipsDialog(activity) } } }) } private fun requestPermissionsResult(activity: Activity, requestCode: Int, permissions: Array<String>, callback: OnPermissionListener) { mOnPermissionListener = callback if (checkPermissions(activity, *permissions)) { if (mOnPermissionListener != null) mOnPermissionListener!!.onPermissionGranted() } else { val deniedPermissions = getDeniedPermissions(activity, *permissions) if (deniedPermissions.isNotEmpty()) { mRequestCode = requestCode ActivityCompat.requestPermissions(activity, deniedPermissions .toTypedArray(), requestCode) } } } fun onRequestPermissionsResult(requestCode: Int, permissions: Array<String>, grantResults: IntArray) { if (requestCode == mRequestCode) { if (verifyPermissions(grantResults)) { if (mOnPermissionListener != null) mOnPermissionListener!!.onPermissionGranted() } else { if (mOnPermissionListener != null) mOnPermissionListener!!.onPermissionDenied() } } } private fun startAppSettings(context: Context) { val intent = Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS) intent.data = Uri.parse("package:" + context.packageName) context.startActivity(intent) } private fun verifyPermissions(grantResults: IntArray): Boolean { if (grantResults.isEmpty()) return false // 循環(huán)判斷每個(gè)權(quán)限是否被拒絕 for (grantResult in grantResults) { if (grantResult != PackageManager.PERMISSION_GRANTED) { return false } } return true } private fun getDeniedPermissions(context: Context, vararg permissions: String): List<String> { val deniedPermissions = ArrayList<String>() for (permission in permissions) { if (ContextCompat.checkSelfPermission(context, permission) == PackageManager.PERMISSION_DENIED) { deniedPermissions.add(permission) } } return deniedPermissions } private fun checkPermissions(context: Context, vararg permissions: String): Boolean { if (isOverMarshmallow) { for (permission in permissions) { if (ContextCompat.checkSelfPermission(context, permission) == PackageManager.PERMISSION_DENIED) { return false } } } return true } fun showTipsDialog(activity: Activity) { AlertDialog.Builder(activity) .setTitle("提示信息") .setMessage("當(dāng)前應(yīng)用缺少必要權(quán)限,無(wú)法正常使用,請(qǐng)單擊【確定】按鈕前往設(shè)置中心進(jìn)行權(quán)限授權(quán)。") .setNegativeButton("取消", null) .setPositiveButton("確定") { _, _ -> activity.finish() startAppSettings(activity) }.show() } fun showTipsDialogWel(activity: Activity) { AlertDialog.Builder(activity) .setTitle("提示信息") .setMessage("當(dāng)前應(yīng)用缺少必要權(quán)限,無(wú)法正常使用,請(qǐng)單擊【確定】按鈕前往設(shè)置中心進(jìn)行權(quán)限授權(quán)。") .setNegativeButton("取消") { _, _ -> activity.finish() } .setPositiveButton("確定") { _, _ -> activity.finish() startAppSettings(activity) }.show() } interface OnPermissionGrantedListener { fun onPermissionGranted() } interface OnPermissionListener { fun onPermissionGranted() fun onPermissionDenied() } }
寫的不好。大家自行修改吧。
Activity中的使用在onCreate中一開始調(diào)用一下代碼:
MangoPermissionUtil.requestPermissions(this@IndexActivity, Constant.PERMISSION_OPERATION_CODE_SCAN, arrayOf(Manifest.permission.CAMERA), false, object : MangoPermissionUtil.OnPermissionGrantedListener { override fun onPermissionGranted() { //在這表示用戶同意了權(quán)限申請(qǐng)。 //假如用戶拒絕了權(quán)限申請(qǐng)?jiān)谶@兒我是沒讓他進(jìn)入到應(yīng)用中的效果如下 } })
只要有任何一個(gè)權(quán)限用戶沒通過都會(huì)彈出這個(gè)Dialog。直到用戶全部授權(quán)。。。。
點(diǎn)擊取消退出應(yīng)用。確定按鈕去到設(shè)置界面為應(yīng)用授權(quán)。。。。
下面是應(yīng)用啟動(dòng)的場(chǎng)景(很舒服2333)
還有個(gè)劇TM惡心的問題這些所有的邏輯在除了小米6.xxx的設(shè)備上跑是沒問題的。必須全部授權(quán)才能進(jìn)入應(yīng)用。但是小米6.xxx的設(shè)備上當(dāng)我第一次拒絕了權(quán)限申請(qǐng)之后。第二次進(jìn)入應(yīng)用判斷權(quán)限的時(shí)候它竟然在checkPermisssion的方法中給我返回了PERMISSION_GRANTED這就比較尷尬了。這樣我是可以進(jìn)入掉權(quán)限請(qǐng)求成功的回調(diào)。但是我進(jìn)去之后確實(shí)沒權(quán)限啊。對(duì)應(yīng)權(quán)限相關(guān)的操作一樣不能執(zhí)行。。。不得不說(shuō)小米的6.xxx設(shè)備是真的坑。。。。
還有一點(diǎn)油條用的時(shí)候還要在當(dāng)前申請(qǐng)的Activity中調(diào)用一下來(lái)執(zhí)行到油條中自定義的回調(diào)
override fun onRequestPermissionsResult(requestCode: Int, permissions: Array<String>, grantResults: IntArray) { MangoPermissionUtil.onRequestPermissionsResult(requestCode, permissions, grantResults) super.onRequestPermissionsResult(requestCode, permissions, grantResults) }
最后
對(duì)于一些比較特別的權(quán)限,比如文件的讀寫權(quán)限,一般在我們第一次開啟APP的時(shí)候就要去獲取了,假設(shè)我們一開始沒有獲取到這個(gè)權(quán)限,那么如果我的首頁(yè)有輪播廣告圖,這個(gè)廣告圖是網(wǎng)絡(luò)獲取的,做了三級(jí)緩存,這樣就會(huì)到導(dǎo)致磁盤緩存無(wú)法寫入。這邊提供一個(gè)解決方法,就是在你引導(dǎo)APP啟動(dòng)的時(shí)候,就引導(dǎo)用戶去獲取權(quán)限,當(dāng)用戶拒絕的時(shí)候,應(yīng)該給出彈出框并跳轉(zhuǎn)對(duì)應(yīng)的應(yīng)用權(quán)限管理界面(需要對(duì)不同機(jī)型進(jìn)行設(shè)置)。
可以參考微信的做法:
啟動(dòng)app,在閃屏頁(yè)的時(shí)候向用戶提出權(quán)限的申請(qǐng)
- 存儲(chǔ)空間權(quán)限,關(guān)閉微信
- 電話權(quán)限,關(guān)閉微信
- 位置權(quán)限,關(guān)閉微信
- 進(jìn)入app:
- 發(fā)照片時(shí),申請(qǐng)照片權(quán)限
- 發(fā)語(yǔ)音時(shí),申請(qǐng)麥克風(fēng)權(quán)限
- 用戶每次點(diǎn)擊拒絕,都彈出自定義對(duì)話框,提示用戶設(shè)置權(quán)限
總結(jié)
以上就是這篇文章的全部?jī)?nèi)容了,希望本文的內(nèi)容對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,如果有疑問大家可以留言交流,謝謝大家對(duì)腳本之家的支持。
- Android 如何實(shí)現(xiàn)動(dòng)態(tài)申請(qǐng)權(quán)限
- Android 拍照選擇圖片并上傳功能的實(shí)現(xiàn)思路(包含權(quán)限動(dòng)態(tài)獲取)
- 詳解Android開發(fā)錄音和播放音頻的步驟(動(dòng)態(tài)獲取權(quán)限)
- Android 6.0動(dòng)態(tài)權(quán)限及跳轉(zhuǎn)GPS設(shè)置界面的方法
- 安卓Android6.0權(quán)限動(dòng)態(tài)獲取操作示例
- android6.0權(quán)限動(dòng)態(tài)申請(qǐng)框架permissiondispatcher的方法
- Android 6.0動(dòng)態(tài)權(quán)限申請(qǐng)教程
- Android6.0動(dòng)態(tài)申請(qǐng)權(quán)限所遇到的問題小結(jié)
- Android中不支持動(dòng)態(tài)申請(qǐng)權(quán)限的原因
- Android6.0獲取動(dòng)態(tài)權(quán)限代碼示例
相關(guān)文章
Android編程動(dòng)態(tài)加載布局實(shí)例詳解【附demo源碼】
這篇文章主要介紹了Android編程動(dòng)態(tài)加載布局,結(jié)合實(shí)例形式分析了Android動(dòng)態(tài)加載布局的原理、操作步驟與相關(guān)實(shí)現(xiàn)技巧,需要的朋友可以參考下2016-10-10Android使用glide加載gif動(dòng)畫設(shè)置播放次數(shù)
這篇文章主要為大家詳細(xì)介紹了Android使用glide加載gif動(dòng)畫設(shè)置播放次數(shù),文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2016-11-11android中UIColletionView瀑布流布局實(shí)現(xiàn)思路以及封裝的實(shí)現(xiàn)
本篇文章主要介紹了android中UIColletionView瀑布流布局實(shí)現(xiàn)思路以及封裝的實(shí)現(xiàn),具有一定的參考價(jià)值,有興趣的可以了解一下。<BR>2017-02-02保持Android Service在手機(jī)休眠后繼續(xù)運(yùn)行的方法
下面小編就為大家分享一篇保持Android Service在手機(jī)休眠后繼續(xù)運(yùn)行的方法,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過來(lái)看看吧2018-03-03Android事件分發(fā)機(jī)制深入刨析原理及源碼
Android?的事件分發(fā)機(jī)制大體可以分為三部分:事件生產(chǎn)、事件分發(fā)?、事件消費(fèi)。事件的生產(chǎn)是由用戶點(diǎn)擊屏幕產(chǎn)生,我們這次著重分析事件的分發(fā)和消費(fèi),因?yàn)槭录职l(fā)和處理聯(lián)系的過于緊密,這篇文章將把事件的分發(fā)和消費(fèi)放在一起分析2023-04-04Android實(shí)現(xiàn)Activity界面切換添加動(dòng)畫特效的方法
這篇文章主要介紹了Android實(shí)現(xiàn)Activity界面切換添加動(dòng)畫特效的方法,非常實(shí)用的技巧,需要的朋友可以參考下2014-08-08