Android 新权限申请模型(Activity Result API)

一、核心变化(先抓主线)

旧方式(已废弃):

java 复制代码
startActivityForResult(...)
onActivityResult(...)

新方式(推荐):

java 复制代码
registerForActivityResult(...) → 得到 launcher
launcher.launch(...) → 发起请求
回调函数 → 收结果

👉 本质变化:
"回调注册前置 + 生命周期感知"


二、permissionLauncher 到底是什么?

你问的关键点👇

这个 Launcher 从何而来?

它其实是:

👉 一个"已经绑定好回调逻辑的请求发射器"

标准写法(单权限):

kotlin 复制代码
val permissionLauncher = registerForActivityResult(
    ActivityResultContracts.RequestPermission()
) { isGranted: Boolean ->

    if (isGranted) {
        // 权限允许
    } else {
        // 权限拒绝
    }
}

拆解一下:

1️⃣ registerForActivityResult 做了什么?

干了两件事:

  1. 注册"请求类型"

    kotlin 复制代码
    RequestPermission()
  2. 注册"回调函数"

    kotlin 复制代码
    { isGranted -> ... }
  3. 返回一个对象:

    kotlin 复制代码
    ActivityResultLauncher<String>

👉 这个对象就是:

permissionLauncher


2️⃣ launcher 的本质

可以把它理解成:

复制代码
launcher = 已经配置好规则的"发射器"

你之后只需要:

kotlin 复制代码
permissionLauncher.launch(Manifest.permission.CALL_PHONE)

系统就会:

  1. 弹权限框
  2. 用户点"允许/拒绝"
  3. 自动调用你写好的回调

三、完整流程(非常重要)

把整个流程串起来👇


Step 1:先注册(必须在 onCreate 里)

kotlin 复制代码
val permissionLauncher = registerForActivityResult(
    ActivityResultContracts.RequestPermission()
) { isGranted ->

    if (isGranted) {
        callPhone()
    } else {
        showToast("权限被拒绝")
    }
}

Step 2:检查权限(和旧的一样)

kotlin 复制代码
if (ContextCompat.checkSelfPermission(
        this,
        Manifest.permission.CALL_PHONE
    ) == PackageManager.PERMISSION_GRANTED
) {
    callPhone()
} else {
    permissionLauncher.launch(Manifest.permission.CALL_PHONE)
}

Step 3:系统处理 + 回调

用户点击:

  • ✅ 允许 → isGranted = true
  • ❌ 拒绝 → isGranted = false

四、多权限版本(重点)

如果是多个权限:

kotlin 复制代码
val multiLauncher = registerForActivityResult(
    ActivityResultContracts.RequestMultiplePermissions()
) { result: Map<String, Boolean> ->

    result.forEach { (permission, granted) ->
        if (granted) {
            println("$permission 被允许")
        } else {
            println("$permission 被拒绝")
        }
    }
}

调用:

kotlin 复制代码
multiLauncher.launch(
    arrayOf(
        Manifest.permission.CAMERA,
        Manifest.permission.ACCESS_FINE_LOCATION
    )
)

五、为什么要换新模式?

旧方式问题:

问题 解释
生命周期不安全 Activity 重建会丢回调
requestCode混乱 手动区分
回调集中 一个函数处理所有逻辑

新方式优势:

优势 解释
生命周期感知 自动绑定 Activity
类型安全 不用 requestCode
解耦 每个请求独立回调
更清晰 逻辑分散更合理

六、"串起一切"是什么意思?

其实就是:

👉 按钮点击 → 检查权限 → launch → 回调 → 执行业务

比如:

kotlin 复制代码
button.setOnClickListener {
    if (hasPermission()) {
        doSomething()
    } else {
        launcher.launch(permission)
    }
}

👉 这就是课件说的:

"在两个按钮单击事件中串起一切"


七、权限被拒绝要做什么

课件也提到一个关键点:

👉 不只是成功逻辑,还要处理失败

比如:

kotlin 复制代码
if (!isGranted) {
    // 1. 禁用功能
    button.isEnabled = false

    // 2. 提示用户
    Toast.makeText(this, "没有权限,功能不可用", Toast.LENGTH_SHORT).show()
}

八、一句话总结

👉 新权限模型本质:

先注册回调 → 再用 launcher 发请求 → 系统自动回调结果


九、代码详细分析

源码

kotlin 复制代码
package com.jinxuliang.mutlipermissiomdemo

import android.Manifest
import android.content.pm.PackageManager
import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import android.util.Log
import android.widget.Button
import android.widget.TextView
import android.widget.Toast
import androidx.activity.result.ActivityResultLauncher
import androidx.activity.result.contract.ActivityResultContracts
import androidx.core.app.ActivityCompat

class MainActivity : AppCompatActivity() {

    //用于启动一个新Activity的启动器
    //泛型参数表示要向新Activity传入的数据类型
    private lateinit var permissionLancher: ActivityResultLauncher<Array<String>>

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
        val tvInfo: TextView = findViewById(R.id.tvInfo)
        //注册回调函数
        permissionLancher =
            registerForActivityResult(ActivityResultContracts.RequestMultiplePermissions()) {
                //处理用户权限授予结果
                permissionCallback(it, tvInfo)
            }

        val btnShowPermissionInfo: Button = findViewById(R.id.btnShowPermissionInfo)
        btnShowPermissionInfo.setOnClickListener {
            //显示权限状态
            showPermissionInfo(tvInfo)
        }

        val btnRequestPermission: Button = findViewById(R.id.btnRequestPermission)
        btnRequestPermission.setOnClickListener {
            //申请权限
            requestPermissions()
        }

    }

    //显示当前App的权限信息
    private fun showPermissionInfo(tvInfo: TextView) {
        val stringBuffer = StringBuffer()
        if (hasBackgroundLocationPermission()) {
            stringBuffer.append("已拥有存取后台位置信息的权限\n")
        } else {
            stringBuffer.append("未拥有存取后台位置信息的权限\n")
        }
        if (hasCameraPermission()) {
            stringBuffer.append("已拥有使用摄像头的权限\n")
        } else {
            stringBuffer.append("未拥有使用摄像头的权限\n")
        }
        if (hasCallPhonePermission()) {
            stringBuffer.append("已拥有打电话的权限\n")
        } else {
            stringBuffer.append("未拥有打电话的权限\n")
        }
        tvInfo.text = stringBuffer.toString()
    }

    //处理权限授予结果
    private fun permissionCallback(
        permisssions: Map<String, Boolean>,
        tvInfo: TextView
    ) {
        val stringBuffer = StringBuffer()
        stringBuffer.append("权限回调函数被调用。\n")
        permisssions.forEach { permission, enabled ->
            stringBuffer.append("$permission 是否被授予? $enabled \n")
        }
        tvInfo.text = stringBuffer.toString()
    }

    //检测打电话的权限是否己经授予
    private fun hasCallPhonePermission() =
        ActivityCompat.checkSelfPermission(
            this,
            Manifest.permission.CALL_PHONE
        ) == PackageManager.PERMISSION_GRANTED

    //检测使用摄像头的权限是否己经授予
    private fun hasCameraPermission() =
        ActivityCompat.checkSelfPermission(
            this,
            Manifest.permission.CAMERA
        ) == PackageManager.PERMISSION_GRANTED

    //检测获取位置信息的权限是否己经授予
    private fun hasBackgroundLocationPermission() =
        ActivityCompat.checkSelfPermission(
            this,
            Manifest.permission.ACCESS_BACKGROUND_LOCATION
        ) == PackageManager.PERMISSION_GRANTED

    //请求用户授予特定的权限
    private fun requestPermissions() {
        //获取App当前还没有权限清单
        val permissionsToRequest = mutableListOf<String>()
        if (!hasCallPhonePermission()) {
            permissionsToRequest.add(Manifest.permission.CALL_PHONE)
        }
        if (!hasCameraPermission()) {
            permissionsToRequest.add(Manifest.permission.CAMERA)
        }
        if (!hasBackgroundLocationPermission()) {
            permissionsToRequest.add(Manifest.permission.ACCESS_BACKGROUND_LOCATION)
        }
        //如果还有权限没有被授予......
        if (permissionsToRequest.isNotEmpty()) {
            //打开系统权限对话框,要求用户给与授权
            permissionLancher.launch(permissionsToRequest.toTypedArray())
        }
    }
}

这段代码其实已经是 Android 新权限申请模式(Activity Result API) 的一个标准实现了


一、核心机制:permissionLauncher 是干嘛的?

kotlin 复制代码
private lateinit var permissionLancher: ActivityResultLauncher<Array<String>>

👉 本质:

这是一个"异步请求权限的发射器(launcher)"

你可以理解为:

复制代码
launch() 负责发起请求
回调函数 负责接收结果

二、注册回调

kotlin 复制代码
permissionLancher =
    registerForActivityResult(
        ActivityResultContracts.RequestMultiplePermissions()
    ) {
        permissionCallback(it, tvInfo)
    }

这里干了三件事:


1️⃣ 指定"要干什么事"

kotlin 复制代码
ActivityResultContracts.RequestMultiplePermissions()

👉 表示:

我要请求 多个权限


2️⃣ 注册"结果回来后怎么办"

kotlin 复制代码
{ permissionCallback(it, tvInfo) }

👉 这里的 it

kotlin 复制代码
Map<String, Boolean>

例如:

复制代码
{
  "android.permission.CAMERA" -> true
  "android.permission.CALL_PHONE" -> false
}

3️⃣ 返回 launcher

之后你就可以用:

kotlin 复制代码
permissionLancher.launch(...)

发起请求


三、真正发起权限请求

kotlin 复制代码
permissionLancher.launch(permissionsToRequest.toTypedArray())

👉 等价于以前的:

java 复制代码
requestPermissions(...)

但区别是:

旧方式 新方式
requestPermissions launch
onRequestPermissionsResult 回调 lambda

四、完整执行流程

我帮你按时间顺序梳理👇


🔹 Step 1:点击按钮

kotlin 复制代码
btnRequestPermission.setOnClickListener {
    requestPermissions()
}

🔹 Step 2:收集没授权的权限

kotlin 复制代码
if (!hasCameraPermission()) {
    permissionsToRequest.add(Manifest.permission.CAMERA)
}

👉 动态判断哪些权限还没给


🔹 Step 3:弹系统对话框

kotlin 复制代码
permissionLancher.launch(...)

👉 系统弹窗:

复制代码
是否允许使用摄像头?
是否允许打电话?
是否允许后台定位?

🔹 Step 4:用户操作

用户点:

  • 允许 / 拒绝

🔹 Step 5:回调触发

kotlin 复制代码
permissionCallback(it, tvInfo)

五、回调函数在干嘛?

kotlin 复制代码
private fun permissionCallback(
    permisssions: Map<String, Boolean>,
    tvInfo: TextView
)

👉 参数含义:

key value
权限名 是否授权

示例输出:

复制代码
权限回调函数被调用。
android.permission.CAMERA 是否被授予? true
android.permission.CALL_PHONE 是否被授予? false

六、三个权限检测函数

例如:

kotlin 复制代码
private fun hasCameraPermission() =
    ActivityCompat.checkSelfPermission(
        this,
        Manifest.permission.CAMERA
    ) == PackageManager.PERMISSION_GRANTED

👉 本质就是:

复制代码
系统有没有给你这个权限?

返回:

  • true → 有权限
  • false → 没权限

七、这段代码的设计规范

它体现了三个重要思想:


✅ 1. "先检查,再申请"

kotlin 复制代码
if (!hasCameraPermission())

👉 避免重复弹窗


✅ 2. "按需申请"

只申请没给的权限


✅ 3. "结果集中处理"

kotlin 复制代码
permissionCallback()

👉 所有权限结果统一处理


八、一个容易踩坑的点

❗ Android 10+ 限制:

kotlin 复制代码
ACCESS_BACKGROUND_LOCATION

👉 不能和前台定位一起申请!

必须:

复制代码
先申请前台定位
再单独申请后台定位

否则:

👉 很可能直接被系统拒绝


九、你可以这样理解整套机制

一句话总结:

复制代码
register → 定义回调
launch → 发起请求
callback → 接收结果

相关推荐
阿拉斯攀登1 小时前
【RK3576 安卓 JNI/NDK 系列 04】JNI 核心语法(下):字符串、数组与对象操作
android·驱动开发·rk3568·瑞芯微·rk安卓驱动·jni字符串操作
2501_915909061 小时前
不用越狱就看不到 iOS App 内部文件?使用 Keymob 查看和导出应用数据目录
android·ios·小程序·https·uni-app·iphone·webview
llxxyy卢2 小时前
web部分中等题目
android·前端
轩情吖2 小时前
MySQL之事务管理
android·后端·mysql·adb·事务·隔离性·原子性
万物得其道者成2 小时前
uni-app Android 离线打包:多环境(prod/dev)配置
android·opencv·uni-app
符哥20082 小时前
Firebase 官方提供的Quick Start-Android 库的功能集讲解
android
koeda3 小时前
android17系统兼容
android·安卓
进击的cc3 小时前
面试官:Handler 没消息时为啥不卡死?带你从源码到底层内核彻底整明白!
android·面试
Yang-Never4 小时前
OpenGL ES ->YUV图像基础知识
android·java·开发语言·kotlin·android studio