观心静

  博客园  :: 首页  :: 新随笔  :: 联系 :: 订阅 订阅  :: 管理

前言

 在使用Jetpack Compose开发UI的时候,已经不能用以前在activity的方式申请动态权限了。 甚至连申请权限的思维也不太一样。虽然现在也可以直接申请动态权限。但是,现在的申请动态权限的规范是先要弹出一个弹窗告知用户权限的使用目的,然后再去申请这个权限。所以在Jetpack Compose申请权限最好是跟申请目的对话框捆绑在一起。 这个博客提供申请蓝牙Ble相关权限与相机权限作为参考。 

 

申请蓝牙权限


import android.Manifest
import android.app.Activity
import android.content.Context
import android.content.pm.PackageManager
import android.os.Build
import androidx.activity.compose.rememberLauncherForActivityResult
import androidx.activity.result.contract.ActivityResultContracts
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.width
import androidx.compose.material3.Button
import androidx.compose.material3.ButtonDefaults
import androidx.compose.material3.Card
import androidx.compose.material3.CardDefaults
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.mutableStateListOf
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
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.compose.ui.window.Dialog
import androidx.core.app.ActivityCompat
import androidx.core.content.ContextCompat
import com.yak.bleSdk.log.logE

private val bluetoothPermissions by lazy {
    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
        arrayOf(
            Manifest.permission.BLUETOOTH_SCAN,
            Manifest.permission.BLUETOOTH_CONNECT,
            Manifest.permission.ACCESS_FINE_LOCATION // 某些情况仍需要
        )
    } else {
        arrayOf(
            Manifest.permission.BLUETOOTH,
            Manifest.permission.BLUETOOTH_ADMIN,
            Manifest.permission.ACCESS_FINE_LOCATION
        )
    }
}


/**
 * 检查蓝牙权限
 */
fun checkBluetoothPermissions(context: Context): Boolean {
    // 检查是否已拥有所有权限
    val hasAllPermissions = bluetoothPermissions.all { permission ->
        ContextCompat.checkSelfPermission(context, permission) == PackageManager.PERMISSION_GRANTED
    }
    return hasAllPermissions
}

/**
 * 请求蓝牙权限
 * @param isRequest 是否请求权限, 这个布尔值可以用来控制申请权限的时机比如按下某个按钮后申请权限,如果不需要选择时机,可以默认为ture
 *  如果需要控制申请时机,请参考:
 *  ```kotlin
 *     val isRequestBluetoothPermissions = remember { mutableStateOf(false) }
 *     RequestBluetoothPermissions(
 *         isRequest = isRequestBluetoothPermissions.value,
 *         agree = {
 *             // 用户同意权限后的操作
 *         },
 *         refuse = {
 *             // 用户拒绝权限后的操作
 *         }
 *     )
 *     Button(
 *             onClick = {
 *                 if(!checkBluetoothPermissions(context)){
 *                     isRequestBluetoothPermissions.value = true
 *                 }
 *             },
 *         ) {
 *             Text("点击申请权限", modifier = Modifier.padding(8.dp))
 *     }
 * ```
 * @param agree 同意
 * @param refuse 拒绝
 */
@Composable
fun RequestBluetoothPermissions(isRequest: Boolean = true, agree: () -> Unit, refuse: () -> Unit) {
    if (!isRequest) {
        return
    }
    val context = LocalContext.current
    val activity = LocalContext.current as Activity
    val allPermissionsGranted = remember { mutableStateOf(false) }
    // 记录被永久拒绝的权限列表
    val permanentlyDeniedPermissions = remember { mutableStateListOf<String>() }

    val permissionLauncher = rememberLauncherForActivityResult(
        ActivityResultContracts.RequestMultiplePermissions()
    ) { permissionsMap ->
        allPermissionsGranted.value = permissionsMap.values.all { it }

        if (allPermissionsGranted.value) {
            // 所有权限都已授予
            "所有权限都已授予".logE()
            agree.invoke()
        } else {
            // 处理权限被拒绝的情况
            "处理权限被拒绝的情况".logE()
            refuse.invoke()
            // 遍历结果,找出被拒绝的权限,并判断是否为永久拒绝
            permissionsMap.entries.forEach { entry ->
                val permission = entry.key
                val isGranted = entry.value
                if (!isGranted) {
                    // 关键判断:检查该权限是否被永久拒绝
                    if (!ActivityCompat.shouldShowRequestPermissionRationale(activity, permission)) {
                        "有权限被永久拒绝".logE()
                        permanentlyDeniedPermissions.add(permission)
                    }
                }
            }
        }
    }

    val isShowRefusePermissionDialog = remember { mutableStateOf(true) }

    // 如果有权限被永久拒绝,可以在这里提示用户前往应用设置页手动开启
    if (permanentlyDeniedPermissions.isNotEmpty() && isShowRefusePermissionDialog.value) {
        // 例如,显示一个对话框引导用户去设置
        Dialog(onDismissRequest = {
            isShowRefusePermissionDialog.value = false
            refuse.invoke()
        }) {
            // 自定义对话框内容
            Card(
                colors = CardDefaults.cardColors(
                    containerColor = Color.White,           // 默认状态
                    disabledContainerColor = Color.LightGray // 禁用状态
                )
            ) {
                Column(
                    modifier = Modifier.padding(20.dp),
                    horizontalAlignment = Alignment.CenterHorizontally
                ) {
                    Text(
                        text = "温馨提示",
                        fontSize = 18.sp,
                        modifier = Modifier.padding(bottom = 18.dp)
                    )
                    Spacer(modifier = Modifier.height(10.dp))
                    Text(
                        text = "需要蓝牙权限来扫描和连接附近的设备,但是权限被拒绝,请前往设置页面手动开启",
                        fontSize = 16.sp,
                        modifier = Modifier.padding(bottom = 16.dp)
                    )
                    Spacer(modifier = Modifier.height(10.dp))
                    Row(
                        modifier = Modifier.fillMaxWidth(),
                        horizontalArrangement = Arrangement.Center
                    ) {
                        Button(
                            modifier = Modifier.weight(1f),
                            onClick = {
                                isShowRefusePermissionDialog.value = false
                                // 跳转到应用设置页面
                                val intent = android.content.Intent(
                                    android.provider.Settings.ACTION_APPLICATION_DETAILS_SETTINGS,
                                    android.net.Uri.parse("package:${context.packageName}")
                                )
                                context.startActivity(intent)
                            }) {
                            Text("知道了", color = MaterialTheme.colorScheme.onPrimary)
                        }
                    }
                }
            }
        }
        return
    }

    // 检查是否已拥有所有权限
    val hasAllPermissions = remember(bluetoothPermissions) {
        bluetoothPermissions.all { permission ->
            ContextCompat.checkSelfPermission(
                context,
                permission
            ) == PackageManager.PERMISSION_GRANTED
        }
    }

    val isShowDialog = remember { mutableStateOf(true) }

    if (hasAllPermissions || allPermissionsGranted.value) {
        "所有权限都已授予".logE()
        agree.invoke()
    } else {
        if (isShowDialog.value) {
            Dialog(onDismissRequest = {
                isShowDialog.value = false
                refuse.invoke()
            }) {
                // 自定义对话框内容
                Card(
                    colors = CardDefaults.cardColors(
                        containerColor = Color.White,           // 默认状态
                        disabledContainerColor = Color.LightGray // 禁用状态
                    )
                ) {
                    Column(
                        modifier = Modifier.padding(20.dp),
                        horizontalAlignment = Alignment.CenterHorizontally
                    ) {
                        Text(
                            text = "申请权限",
                            fontSize = 18.sp,
                            modifier = Modifier.padding(bottom = 18.dp)
                        )
                        Spacer(modifier = Modifier.height(10.dp))
                        Text(
                            text = "需要蓝牙权限来扫描和连接附近的设备",
                            fontSize = 16.sp,
                            modifier = Modifier.padding(bottom = 16.dp)
                        )
                        Spacer(modifier = Modifier.height(10.dp))
                        Row(
                            modifier = Modifier.fillMaxWidth(),
                            horizontalArrangement = Arrangement.Center
                        ) {
                            Button(
                                modifier = Modifier.weight(1f),
                                colors = ButtonDefaults.buttonColors(
                                    containerColor = Color.Gray.copy(alpha = 0.5f),
                                    contentColor = Color.White
                                ),
                                onClick = {
                                    isShowDialog.value = false
                                    refuse.invoke()
                                }) {
                                Text("取消")
                            }
                            Spacer(modifier = Modifier.width(16.dp))
                            Button(
                                modifier = Modifier.weight(1f),
                                onClick = {
                                    permissionLauncher.launch(bluetoothPermissions)
                                }) {
                                Text("申请", color = MaterialTheme.colorScheme.onPrimary)
                            }
                        }
                    }
                }
            }
        }
    }
}

申请相机权限



import android.Manifest
import android.app.Activity
import android.content.Context
import android.content.Intent
import android.content.pm.PackageManager
import android.net.Uri
import android.provider.Settings
import androidx.activity.compose.rememberLauncherForActivityResult
import androidx.activity.result.contract.ActivityResultContracts
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.width
import androidx.compose.material3.Button
import androidx.compose.material3.ButtonDefaults
import androidx.compose.material3.Card
import androidx.compose.material3.CardDefaults
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.mutableStateListOf
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
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.compose.ui.window.Dialog
import androidx.core.app.ActivityCompat
import androidx.core.content.ContextCompat
import com.yak.bleSdk.log.logE
import com.yak.bleSdk.log.logI


private val cameraPermissions by lazy {
    arrayOf(Manifest.permission.CAMERA)
}


/**
 * 检查相机权限
 */
fun checkCameraPermissions(context: Context): Boolean {
    // 检查是否已拥有所有权限
    val hasAllPermissions = cameraPermissions.all { permission ->
        ContextCompat.checkSelfPermission(context, permission) == PackageManager.PERMISSION_GRANTED
    }
    return hasAllPermissions
}

/**
 * 请求相机权限
 * @param isRequest 是否请求权限, 这个布尔值可以用来控制申请权限的时机比如按下某个按钮后申请权限,如果不需要选择时机,可以默认为true
 *  如果需要控制申请时机,请参考:
 *  ```kotlin
 *     val isRequestCameraPermissions = remember { mutableStateOf(false) }
 *     RequestCameraPermissions(
 *         isRequest = isRequestCameraPermissions.value,
 *         agree = {
 *             // 用户同意权限后的操作
 *         },
 *         refuse = {
 *             // 用户拒绝权限后的操作
 *         }
 *     )
 *     Button(
 *             onClick = {
 *                 if(!checkCameraPermissions(context)){
 *                     isRequestCameraPermissions.value = true
 *                 }
 *             },
 *         ) {
 *             Text("点击申请权限", modifier = Modifier.padding(8.dp))
 *     }
 * ```
 * @param agree 同意
 * @param refuse 拒绝
 */
@Composable
fun RequestCameraPermissions(isRequest: Boolean = true, agree: () -> Unit, refuse: () -> Unit) {
    if (!isRequest) {
        return
    }
    val context = LocalContext.current
    val activity = LocalContext.current as Activity
    val allPermissionsGranted = remember { mutableStateOf(false) }
    // 记录被永久拒绝的权限列表
    val permanentlyDeniedPermissions = remember { mutableStateListOf<String>() }

    val permissionLauncher = rememberLauncherForActivityResult(
        ActivityResultContracts.RequestMultiplePermissions()
    ) { permissionsMap ->
        allPermissionsGranted.value = permissionsMap.values.all { it }

        if (allPermissionsGranted.value) {
            // 所有权限都已授予
            "相机权限已授予".logI()
            agree.invoke()
        } else {
            // 处理权限被拒绝的情况
            "相机权限被拒绝".logE()
            refuse.invoke()
            // 遍历结果,找出被拒绝的权限,并判断是否为永久拒绝
            permissionsMap.entries.forEach { entry ->
                val permission = entry.key
                val isGranted = entry.value
                if (!isGranted) {
                    // 关键判断:检查该权限是否被永久拒绝
                    if (!ActivityCompat.shouldShowRequestPermissionRationale(activity, permission)) {
                        "有权限被永久拒绝".logE()
                        permanentlyDeniedPermissions.add(permission)
                    }
                }
            }
        }
    }

    val isShowRefusePermissionDialog = remember { mutableStateOf(true) }

    // 如果有权限被永久拒绝,可以在这里提示用户前往应用设置页手动开启
    if (permanentlyDeniedPermissions.isNotEmpty() && isShowRefusePermissionDialog.value) {
        // 例如,显示一个对话框引导用户去设置
        Dialog(onDismissRequest = {
            isShowRefusePermissionDialog.value = false
            refuse.invoke()
        }) {
            // 自定义对话框内容
            Card(
                colors = CardDefaults.cardColors(
                    containerColor = Color.White,           // 默认状态
                    disabledContainerColor = Color.LightGray // 禁用状态
                )
            ) {
                Column(
                    modifier = Modifier.padding(20.dp),
                    horizontalAlignment = Alignment.CenterHorizontally
                ) {
                    Text(
                        text = "温馨提示",
                        fontSize = 18.sp,
                        modifier = Modifier.padding(bottom = 18.dp)
                    )
                    Spacer(modifier = Modifier.height(10.dp))
                    Text(
                        text = "需要相机权限来扫描二维码,但是权限被拒绝,请前往设置页面手动开启",
                        fontSize = 16.sp,
                        modifier = Modifier.padding(bottom = 16.dp)
                    )
                    Spacer(modifier = Modifier.height(10.dp))
                    Row(
                        modifier = Modifier.fillMaxWidth(),
                        horizontalArrangement = Arrangement.Center
                    ) {
                        Button(
                            modifier = Modifier.weight(1f),
                            onClick = {
                                isShowRefusePermissionDialog.value = false
                                // 跳转到应用设置页面
                                val intent = Intent(
                                    Settings.ACTION_APPLICATION_DETAILS_SETTINGS,
                                    Uri.parse("package:${context.packageName}")
                                )
                                context.startActivity(intent)
                            }) {
                            Text("知道了", color = MaterialTheme.colorScheme.onPrimary)
                        }
                    }
                }
            }
        }
        return
    }

    // 检查是否已拥有所有权限
    val hasAllPermissions = remember(cameraPermissions) {
        cameraPermissions.all { permission ->
            ContextCompat.checkSelfPermission(
                context,
                permission
            ) == PackageManager.PERMISSION_GRANTED
        }
    }

    val isShowDialog = remember { mutableStateOf(true) }

    if (hasAllPermissions || allPermissionsGranted.value) {
        "相机权限已授予".logI()
        agree.invoke()
    } else {
        if (isShowDialog.value) {
            Dialog(onDismissRequest = {
                isShowDialog.value = false
                refuse.invoke()
            }) {
                // 自定义对话框内容
                Card(
                    colors = CardDefaults.cardColors(
                        containerColor = Color.White,           // 默认状态
                        disabledContainerColor = Color.LightGray // 福用状态
                    )
                ) {
                    Column(
                        modifier = Modifier.padding(20.dp),
                        horizontalAlignment = Alignment.CenterHorizontally
                    ) {
                        Text(
                            text = "申请权限",
                            fontSize = 18.sp,
                            modifier = Modifier.padding(bottom = 18.dp)
                        )
                        Spacer(modifier = Modifier.height(10.dp))
                        Text(
                            text = "需要相机权限来扫描二维码",
                            fontSize = 16.sp,
                            modifier = Modifier.padding(bottom = 16.dp)
                        )
                        Spacer(modifier = Modifier.height(10.dp))
                        Row(
                            modifier = Modifier.fillMaxWidth(),
                            horizontalArrangement = Arrangement.Center
                        ) {
                            Button(
                                modifier = Modifier.weight(1f),
                                colors = ButtonDefaults.buttonColors(
                                    containerColor = Color.Gray.copy(alpha = 0.5f),
                                    contentColor = Color.White
                                ),
                                onClick = {
                                    isShowDialog.value = false
                                    refuse.invoke()
                                }) {
                                Text("取消")
                            }
                            Spacer(modifier = Modifier.width(16.dp))
                            Button(
                                modifier = Modifier.weight(1f),
                                onClick = {
                                    permissionLauncher.launch(cameraPermissions)
                                }) {
                                Text("申请", color = MaterialTheme.colorScheme.onPrimary)
                            }
                        }
                    }
                }
            }
        }
    }
}

请求打开蓝牙


import android.bluetooth.BluetoothAdapter
import android.content.Context
import android.content.Intent
import android.provider.Settings
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.width
import androidx.compose.material3.Button
import androidx.compose.material3.ButtonDefaults
import androidx.compose.material3.Card
import androidx.compose.material3.CardDefaults
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
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.compose.ui.window.Dialog

/**
 * 检查蓝牙是否已开启
 */
fun isBluetoothEnabled(): Boolean {
    val bluetoothAdapter = BluetoothAdapter.getDefaultAdapter()
    return bluetoothAdapter?.isEnabled ?: false
}

/**
 * 请求开启蓝牙功能
 */
@Composable
fun RequestOpenBluetooth(isRequest: Boolean = true) {
    if (!isRequest) {
        return
    }
    val context = LocalContext.current
    val bluetoothAdapter = BluetoothAdapter.getDefaultAdapter()
    val isBluetoothEnabled = remember { mutableStateOf(bluetoothAdapter?.isEnabled ?: false) }
    val isShowDialog = remember { mutableStateOf(true) }

    if (!isBluetoothEnabled.value) {
        if (isShowDialog.value) {
            Dialog(onDismissRequest = {
                isShowDialog.value = false
            }) {
                Card(
                    colors = CardDefaults.cardColors(
                        containerColor = Color.White,
                        disabledContainerColor = Color.LightGray
                    )
                ) {
                    Column(
                        modifier = Modifier.padding(20.dp),
                        horizontalAlignment = Alignment.CenterHorizontally
                    ) {
                        Text(
                            text = "开启蓝牙",
                            fontSize = 18.sp,
                            modifier = Modifier.padding(bottom = 18.dp)
                        )
                        Spacer(modifier = Modifier.height(10.dp))
                        Text(
                            text = "需要开启蓝牙来扫描和连接附近的设备",
                            fontSize = 16.sp,
                            modifier = Modifier.padding(bottom = 16.dp)
                        )
                        Spacer(modifier = Modifier.height(10.dp))
                        Row(
                            modifier = Modifier.fillMaxWidth(),
                            horizontalArrangement = Arrangement.Center
                        ) {
                            Button(
                                modifier = Modifier.weight(1f),
                                colors = ButtonDefaults.buttonColors(
                                    containerColor = Color.Gray.copy(alpha = 0.5f),
                                    contentColor = Color.White
                                ),
                                onClick = {
                                    isShowDialog.value = false
                                }) {
                                Text("取消")
                            }
                            Spacer(modifier = Modifier.width(16.dp))
                            Button(
                                modifier = Modifier.weight(1f),
                                onClick = {
                                    openBluetoothSettings(context)
                                    isShowDialog.value = false
                                }) {
                                Text("去设置")
                            }
                        }
                    }
                }
            }
        }
    }
}

/**
 * 跳转到蓝牙设置页面
 */
fun openBluetoothSettings(context: Context) {
    val intent = Intent(Settings.ACTION_BLUETOOTH_SETTINGS)
    context.startActivity(intent)
}

 

 

 

End

posted on 2025-11-28 09:17  观心静  阅读(4)  评论(0)    收藏  举报