您当前的位置:首页 > 计算机 > 编程开发 > 安卓(android)开发

最容易理解的Android6.0动态权限申请教程

时间:02-05来源:作者:点击数:

1、前言

这已经是N年前的知识点了,但是我一直以来都有点逃避学习这个,而且印象中很麻烦,后来也不间断有学习过一点,但是一直没认真去用过,所以对这个android6.0的动态权限申请一直是不清楚的状态,而且项目中用到动态权限申请的时候我就使用RxPermission来实现。今天心血来潮整了一下,发现其实原生的动态权限申请也没有想象中的麻烦,还是很简单的,所以这里记录一下,以防不用的话后面又忘了。

2、动态权限申请详解

总的来说,动态权限申请分两步:

  1. 清单文件中声明需要的权限
  2. 在代码中动态申请权限

本来感觉在代码中动态申请权限就行了,清单文件里应该就不用声明权限了吧?其实是需要的,如果清单文件中不声明的话,动态申请权限时是申请不到的,为什么需要在清单文件也声明权限呢?答:假如你的app安装在Android6.0以下的手机上,这些手机是没有动态权限申请的,则还是走清单文件申请权限这一套旧规则。而且就算是在高版本的手机,应用安装好之后,在系统设置中,打开应用管理,也能看到你的应用申请的所有权限,这些都是系统通过读取清单文件来实现的。

这里着重讲解代码中如何动态申请权限,先画一个流程图,通过流程图可以很简地理解申请权限的流程:

在这里插入图片描述

如上流程图写的比较详细,看着好似需要写好多代码,其实并不是,代码特别简单,关键代码如下:

  1. 判断是否拥有某权限:
    ActivityCompat.checkSelfPermission(context, permission)
    
  2. 申请权限:
    ActivityCompat.requestPermissions(activity, permissions, requestCode)
    
  3. 权限申请结果回调函数:
    onRequestPermissionsResult(requestCode, permissions, grantResults)
    
  4. 判断用户是否选择了不再提示:
    ActivityCompat.shouldShowRequestPermissionRationale(activity, permission)
    

完整示例代码如下:

const val PERMISSIONS_CAMERA = 0x1

class MainActivity : AppCompatActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
        requestPermission()
    }

    private fun init() {
        // TODO 得到权限后要做的事情
    }
    
    private fun requestPermission() {
        val hasCameraPermission = ActivityCompat.checkSelfPermission(this, Manifest.permission.CAMERA) == PackageManager.PERMISSION_GRANTED
        if (hasCameraPermission) {
            init()
        } else {
			requestCameraPermission()
		}
    }

    private fun requestCameraPermission() {
        ActivityCompat.requestPermissions(this, arrayOf(Manifest.permission.CAMERA), PERMISSIONS_CAMERA)
    }

    override fun onRequestPermissionsResult(requestCode: Int, permissions: Array<out String>, grantResults: IntArray) {
        super.onRequestPermissionsResult(requestCode, permissions, grantResults)
        if (requestCode == PERMISSIONS_CAMERA) {
            if (grantResults.isNotEmpty() && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
                // 已经授权了,开始使用功能吧
                init()
                return
            }

            // 权限被拒绝
            val shouldShowRequestPermissionRationale = ActivityCompat.shouldShowRequestPermissionRationale(this, Manifest.permission.CAMERA)
            if (shouldShowRequestPermissionRationale) {
                // 用户拒绝了摄像头权限,应该解释一下为什么需要此权限
                AlertDialog.Builder(this)
                        .setTitle("解释需要此权限的理由")
                        .setMessage("录像时需要用到摄像头权限,请允许此权限,否则无法录制摄像头")
                        .setPositiveButton("确定") { dialog, which -> requestCameraPermission() }
                        .setNegativeButton("取消", null)
                        .setCancelable(false)
                        .create()
                        .show()
            } else {
                // 用户拒绝了摄像头权限,并且勾选了不再提示
                AlertDialog.Builder(this)
                        .setTitle("打开权限步骤提示")
                        .setMessage("您好狠啊,竟然选择拒绝了权限,而且选择不再提示。好吧,如果你后悔了,可以在设置中找到应用来开启此权限")
                        .setPositiveButton("确定", null)
                        .setCancelable(false)
                        .create()
                        .show()
            }
        }

    }
}

第一次运行时,效果如下:

系统会弹出申请权限对话框:

在这里插入图片描述

注:第一次申请权限时,没有不再提示的选项哦!

如果用户选择禁止,此时我们就会弹出自定义的对话框,以给用户解释我们为什么需要这个权限,如下:

在这里插入图片描述

此时,如果用户点击取消,则我们不做任何操作,如果用户选择确定,则我们再一次申请权限,此时系统会再次弹出系统的申请权限对话框,如下:

在这里插入图片描述

注意,此时是第二次申请权限,所以多了一个”禁止后不再提示“的选项。如果用户选择“禁止”按钮,则还是走之前的流程,如果用户选择了“禁止后不再提示”,则弹另一个我们自定义的对话框,以告诉用户如果你还想打开此权限的话只能到设置里去打开了,如下:

在这里插入图片描述

此时,你再申请权限时不会再弹出系统的申请权限的对话框了,因为用户选择了不再提示,但是我们自定义的这个对话框还是可以弹出来的,以告诉用户可以在设置中来打开此权限,如果用户到设置中打开了权限,则开开心心,如果用户在设置打开了权限然后想了一下又关掉了,则此时的权限状态恢复到初始化状态,比如你申请权限时,系统会认为你是第一次申请,会弹出系统权限申请对话框,问你是否允许权限,跟开头的流程一模一样的。

3、动态申请权限流程优化

再回顾一下动态申请权限的流程:

在这里插入图片描述

这里最前的一步,先判断是否拥有权限,其实可以不判断的,直接申请权限,这样的结果就是,如果有权限,则不弹任何对话框,在获取结果的回调函数中将会得到权限已授权的结果,如果申请权限时该权限没有被授权,则还是走原来的流程。虽然先判断没权限再申请权限好像正规一点、效率也更高一点,但是为了简单,这点效率的影响应该微乎其微。

总结一下动态申请权限的步骤就更简单了:

  1. 申请权限
  2. 获取申请权限结果,如果权限已授权,则做该做的事,如果权限没被授权,则判断用户是选择了“禁止”还是选择了“禁止后不再提示”,并据此弹出对应的自定义对话框。

优化后的代码如下:

const val PERMISSIONS_CAMERA = 0x1

class MainActivity : AppCompatActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
        requestCameraPermission()
    }

    private fun init() {
        // TODO 得到权限后要做的事情
    }

    private fun requestCameraPermission() {
        ActivityCompat.requestPermissions(this, arrayOf(Manifest.permission.CAMERA), PERMISSIONS_CAMERA)
    }

    override fun onRequestPermissionsResult(requestCode: Int, permissions: Array<out String>, grantResults: IntArray) {
        super.onRequestPermissionsResult(requestCode, permissions, grantResults)
        if (requestCode == PERMISSIONS_CAMERA) {
            if (grantResults.isNotEmpty() && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
                // 已经授权了,开始使用功能吧
                init()
                return
            }

            // 权限被拒绝
            val shouldShowRequestPermissionRationale = ActivityCompat.shouldShowRequestPermissionRationale(this, Manifest.permission.CAMERA)
            if (shouldShowRequestPermissionRationale) { // 应该解释一下为什么需要此权限
                // 用户拒绝了摄像头权限
                AlertDialog.Builder(this)
                        .setTitle("解释需要此权限的理由")
                        .setMessage("录像时需要用到摄像头权限,请允许此权限,否则无法录制摄像头")
                        .setPositiveButton("确定") { dialog, which -> requestCameraPermission() }
                        .setNegativeButton("取消", null)
                        .setCancelable(false)
                        .create()
                        .show()
            } else {
                // 用户拒绝了摄像头权限,并且勾选了不再提示
                AlertDialog.Builder(this)
                        .setTitle("打开权限步骤提示")
                        .setMessage("您好狠啊,竟然选择拒绝了权限,而且选择不再提示。好吧,如果你后悔了,可以在设置中找到应用来开启此权限")
                        .setPositiveButton("确定", null)
                        .setCancelable(false)
                        .create()
                        .show()
            }
        }
    }
}

4、RxPermissions的使用

了解了动态权限的申请之后,我们就会发现,每次申请权限,要写一些模板代码在Activity中,能不能对申请权限的代码进行封装呢?答案是可以的,RxPermissions库就实现了这样的功能,它的原理是给当前Activity添加一个无界面的Fragment,在这个Fragment中申请权限,并在这个Fragment中接收申请权限的结果,所以可以在这个Fragment中封装申请权限的逻辑。并且该库使用RxJava进行封装,使我们更容易的拿到申请权限的结果。

RxPermissions官网:https://github.com/tbruyelle/RxPermissions

添加RxPermissions依赖

在项目的根目录下的build.gradle添加如下配置:

allprojects {
    repositories {
        maven { url 'https://jitpack.io' }
    }
}

在module目录下的build.gradle添加如下配置:

dependencies {
    implementation 'com.github.tbruyelle:rxpermissions:0.12'    
}

我们知道RxPermissions是用到了RxJava的,通过查看依赖传递,我们发现rxpermissions中有依赖RxJava,但是并没有依赖RxAndroid,我们知道在Android中使用RxJava必须要依赖RxAndroid的,所以还需要添加RxAndroid依赖,如下:

dependencies {
    implementation 'com.github.tbruyelle:rxpermissions:0.12'    
    implementation 'io.reactivex.rxjava3:rxandroid:3.0.0'
}

使用RxPermissions完成前面的例子

接下来我们使用RxPermissions来完成前面的例子,看看是不是更简洁呢,代码如下:

class MainActivity : AppCompatActivity() {

    private val mRxPermissions = RxPermissions(this)

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
        requestCameraPermission()
    }

    private fun init() {
        // TODO 得到权限后要做的事情
    }

    private fun requestCameraPermission() {
        mRxPermissions
                .requestEach(Manifest.permission.CAMERA)
                .subscribe { permission ->
                    when {
                        permission.granted -> {
                            // 已经授权了,开始使用功能吧
                            init()
                        }
                        permission.shouldShowRequestPermissionRationale -> {
                            // 用户拒绝了摄像头权限,应该解释一下为什么需要此权限
                            AlertDialog.Builder(this)
                                    .setTitle("解释需要此权限的理由")
                                    .setMessage("录像时需要用到摄像头权限,请允许此权限,否则无法录制摄像头")
                                    .setPositiveButton("确定") { dialog, which -> requestCameraPermission() }
                                    .setNegativeButton("取消", null)
                                    .setCancelable(false)
                                    .create()
                                    .show()
                        }
                        else -> {
                            // 用户拒绝了摄像头权限,并且勾选了不再提示
                            AlertDialog.Builder(this)
                                    .setTitle("打开权限步骤提示")
                                    .setMessage("您好狠啊,竟然选择拒绝了权限,而且选择不再提示。好吧,如果你后悔了,可以在设置中找到应用来开启此权限")
                                    .setPositiveButton("确定", null)
                                    .setCancelable(false)
                                    .create()
                                    .show()
                        }
                    }
                }
    }

}

可以看到,使用RxPermission后代码更紧凑了,当然了,用法上还有更多的好处,可以查看RxPermission官网的说明教程。

封装RxPermissions

这里虽然使用RxPermission简单了许多,但是代码还是入侵了Activity,一个申请权限的代码没什么技术含量,但是代码也不算少,影响阅读Activity的逻辑,所以这个权限申请可以封装到工具类中,或者写到Presenter中都是可以的,例如我们封装到工具类中,示例如下,会看到Activity就清爽多了:

class MainActivity : AppCompatActivity() {

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
        PermissionsHelper.requestCameraPermission(this, ::init)
    }

    private fun init() {
        // TODO 得到权限后要做的事情
    }

}
object PermissionsHelper {

    fun requestCameraPermission(activity: FragmentActivity, callback: () -> Unit) {
        RxPermissions(activity)
            .requestEach(Manifest.permission.CAMERA)
            .subscribe { permission ->
                when {
                    permission.granted -> {
                        // 已经授权了,开始使用功能吧
                        callback()
                    }
                    permission.shouldShowRequestPermissionRationale -> {
                        // 用户拒绝了摄像头权限,应该解释一下为什么需要此权限
                        AlertDialog.Builder(activity)
                            .setTitle("解释需要此权限的理由")
                            .setMessage("录像时需要用到摄像头权限,请允许此权限,否则无法录制摄像头")
                            .setPositiveButton("确定") { _, _ -> requestCameraPermission(activity, callback) }
                            .setNegativeButton("取消", null)
                            .setCancelable(false)
                            .create()
                            .show()
                    }
                    else -> {
                        // 用户拒绝了摄像头权限,并且勾选了不再提示
                        AlertDialog.Builder(activity)
                            .setTitle("打开权限步骤提示")
                            .setMessage("您好狠啊,竟然选择拒绝了权限,而且选择不再提示。好吧,如果你后悔了,可以在设置中找到应用来开启此权限")
                            .setPositiveButton("确定", null)
                            .setCancelable(false)
                            .create()
                            .show()
                    }
                }
            }
    }

}

5、最新版本权限申请方式(2021-08-16)

新方式描述

更多关于权限的文章见另一篇文章:https://www.cdsy.xyz/computer/programme/android/230205/cd40208.html

不知道什么时候有的这种方式,感觉有了这个新的方式,不再需要使用RxPermissions这些第三方的权限申请库了,直接使用Android官方的方式即可,官方使用教程:https://developer.android.google.cn/training/permissions/requesting#request-permission

需要注意的是,这种新的方式需要使用Jetpack组件中的Activity或Fragment,如下:

androidx.activity,1.2.0 或更高版本。

androidx.fragment,1.3.0 或更高版本。

比如:

implementation “androidx.activity:activity-ktx:1.3.0”

implementation “androidx.fragment:fragment-ktx:1.3.6”

注:这两个依赖必须同时声明,即使你只是在Activity中申请权限,但是Activity是用到了FragmentActivity的,此类必须是fragment-ktx里面的,如果这个版本低的话也是不行的,比如我们不添加这个依赖,运行时报如下异常:

IllegalArgumentException: Can only use lower 16 bits for requestCode

对于这个异常,真是让人摸不到头脑,因为我代码中根本就没有使用到请求码(requestCode),为什么会报这个错呢,只要把fragment的依赖设置好问题就没了。

虽然androidx.appcompat组件的依赖传递包含有androidx.activity和androidx.fragment,但是版本都比较低,达不到要求的版本,所以需要单独依赖androidx.activity和androidx.fragment的最新版本。

startActivityForResult的实现(过时,不推荐)

新的权限申请方式使用的是ActivityResultLauncher,使用这种方式就不再需要覆盖Activity的onActivityResult函数,而且它也能代替startActivityForResult的使用,也是为了不再需要使用onActivityResult函数来接收结果。我们先写一个startActivityForResult的原始使用Demo,如下:

有三个Activity:MainActivity,AActivity、BActivity。

MainActivity界面如下:

在这里插入图片描述

AActivity和BActivity界面分别如下:

在这里插入图片描述
在这里插入图片描述

MainActivity代码如下:

class MainActivity : AppCompatActivity() {

    private val A_ACTIVITY_REQUEST_CODE = 0
    private val B_ACTIVITY_REQUEST_CODE = 1

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        findViewById<Button>(R.id.button1).setOnClickListener {
            startActivityForResult(Intent(this, AActivity::class.java), A_ACTIVITY_REQUEST_CODE)
        }

        findViewById<Button>(R.id.button2).setOnClickListener {
            startActivityForResult(Intent(this, BActivity::class.java), B_ACTIVITY_REQUEST_CODE)
        }
    }

    override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
        super.onActivityResult(requestCode, resultCode, data)
        if (resultCode == Activity.RESULT_OK) {
            when (requestCode) {
                A_ACTIVITY_REQUEST_CODE -> Log.i("ABCD","收到A_Activtiy的结果:${data?.getStringExtra("data")}")
                B_ACTIVITY_REQUEST_CODE -> Log.i("ABCD","收到B_Activtiy的结果:${data?.getStringExtra("data")}")
            }
        }
    }

}

AActivity代码中如下:

class AActivity : AppCompatActivity() {

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_a)
    }

    override fun onBackPressed() {
        setResult(Activity.RESULT_OK, Intent().apply { putExtra("data", "AAA") })
        super.onBackPressed()
    }

}

BActivity代码如下:

class BActivity : AppCompatActivity() {

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_b)
    }

    override fun onBackPressed() {
        setResult(Activity.RESULT_OK, Intent().apply { putExtra("data", "BBB") })
        super.onBackPressed()
    }

}

代码很简单,运行后,当打开AActivity并返回到MainActivity时就能收到“AAA”,当打开BActivity后再返回时就能收到“BBB”。

startActivityForResult的替代方式(推荐)

直接在Activity中实现:

在AndroidStudio开发工具中,可以看到startActivityForResult方法和onActivityResult方法都是过时的,也就是说不推荐使用了,那我们就使用它推荐的方式,示例如下:

class MainActivity : AppCompatActivity() {

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
        
        val aActivityResultLauncher = registerForActivityResult(ActivityResultContracts.StartActivityForResult()) { activityResult: ActivityResult ->
            Log.i("ABCD","收到A_Activtiy的结果:${activityResult.data?.getStringExtra("data")}")
        }
        
        val bActivityResultLauncher = registerForActivityResult(ActivityResultContracts.StartActivityForResult()) { activityResult: ActivityResult ->
            Log.i("ABCD","收到B_Activtiy的结果:${activityResult.data?.getStringExtra("data")}")
        }

        findViewById<Button>(R.id.button1).setOnClickListener {
            aActivityResultLauncher.launch(Intent(this, AActivity::class.java))
        }

        findViewById<Button>(R.id.button2).setOnClickListener {
            bActivityResultLauncher.launch(Intent(this, BActivity::class.java))
        }
    }

}

可以看到,这种方式更紧凑了,不需要覆盖onActivityResult方法来接收结果了,这样的好处是代码可以更加解耦,因为registerForActivityResult这种代码我们是可以封装到工具类中去的,方便封装。

需要注意的是,使用新方式后,发现没有地方可设置requestCode参数了,所以,对于每一个开启Activity的请求,都需要分别注册一个监听器(调用registerForActivityResult来注册),以获取对应的回调结果。

另外还有一个注意事项,registerForActivityResult的调用必须在STARTED状态之前调用,否则会抛出如下异常:

IllegalStateException: LifecycleOwner cn.dazhou.permissionrequestdemo.MainActivity@f263cb9 is attempting to register while current state is RESUMED. LifecycleOwners must call register before they are STARTED.

所以,必须提前注册好,不能等到你要开启Activity了才去注册,那时就晚了!

封装到工具类中

注册的语句一般注册一次就够了,所以一般放在onCreate方法中进行注册,所以,如果我们要把注册封装到工具类中的话,需要在onCreate中调用注册的代码,示例如下:

object Utils {

    private lateinit var aActivityResultLauncher: ActivityResultLauncher<Intent?>
    private lateinit var bActivityResultLauncher: ActivityResultLauncher<Intent?>

    fun registerForAActivityResult(activity: FragmentActivity) {
        aActivityResultLauncher = activity.registerForActivityResult(ActivityResultContracts.StartActivityForResult()) { activityResult: ActivityResult ->
            Log.i("ABCD", "收到A_Activtiy的结果:${activityResult.data?.getStringExtra("data")}")
        }
    }

    fun registerForBActivityResult(activity: FragmentActivity) {
        bActivityResultLauncher = activity.registerForActivityResult(ActivityResultContracts.StartActivityForResult()) { activityResult: ActivityResult ->
            Log.i("ABCD", "收到B_Activtiy的结果:${activityResult.data?.getStringExtra("data")}")
        }
    }

    fun startAActivity(activity: FragmentActivity) {
        aActivityResultLauncher.launch(Intent(activity, AActivity::class.java))
    }

    fun startBActivity(activity: FragmentActivity) {
        bActivityResultLauncher.launch(Intent(activity, BActivity::class.java))
    }
}
class MainActivity : AppCompatActivity() {

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
        Utils.registerForAActivityResult(this)
        Utils.registerForBActivityResult(this)

        findViewById<Button>(R.id.button1).setOnClickListener {
            Utils.startAActivity(this)
        }

        findViewById<Button>(R.id.button2).setOnClickListener {
            Utils.startBActivity(this)
        }
    }

}

可以看到,此时MainActivity中的代码就非常的简洁了,对于开启新Activity和接收Activity结果的代码我们就写到了工具类中去了,这些代码没什么技术含量,封装后的好处就是可以使Activity中的代码更简单,方便我们理解Activity的功能。

一般我们接收到新Activity返回的结果是要在Activity中使用的,这个解决起来就很简单了,给工具类方法添加一个回调就可以了,示例如下:

object Utils {

    private lateinit var aActivityResultLauncher: ActivityResultLauncher<Intent?>
    private lateinit var bActivityResultLauncher: ActivityResultLauncher<Intent?>
    private lateinit var aActivityResultCallback: ((String) -> Unit)
    private lateinit var bActivityResultCallback: ((String) -> Unit)

    fun registerForAActivityResult(activity: FragmentActivity) {
        aActivityResultLauncher = activity.registerForActivityResult(ActivityResultContracts.StartActivityForResult()) { activityResult: ActivityResult ->
            val data = activityResult.data?.getStringExtra("data") ?: ""
            aActivityResultCallback(data)
        }
    }

    fun registerForBActivityResult(activity: FragmentActivity) {
        bActivityResultLauncher = activity.registerForActivityResult(ActivityResultContracts.StartActivityForResult()) { activityResult: ActivityResult ->
            val data = activityResult.data?.getStringExtra("data") ?: ""
            bActivityResultCallback(data)
        }
    }

    fun startAActivity(activity: FragmentActivity, resultCallback: (String) -> Unit) {
        aActivityResultCallback = resultCallback
        aActivityResultLauncher.launch(Intent(activity, AActivity::class.java))
    }

    fun startBActivity(activity: FragmentActivity, resultCallback: (String) -> Unit) {
        bActivityResultCallback = resultCallback
        bActivityResultLauncher.launch(Intent(activity, BActivity::class.java))
    }
}
class MainActivity : AppCompatActivity() {

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
        Utils.registerForAActivityResult(this)
        Utils.registerForBActivityResult(this)

        findViewById<Button>(R.id.button1).setOnClickListener {
            Utils.startAActivity(this) { result ->
                Log.i("ABCD", "收到A_Activtiy的结果:$result")
            }
        }

        findViewById<Button>(R.id.button2).setOnClickListener {
            Utils.startBActivity(this) { result ->
                Log.i("ABCD", "收到B_Activtiy的结果:$result")
            }
        }
    }

}

OK,大功告成!此时的MainActivity的代码还是非常简洁的!

请求权限的新方式

一次请求一个权限

前面学了使用registerForActivityResult的方式来开启新Activity并接收返回结果,而请求权限也是使用这种方式,这样使用方式就很统一了,示例如下:

在清单文件中声明如下权限:

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

MainActivity界面如下:

在这里插入图片描述

MainActivity代码如下:

class MainActivity : AppCompatActivity() {

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        val requestCameraResultLauncher = registerForActivityResult(ActivityResultContracts.RequestPermission()) { isGranted ->
            Log.i("ABCD", "是否获得了摄像头权限:${if (isGranted) '是' else '否'}")
        }

        val requestReadPhoneStateResultLauncher = registerForActivityResult(ActivityResultContracts.RequestPermission()) { isGranted ->
            Log.i("ABCD", "是否获得了读取电话状态权限:${if (isGranted) '是' else '否'}")
        }

        findViewById<Button>(R.id.button1).setOnClickListener {
            requestCameraResultLauncher.launch(Manifest.permission.CAMERA)
        }

        findViewById<Button>(R.id.button2).setOnClickListener {
            requestReadPhoneStateResultLauncher.launch(Manifest.permission.READ_PHONE_STATE)
        }
    }

}

如果返回的granted为false,则为权限被拒绝了,如果想要知道用户是普通的拒绝,还是拒绝后不再提示,还是老办法,调用ActivityCompat.shouldShowRequestPermissionRationale(activity, permission)来判断就行了。

一次请求多个权限

前面的例子是一次请求一个权限,总共需要两个权限,其实也可以一次就请求多个权限。把ActivityResultContracts.RequestPermission()替换为ActivityResultContracts.RequestMultiplePermissions(),示例如下:

MainActivity界面如下:

在这里插入图片描述

MainActivity代码如下:

class MainActivity : AppCompatActivity() {

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        val permissionsResultLauncher = registerForActivityResult(ActivityResultContracts.RequestMultiplePermissions()) { grantResults ->
            grantResults.forEach { (permission, isGranted) ->
                Log.i("ABCD", "是否获得了${permission}权限:${if (isGranted) '是' else '否'}")
            }
        }

        findViewById<Button>(R.id.button).setOnClickListener {
            val permissions = arrayOf(Manifest.permission.CAMERA, Manifest.permission.READ_PHONE_STATE)
            permissionsResultLauncher.launch(permissions)
        }

    }

}

请求权限的工具类封装

class PermissionUtil {

    companion object {

        private lateinit var launcher: ActivityResultLauncher<Array<String>>
        private lateinit var hasAllPermissionCallback: () -> Unit

        // 声明需要申请的权限和对应的权限名称
        private val map = mapOf(
            Manifest.permission.CAMERA to "摄像头",
            Manifest.permission.READ_PHONE_STATE to "读取电话状态",
        )

        /**
         * 需求:
         * androidx.activity,1.2.0 或更高版本。
         * androidx.fragment,1.3.0 或更高版本。
         * 示例如下:
         * implementation "androidx.activity:activity-ktx:1.3.0"
         * implementation "androidx.fragment:fragment-ktx:1.3.6"
         */
        fun registerForActivityResult(activity: FragmentActivity) {
            launcher = activity.registerForActivityResult(ActivityResultContracts.RequestMultiplePermissions()) { grantResults ->
                Log.i("ABCD", "权限申请结果:$grantResults")
                val deniedPermissions = mutableListOf<String>()     // 被拒绝的权限
                val neverTipPermissions = mutableListOf<String>()   // 被设置不再提示的权限
                grantResults.forEach { (permission, isGranted) ->
                    if (!isGranted) {
                        if (ActivityCompat.shouldShowRequestPermissionRationale(activity, permission)) {
                            // 权限被拒绝
                            deniedPermissions.add(permission)
                        } else {
                            // 权限被设置为不再提示
                            neverTipPermissions.add(permission)
                        }
                    }
                }

                if (deniedPermissions.isEmpty() && neverTipPermissions.isEmpty()) {
                    Log.i("ABCD", "拥有所有权限了!")
                    hasAllPermissionCallback()
                } else {
                    Log.i("ABCD", "有些权限被拒绝了,再次弹框提示用户授权")
                    showTipDialog(activity, deniedPermissions, neverTipPermissions)
                }
            }
        }

        fun requestPermissions(hasAllPermissionCallback: () -> Unit) {
            this.hasAllPermissionCallback = hasAllPermissionCallback
            val permissions = map.keys.toTypedArray()
            launcher.launch(permissions)
        }

        private fun showTipDialog(activity: FragmentActivity, deniedPermissions: MutableList<String>, neverTipPermissions: MutableList<String>) {
            val desc: String
            val permissions: MutableList<String>

            if (deniedPermissions.isNotEmpty()) {
                // 权限被拒绝
                permissions = deniedPermissions
                desc = "本App需要使用%s权限,请允许此权限,否则无法使用本app"
            } else {
                // 权限被设置为不再提示
                permissions = neverTipPermissions
                desc = "本App需要使用%s权限,您需要到设置中打开此权限,否则无法使用本app"
            }

            val allPermissionDesc = StringBuilder()
            permissions.forEachIndexed { index, permission ->
                if (index > 0) allPermissionDesc.append("、")
                allPermissionDesc.append(map[permission])
            }

            val message = String.format(desc, allPermissionDesc)
            Log.i("ABCD", message)

            showDialog(activity, message) {
                requestPermissions(hasAllPermissionCallback)
            }
        }

        private fun showDialog(activity: FragmentActivity, message: String, okClick: () -> Unit) {
            AlertDialog.Builder(activity)
                .setTitle("提示")
                .setMessage(message)
                .setCancelable(false)
                .setPositiveButton("确定") { _, _ -> okClick() }
                .show()
        }

    }
}
class MainActivity : AppCompatActivity() {

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        PermissionUtil.registerForActivityResult(this)

        findViewById<Button>(R.id.button).setOnClickListener {
            PermissionUtil.requestPermissions {
                Log.i("ABCD", "获得了所有的权限了^_^")
            }
        }

    }

}

可以看到,封装后,MainActivity中的代码非常的简洁,而且使用起来也很简单。这里我们使用了一个暴力设计,即必须让用户给予所有的权限,任意权限没获取到,都会一直弹窗让用户授权,即使用户选择了“禁止后不再提示”也依然弹窗提示用户必须给予权限,否则不能使用App,这种需求在客户目标确定的情景很实用,比如我们公司开发的应用是专门给另一个公司的员工使用的,这些员工是一定要安装这个App来使用的,那么这些员工只能选择给予所有的权限,否则就不能使用App,你不使用App你就违反公司规定,所以我们就可以这么设计,当然,在弹框提示的时候可以说清楚每个权限是干什么用的,让用户清楚的知道你不是乱申请的权限,而是真实的用到App的某项功能中的,这样用户比较容易接受!

方便获取更多学习、工作、生活信息请关注本站微信公众号城东书院 微信服务号城东书院 微信订阅号
推荐内容
相关内容
栏目更新
栏目热门