Jetpack Compose 权限申请

前言

在 Jetpack Compose 中,可以通过 Activity Result API 来同时同时申请多个权限。

我们可以利用 rememberLauncherForActivityResult 配合 ActivityResultContracts.RequestMultiplePermissions 来处理多个权限的请求,这是 Android 官方推荐的方式。

添加配置

AndroidManifest.xml

根节点下添加需要的权限

1
2
3
4
5
6
7
8
9
10
11
<uses-feature
android:name="android.hardware.camera"
android:required="false" />
<uses-permission android:name="android.permission.CAMERA" />
<!-- 网络访问权限 -->
<uses-permission android:name="android.permission.INTERNET" /> <!-- 访问网络状态权限 -->
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" /> <!-- 读写外部存储权限(根据需求添加) -->
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />

权限申请组件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
import android.content.Context
import android.content.Intent
import android.content.pm.PackageManager
import android.net.Uri
import android.provider.Settings
import android.util.Log
import android.widget.Toast
import androidx.activity.ComponentActivity
import androidx.activity.compose.rememberLauncherForActivityResult
import androidx.activity.result.contract.ActivityResultContracts
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.width
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material3.Button
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import androidx.core.content.ContextCompat

// 检查是否有被永久拒绝的权限
fun hasPermanentlyDeniedPermissions(
context: Context,
permissions: Array<String>
): Boolean {
return if (context is ComponentActivity) {
permissions.any { permission ->
!context.shouldShowRequestPermissionRationale(permission)
}
} else {
false
}
}

// 打开应用设置页面
fun openAppSettings(context: Context) {
val intent = Intent(
Settings.ACTION_APPLICATION_DETAILS_SETTINGS,
Uri.fromParts("package", context.packageName, null)
)
context.startActivity(intent)
}

@Composable
fun NativePermissionComp() {
val context = LocalContext.current
// 定义需要请求的多个权限
val permissions = arrayOf(
android.Manifest.permission.CAMERA,
android.Manifest.permission.READ_EXTERNAL_STORAGE,
android.Manifest.permission.ACCESS_FINE_LOCATION,
android.Manifest.permission.ACCESS_COARSE_LOCATION
)

var allPermissionsGranted by remember { mutableStateOf(false) }


// 检查所有权限是否已授予
allPermissionsGranted = permissions.all { permission ->
ContextCompat.checkSelfPermission(context, permission) == PackageManager.PERMISSION_GRANTED
}

// 注册多权限请求 launcher
val multiplePermissionsLauncher = rememberLauncherForActivityResult(
contract = ActivityResultContracts.RequestMultiplePermissions()
) { permissionsMap ->
// 检查所有权限的授予结果
allPermissionsGranted = permissionsMap.all { it.value }

if (!allPermissionsGranted) {
// 是否拒绝了权限
val result = hasPermanentlyDeniedPermissions(context, permissions)
if (result) {
openAppSettings(context)
}
}
}

when {
allPermissionsGranted -> {
Toast.makeText(context, "所有权限已授予,可以使用相关功能", Toast.LENGTH_SHORT).show()
}

else -> {
Box(
modifier = Modifier
.fillMaxSize()
.background(Color(0x33000000))
) {
Column(
modifier = Modifier
.width(300.dp)
.background(Color.White, RoundedCornerShape(8.dp))
.padding(10.dp, 20.dp)
.align(Alignment.Center),
horizontalAlignment = Alignment.CenterHorizontally,
verticalArrangement = Arrangement.Center
) {
Text("需要以下权限才能继续:\n相机、存储、位置")
Button(
modifier = Modifier
.padding(top = 10.dp),
onClick = { multiplePermissionsLauncher.launch(permissions) }) {
Text("申请权限", fontSize = 12.sp)
}
}
}

}
}
}

方法说明

方法1

shouldShowRequestPermissionRationale() 是 Android 权限系统中的一个关键方法,用于判断是否需要向用户解释为什么需要某个权限。

其返回值的含义如下:

  • true
    • 表示系统认为应该向用户解释申请该权限的原因
    • 通常发生在:用户曾经一次拒绝了该权限请求,但但未勾选 “不再询问” 选项
    • 这是一个提示,说明你应该向用户解释为什么需要这个权限,以提高用户授权的可能性
  • false
    • 表示不需要解释权限申请原因,或无法再通过代码请求该权限
    • 可能的情况包括:
      1. 用户从未拒绝过该权限(首次请求)
      2. 用户拒绝时勾选了 “不再询问”(永久拒绝)
      3. 设备策略禁止该权限(如企业设备限制)
      4. 权限属于 “正常权限”(无需运行时申请的权限)

方法2

rememberLauncherForActivityResult 是 Jetpack Compose 中用于处理 Activity 结果回调的关键 API,它是对 Android 传统 startActivityForResult 机制的现代替代方案,专门为 Compose 声明式编程模型设计。

关键参数解析

  1. contract(契约)
    • 定义了启动的目标和数据交换格式的协议
    • Android 提供了多种内置契约,例如:
      • ActivityResultContracts.RequestPermission():请求单个权限
      • ActivityResultContracts.RequestMultiplePermissions():请求多个权限
      • ActivityResultContracts.TakePicture():拍照
      • ActivityResultContracts.GetContent():选择文件
      • ActivityResultContracts.StartActivityForResult():通用的 Activity 启动
  2. result callback(结果回调)
    • 当目标 Activity 完成并返回结果时触发
    • 回调参数的类型由契约决定(例如权限请求返回 Boolean,文件选择返回 Uri?
  3. launch(input)
    • 触发启动器,参数类型由契约决定
    • 例如权限请求需要传入权限字符串,文件选择不需要参数