Android - 运行时权限 PermissionX

查看作者各版本专栏

一、版本权限变更

1.1 前台Service

Android 10获取定位需要Manifest配置
Android 11增加了摄像头和麦克风需要Manifest配置
PermissionX正常代码申请,记得在Manifest中对Service配置就行。
<service ...
    android:foregroundServiceType="location"    //10
    android:foregroundServiceType="location|camera|microphone"    //11
/>

1.2 后台定位

后台定位:ACCESS_BACKGROUND_LOCATION

前台定位:ACCESS_FINE_LOCATION、ACCESS_COARSE_LOCATION

Android 10后台定位和前台定位需要一起申请(不具备前台资格怎么可能具备后台)
【始终允许】表示都同意
【仅在使用时允许】表示只允许前台
【拒绝】表示都不允许
Android 11同时申请后台和前台会崩溃,申请后台之前需要已经授权了前台。
PermissionX同时申请前台和后台权限会就行,会自动处理:≤9只申请前台,10同时申请,11分开申请自动最后申请后台。

1.3 蓝牙

Android 12

修复使用蓝牙扫描附近设备的时候需要申请定位权限(蓝牙不是运行时权限,定位是),被拆封成三个运行时权限,用到哪个申请哪个(属于同一个权限组):

【BLUETOOTH_SCAN】用于使用蓝牙扫描附件其他的蓝牙设备

【BLUETOOTH_ADVERTISE】用于允许当前的设备被其他的蓝牙设备所发现

【BLUETOOTH_CONNECT】用于连接之前已经配对过的蓝牙设备

//≥12用到哪个申请哪个(都是同一个权限组)
<uses-permission android:name="android.permission.BLUETOOTH_SCAN" />
<uses-permission android:name="android.permission.BLUETOOTH_ADVERTISE" />
<uses-permission android:name="android.permission.BLUETOOTH_CONNECT" />
<uses-permission android:name="android.permission.BLUETOOTH" android:maxSdkVersion="30" />
<uses-permission android:name="android.permission.BLUETOOTH_ADMIN" android:maxSdkVersion="30" />

if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
    add(Manifest.permission.BLUETOOTH_SCAN)
    add(Manifest.permission.BLUETOOTH_ADVERTISE)
    add(Manifest.permission.BLUETOOTH_CONNECT)
}

1.4 应用安装

Android 88之前只要用户在手机上开启“允许安装未知来源的应用”选项就可以任意安装APP,8之后每次安装都会自动让用户同意一遍。对于开发者来说只要Manifest申请权限就行,但国内魔改ROM会在第一次安装时提醒,未授权后面不会再提醒。手动代码申请一下就行。
<uses-permission android:name="android.permission.REQUEST_INSTALL_PACKAGES" />

add(Manifest.permission.REQUEST_INSTALL_PACKAGES)

1.5  媒体文件

Android 13

当 targetSsk≥33 时申请 READ_EXTERNAL_STORAGE 无效果,取而代之的是更加细分的权限,图片音频视频需要申请以下三种权限,其他文件需要跳转到系统文件管理器,详见MediaStore。

【READ_MEDIA_IMAGES】图片

【READ_MEDIA_VIDEO】视频

【READ_MEDIA_AUDIO】音频

//≥13用到哪个申请哪个(图片视频是同一个权限组,音频是别的)
<uses-permission android:name="android.permission.READ_MEDIA_IMAGES" />
<uses-permission android:name="android.permission.READ_MEDIA_AUDIO" />
<uses-permission android:name="android.permission.READ_MEDIA_VIDEO" />
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" android:maxSdkVersion="32" />

if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
    add(Manifest.permission.READ_MEDIA_IMAGES)
    add(Manifest.permission.READ_MEDIA_AUDIO)
    add(Manifest.permission.READ_MEDIA_VIDEO)
}

1.6 通知

Android 8加入通知渠道,桌面图标角标。
Android 1313之前系统默认APP有通知权限可以随时发,13之后发的时候需要申请,也不能像之前那样检测被关闭后跳转引导开启。
<uses-permission android:name="android.permission.POST_NOTIFICATIONS"/>

if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
    add(Manifest.permission.POST_NOTIFICATIONS)
}
//或者使用这个就不用手动判断了
add(PermissionX.permission.POST_NOTIFICATIONS)

1.7 后台运动传感器权限

Android 13

13之前申请了前后台都可以使用,13之后只能前台用,多了一个后台,并且需要前台通过了才能申请后台,一起申请还会同时被拒。
PermissionX两个一起申请,并对后台做SDK判断
<uses-permission android:name="android.permission.BODY_SENSORS"/>
<uses-permission android:name="android.permission.BODY_SENSORS_BACKGROUND"/>

add(Manifest.permission.BODY_SENSORS)
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
    add(Manifest.permission.BODY_SENSORS_BACKGROUND)
}

1.8 附近WiFi设备权限

Android 13

修复使用Wifi调用热点、直连、RTTT等功能的时候需要申请定位权限(Wifi不是运行时权限,定位是),被拆封成四个运行时权限,用到哪个申请哪个(属于同一个权限组):

【BLUETOOTH_SCAN】
【BLUETOOTH_ADVERTISE】
【BLUETOOTH_CONNECT】

【NEARBY_WIFI_DEVICES】

<uses-permission android:name="android.permission.NEARBY_WIFI_DEVICES"/>

add(Manifest.permission.NEARBY_WIFI_DEVICES)

二、使用 PermissionX

查看最新版本

默认弹窗上的文字已适配国际化(只需要保证自己传入的文字使用@String)、颜色已适配深色模式。

implementation 'com.guolindev.permissionx:permissionx:1.7.1'

2.1 配置Manifest

<uses-permission android:name="android.permission.CALL_PHONE" />
<uses-permission android:name="android.permission.CAMERA" />

2.2 权限申请 & 回调处理

//定义申请的权限清单
val requestList = ArrayList<String>().apply {
    //一般添加权限
    add(Manifest.permission.CALL_PHONE)
    //有些权限存在版本区别,需要判断
    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
        add(Manifest.permission.BLUETOOTH_SCAN) //12的蓝牙
    }
}
if (requestList.isNotEmpty()) {
    //初始化
    PermissionX.init(this)
        //要申请的权限列表
        .permissions(Manifest.permission.CALL_PHONE, Manifest.permission.CAMERA)
        //(可选)修改默认对话框的颜色(浅色主题、深色主题)(默认已适配深色模式)
        .setDialogTintColor(Color.parseColor("#008577"), Color.parseColor("#83e8dd"))
        //(可选)在申请前先弹窗说明(下方通过beforeRequest区分)
        .explainReasonBeforeRequest()
        //弹窗申请原因(提供功能函数的上下文对象,被拒绝或者还未申请的权限清单,是否是申请前)
        .onExplainRequestReason { scope, deniedList, beforeRequest ->
            if (beforeRequest) {
                //申请前说明(上方需要添加.explainReasonBeforeRequest())
                scope.showRequestReasonDialog(
                    deniedList,"为了保证程序正常工作,请您同意以下权限申请","我已明白")
            } else {
                //拒绝后说明(会再次申请)
                val filteredList = deniedList.filter {
                    it == Manifest.permission.CAMERA
                }
                //使用默认的弹窗
                scope.showRequestReasonDialog(
                    filteredList,"摄像机权限是程序必须依赖的权限","同意","拒绝")
                //(可选)使用自定义弹窗
//                            val str = "摄像机权限是程序必须依赖的权限"
//                            val myDialog = MyDialog(context, str, deniedList)
//                            scope.showRequestReasonDialog(myDialog)
            }
        }
        //针对被拒绝且不再询问的权限进行解释并跳转到系统设置
        .onForwardToSettings { scope, deniedList ->
            scope.showForwardToSettingsDialog(deniedList, "您需要去应用程序设置当中手动开启权限", "我已明白")
        }
        //申请权限并接收结果(是否全通过、通过的权限名单、拒绝的权限名单)
        .request { allGranted, grantedList, deniedList ->
            if (allGranted) {
                Toast.makeText(this, "所有申请的权限都已通过", Toast.LENGTH_SHORT).show()
            } else {
                Toast.makeText(this, "您拒绝了如下权限:$deniedList", Toast.LENGTH_SHORT).show()
            }
        }
}

2.3 创建自定义弹窗 (可选)

2.3.1 通过 Dialog

//自定义弹窗
class MyDialog(context: Context, val str: String, val permissions: List<String>) : RationaleDialog(context) {

    private val groupSet = HashSet<String>()
    private lateinit var negativeBtn: Button
    private lateinit var positiveBtn: Button
    private lateinit var permissionsLayout: LinearLayout

    //一个权限组包含多个权限
    private val permissionMap = mapOf(
        Manifest.permission.READ_CALENDAR to Manifest.permission_group.CALENDAR,
        Manifest.permission.WRITE_CALENDAR to Manifest.permission_group.CALENDAR,
        Manifest.permission.READ_CALL_LOG to Manifest.permission_group.CALL_LOG,
        Manifest.permission.WRITE_CALL_LOG to Manifest.permission_group.CALL_LOG,
        Manifest.permission.PROCESS_OUTGOING_CALLS to Manifest.permission_group.CALL_LOG,
        Manifest.permission.CAMERA to Manifest.permission_group.CAMERA,
        Manifest.permission.READ_CONTACTS to Manifest.permission_group.CONTACTS,
        Manifest.permission.WRITE_CONTACTS to Manifest.permission_group.CONTACTS,
        Manifest.permission.GET_ACCOUNTS to Manifest.permission_group.CONTACTS,
        Manifest.permission.ACCESS_FINE_LOCATION to Manifest.permission_group.LOCATION,
        Manifest.permission.ACCESS_COARSE_LOCATION to Manifest.permission_group.LOCATION,
        Manifest.permission.ACCESS_BACKGROUND_LOCATION to Manifest.permission_group.LOCATION,
        Manifest.permission.RECORD_AUDIO to Manifest.permission_group.MICROPHONE,
        Manifest.permission.READ_PHONE_STATE to Manifest.permission_group.PHONE,
        Manifest.permission.READ_PHONE_NUMBERS to Manifest.permission_group.PHONE,
        Manifest.permission.CALL_PHONE to Manifest.permission_group.PHONE,
        Manifest.permission.ANSWER_PHONE_CALLS to Manifest.permission_group.PHONE,
        Manifest.permission.ADD_VOICEMAIL to Manifest.permission_group.PHONE,
        Manifest.permission.USE_SIP to Manifest.permission_group.PHONE,
        Manifest.permission.ACCEPT_HANDOVER to Manifest.permission_group.PHONE,
        Manifest.permission.BODY_SENSORS to Manifest.permission_group.SENSORS,
        Manifest.permission.ACTIVITY_RECOGNITION to Manifest.permission_group.ACTIVITY_RECOGNITION,
        Manifest.permission.SEND_SMS to Manifest.permission_group.SMS,
        Manifest.permission.RECEIVE_SMS to Manifest.permission_group.SMS,
        Manifest.permission.READ_SMS to Manifest.permission_group.SMS,
        Manifest.permission.RECEIVE_WAP_PUSH to Manifest.permission_group.SMS,
        Manifest.permission.RECEIVE_MMS to Manifest.permission_group.SMS,
        Manifest.permission.READ_EXTERNAL_STORAGE to Manifest.permission_group.STORAGE,
        Manifest.permission.WRITE_EXTERNAL_STORAGE to Manifest.permission_group.STORAGE,
        Manifest.permission.ACCESS_MEDIA_LOCATION to Manifest.permission_group.STORAGE
    )

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.dialog_permission)
        val messageText: TextView = findViewById(R.id.messageText)
        negativeBtn = findViewById(R.id.negativeBtn)
        positiveBtn = findViewById(R.id.positiveBtn)
        permissionsLayout = findViewById(R.id.permissionsLayout)
        messageText.text = str
        buildPermissionsLayout()
        window?.let {
            val param = it.attributes
            val width = (context.resources.displayMetrics.widthPixels * 0.8).toInt()
            val height = param.height
            it.setLayout(width, height)
        }
    }

    //确定按钮
    override fun getPositiveButton(): View {
        return positiveBtn
    }

    //取消按钮(弹窗不可取消就返回null)
    override fun getNegativeButton(): View {
        return negativeBtn
    }

    //要申请的权限列表
    override fun getPermissionsToRequest(): List<String> {
        return permissions
    }

    //申请是需要使用权限名,一个权限通过后该组其他权限会一起通过,但只需要显示组名给用户看
    private fun buildPermissionsLayout() {
        for (permission in permissions) {
            val permissionGroup = permissionMap[permission]
            if (permissionGroup != null && !groupSet.contains(permissionGroup)) {
                val textView = LayoutInflater.from(context).inflate(R.layout.textview_permission, permissionsLayout, false) as TextView
                textView.text =context.packageManager.getPermissionGroupInfo(permissionGroup, 0).loadLabel(context.packageManager)
                permissionsLayout.addView(textView)
                groupSet.add(permissionGroup)
            }
        }
    }

}

2.3.2 通过 DialogFragment

//具体实现和上面的 Dialog 方式一样
class MyDialogFragment(context: Context, val str: String, val permissions: List<String>) : RationaleDialogFragment() {
    override fun getPositiveButton(): View { }
    override fun getNegativeButton(): View? { }
    override fun getPermissionsToRequest(): MutableList<String> { }
}

2.4 特殊权限申请

 特殊权限,既不属于普通权限也不属于危险权限,申请的时候需要使用intent跳转到系统界面让用户开启。

悬浮窗Android 6开始,只在自己的APP内是不需要申请权限的,悬浮在其他APP上需要。

【Manifest权限名称】<uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW" />

【intent跳转的action】Settings.ACTION_MANAGE_OVERLAY_PERMISSION

【判断是否授权的API】Settings.canDrawOverlays(context)

修改系统设置Android 6开始,允许APP读写系统设置。

【Manifest权限名称】<uses-permission android:name="android.permission.WRITE_SETTINGS" />

【intent跳转的action】Settings.ACTION_MANAGE_WRITE_SETTINGS

【判断是否授权的API】Settings.System.canWrite(context)

管理外置存储Android 11开始,强制启用作用域存储 Scoped Storage 使得所有APP不再对SD卡进行全局读写(10就有了),允许读整个SD卡进行读写。

【Manifest权限名称】<uses-permission android:name="android.permission.MANAGE_EXTERNAL_STORAGE" />

【intent跳转的action】Settings.ACTION_MANAGE_ALL_FILES_ACCESS_PERMISSION

【判断是否授权的API】Environment.isExternalStorageManager()

//手写
if (Build.VERSION.SDK_INT >= 23) {
    if (Settings.canDrawOverlays(context)) {
        showFloatView()
    } else {
        val intent = Intent(Settings.ACTION_MANAGE_OVERLAY_PERMISSION)
        startActivity(intent)
    }
} else {
    showFloatView()
}
//使用PermissionX
PermissionX.init(activity)
    .permissions(Manifest.permission.SYSTEM_ALERT_WINDOW)
    .onExplainRequestReason { scope, deniedList ->
        val message = "PermissionX需要您同意以下权限才能正常使用"
        scope.showRequestReasonDialog(deniedList, message, "Allow", "Deny")
    }
    .request { allGranted, grantedList, deniedList ->
        if (allGranted) {
            Toast.makeText(activity, "所有申请的权限都已通过", Toast.LENGTH_SHORT).show()
        } else {
            Toast.makeText(activity, "您拒绝了如下权限:$deniedList",Toast.LENGTH_SHORT).show()
        }
    }

三、纯手写示例

<uses-permission android:name="android.permission.CALL_PHONE" />
//定义请求码(唯一值),不能为负数
val CALL_PERMISSION = 1
//申请权限后重写这个回调(对用户选择结果进行处理)
override fun onRequestPermissionsResult(requestCode: Int,  permissions: Array<out String>,  grantResults: IntArray) {
    super.onRequestPermissionsResult(requestCode, permissions, grantResults)
    //根据对应的请求码去分类处理
    when (requestCode) {
        CALL_PERMISSION -> {
            //申请通过就执行方法
            if (grantResults.isNotEmpty()&&grantResults[0]==PackageManager.PERMISSION_GRANTED) {
                call()
            } else {
                //申请被拒绝就用对话框、吐司等方式提示
                Toast.makeText(this,"不授权怎么用?",Toast.LENGTH_SHORT).show()
            }
        }
    }
}
//对call()进行封装
fun callWrapper(){
    //拿到权限状态
    val permission = ContextCompat.checkSelfPermission(this, android.Manifest.permission.CALL_PHONE)
    //没有授权就申请,有授权就直接调用
    if(permission != PackageManager.PERMISSION_GRANTED){
        //参数一当前Activity,参数二把要申请的权限名放入数组中,参数三是唯一的请求码
        ActivityCompat.requestPermissions(this, arrayOf(android.Manifest.permission.CALL_PHONE),CALL_PERMISSION)
    } else {
        call()
    }
}
//具体涉及涉及风险权限的业务代码
private fun call(){
    try {
        val intent= Intent(Intent.ACTION_CALL)
        intent.data= Uri.parse("tel:10086")
        startActivity(intent)
    }catch (e:SecurityException){
        e.printStackTrace()
    }
}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Android中,运行时权限是指在应用程序运行过程中,用户需要对某些危险操作进行授权的权限。在 Android 6.0(即targetSdkVersion < 23)之前,应用程序被授予所有申请的权限。然而,在此之后,用户不需要在安装软件时一次性授权所有申请的权限,而是可以在软件的使用过程中再对某一项权限申请进行授权。这就是运行时权限的核心。Android将所有权限分为普通权限和危险权限两类。普通权限是系统自动帮我们授权的,我们只需在注册文件中声明即可。而危险权限必须经过用户手动点击授权才能使用。危险权限包括9组24个权限。 在程序运行时申请权限的过程中,我们需要首先在AndroidManifest.xml文件中声明所需要的权限。以申请打电话的运行时权限为例,可以在文件中添加如下代码: <uses-permission android:name="android.permission.CALL_PHONE"/> 需要注意的是,在Android 5.1(API 22)或更低版本,并且应用的targetSdkVersion是22或更低版本时,系统会在安装应用时要求用户授权权限。即使在安装时已经授予应用所有权限,在Android 6.0之后依然可以通过"Setting"来关闭已经授予的权限。在请求权限时,系统只告知用户应用需要的权限组,而不告知具体权限。因此,在未检查授权的情况下直接使用危险权限,会导致程序崩溃。为了解决这个问题,我们可以使用v4包中的ContextCompat来处理权限,这样不需要考虑版本问题。相关API包括checkSelfPermission()等。<span class="em">1</span><span class="em">2</span><span class="em">3</span> #### 引用[.reference_title] - *1* *2* [Android运行时权限](https://blog.csdn.net/Cristiano_san/article/details/119840235)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_2"}}] [.reference_item style="max-width: 50%"] - *3* [Android 运行时权限终极总结](https://blog.csdn.net/LucasXu01/article/details/80860070)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_2"}}] [.reference_item style="max-width: 50%"] [ .reference_list ]

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值