概述:
动态获取权限说明:
- 1.Android 6 至 Android 11 需要动态获取 ACCESS_FINE_LOCATION 和 ACCESS_COARSE_LOCATION 权限;
- 2.Android 10 至 Android 11 蓝牙扫描需要开启 定位开关;
- 3.Android 12 及以上 需要动态获取 BLUETOOTH_CONNECT 和BLUETOOTH_SCAN 权限;
<!-- 基础蓝牙权限 -->
<uses-permission android:name="android.permission.BLUETOOTH" />
<uses-permission android:name="android.permission.BLUETOOTH_ADMIN" />
<!-- 位置权限(Android 6.0+ 需要) -->
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
<!-- Android 11+ 新增的蓝牙扫描权限 -->
<uses-permission android:name="android.permission.BLUETOOTH_SCAN" />
<!-- Android 12+ 需要的蓝牙连接权限 -->
<uses-permission android:name="android.permission.BLUETOOTH_CONNECT" />
<!-- 如果需要访问蓝牙设备信息 -->
<uses-permission android:name="android.permission.BLUETOOTH_ADVERTISE" />
<!-- 如果需要前台服务(适用于后台扫描) -->
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
1. Android 6.0(API 级别 23)以下
在 Android 6.0 之前,权限在应用安装时就会被授予,不需要在运行时请求权限。只需要在 AndroidManifest.xml 中声明所需的蓝牙权限即可。
<!-- 蓝牙权限 -->
<uses-permission android:name="android.permission.BLUETOOTH" />
<uses-permission android:name="android.permission.BLUETOOTH_ADMIN" />
2. Android 6.0(API 级别 23)到 Android 11(API 级别 30)
从 Android 6.0 开始,引入了运行时权限机制,部分危险权限需要在运行时请求。对于蓝牙功能,除了声明蓝牙权限外,还需要请求位置权限,因为蓝牙扫描可能会泄露用户的位置信息。
<!-- 位置权限 -->
<!-- <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />-->
<!-- <uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />-->
<uses-permission-sdk-23 android:name="android.permission.ACCESS_FINE_LOCATION" />
<uses-permission-sdk-23 android:name="android.permission.ACCESS_COARSE_LOCATION" />
<!-- 蓝牙权限 -->
<uses-permission android:name="android.permission.BLUETOOTH" />
<uses-permission android:name="android.permission.BLUETOOTH_ADMIN" />
3. Android 12(API 级别 31)及以上
从 Android 12 开始,引入了新的蓝牙权限 BLUETOOTH_CONNECT 和 BLUETOOTH_SCAN,分别用于连接蓝牙设备和扫描蓝牙设备。
<!-- 位置权限 -->
<!-- <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />-->
<!-- <uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />-->
<uses-permission-sdk-23 android:name="android.permission.ACCESS_FINE_LOCATION" />
<uses-permission-sdk-23 android:name="android.permission.ACCESS_COARSE_LOCATION" />
<!-- 新的蓝牙权限 -->
<uses-permission android:name="android.permission.BLUETOOTH_CONNECT" />
<uses-permission android:name="android.permission.BLUETOOTH_SCAN" />
<!-- 旧的蓝牙权限 -->
<uses-permission android:name="android.permission.BLUETOOTH" />
<uses-permission android:name="android.permission.BLUETOOTH_ADMIN" />
权限集合 组装代码
val permissionList = mutableListOf<String>()
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {// Android 6.0到Android 11
permissionList.add(Manifest.permission.ACCESS_FINE_LOCATION)// 精确位置
permissionList.add(Manifest.permission.ACCESS_COARSE_LOCATION)// 粗略位置
}
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) { // Android 12及以上
permissionList.add(Manifest.permission.BLUETOOTH_CONNECT)
permissionList.add(Manifest.permission.BLUETOOTH_SCAN)
}
LogUtils.d("权限列表:$permissionList")
PermissionX.init(this)
.permissions(permissionList)
.onExplainRequestReason { scope, deniedList ->
val message = "蓝牙使用需要同意以下权限才能正常使用"
scope.showRequestReasonDialog(deniedList, message, "允许", "拒绝")
}
.onForwardToSettings { scope, deniedList ->
scope.showForwardToSettingsDialog(
deniedList, "定位权限设置", "ok", "ignore"
)
}
.request { allGranted, grantedList, deniedList -> }
权限申请完整代码
BasePermissionActivity.kt
package com.aimed.measure.ui.measure
import android.Manifest
import android.app.AlertDialog
import android.bluetooth.BluetoothAdapter
import android.content.BroadcastReceiver
import android.content.Context
import android.content.Intent
import android.content.IntentFilter
import android.content.pm.PackageManager
import android.location.LocationManager
import android.os.Build
import android.provider.Settings
import androidx.viewbinding.ViewBinding
import com.aimed.measure.R
import com.aimed.measure.base.BaseVBActivity
import com.aimed.measure.ext.showToast
import com.aimed.measure.utils.GpsUtil
import com.aimed.measure.utils.LogUtils
import com.aimed.measure.utils.StringUtils
import com.kl.minttisdk.ble.BleConstants
import com.permissionx.guolindev.PermissionX
abstract class BasePermissionActivity<VB : ViewBinding> : BaseVBActivity<VB>() {
private var permissionCallback: (() -> Unit)? = null
private val permissionList = mutableListOf<String>()
var isNeedPermission = false
// 统设置页面通常不会返回结果数据,导致 onActivityResult() 不被调用,所以采用广播。
private val gpsStatusReceiver = object : BroadcastReceiver() {
override fun onReceive(context: Context, intent: Intent) {
if (LocationManager.PROVIDERS_CHANGED_ACTION == intent.action) {
LogUtils.d("GPS已开启")
if (GpsUtil.isGpsEnable(this@BasePermissionActivity)) {
if (isBluetoothEnable()) {
LogUtils.d("蓝牙已开启")
isNeedPermission = true
permissionCallback?.invoke()
} else {
//打开蓝牙
enableBluetooth()
}
} else {
showToast("位置信息开启失败,请手动开启")
}
}
}
}
fun requestPermission(callback: (() -> Unit)?) {
permissionCallback = callback
val permissionList = mutableListOf<String>()
permissionList.add(Manifest.permission.WRITE_EXTERNAL_STORAGE)
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {// Android 6.0到Android 11
permissionList.add(Manifest.permission.ACCESS_FINE_LOCATION)// 精确位置
permissionList.add(Manifest.permission.ACCESS_COARSE_LOCATION)// 粗略位置
}
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) { // Android 12及以上
permissionList.add(Manifest.permission.BLUETOOTH_CONNECT)
permissionList.add(Manifest.permission.BLUETOOTH_SCAN)
}
LogUtils.d("权限列表:$permissionList")
LogUtils.d("SDK版本:${Build.VERSION.SDK_INT}")
PermissionX.init(this)
.permissions(permissionList)
.onExplainRequestReason { scope, deniedList ->
val message = "蓝牙使用需要同意以下权限才能正常使用"
scope.showRequestReasonDialog(deniedList, message, "允许", "拒绝")
}
.onForwardToSettings { scope, deniedList ->
scope.showForwardToSettingsDialog(
deniedList, "蓝牙使用需要同意以下权限才能正常使用", "允许", "拒绝"
)
}
.request { allGranted, grantedList, deniedList ->
if (allGranted) {
if (GpsUtil.isGpsEnable(this)) {
if (isBluetoothEnable()) {
LogUtils.d("蓝牙已开启")
isNeedPermission = true
permissionCallback?.invoke()
} else {
//打开蓝牙
enableBluetooth()
}
} else {
showOpenGpsDialog()
}
} else {
showToast(getString(R.string.aimed_common_permission))
}
}
}
/**
* 请求打开蓝牙
*/
private fun enableBluetooth() {
try {
val openIntent = Intent(BluetoothAdapter.ACTION_REQUEST_ENABLE)
startActivityForResult(openIntent, BleConstants.ACTION_ENABLE_BLUETOOTH)
} catch (e: SecurityException) {
// 处理异常情况,例如提示用户权限被拒绝
LogUtils.d("请先授予蓝牙权限")
showToast("请先授予蓝牙权限")
}
}
private fun showOpenGpsDialog() {
// 创建一个 AlertDialog.Builder 实例
AlertDialog.Builder(this)
.setTitle("位置信息未启用") // 设置标题
.setMessage(StringUtils.getString(R.string.aimed_local_not_open)) // 设置内容
.setPositiveButton(
"前往设置"
) { dialog, which -> // 用户点击“确定”按钮后的逻辑
// 注册广播接收器
val filter = IntentFilter(LocationManager.PROVIDERS_CHANGED_ACTION)
registerReceiver(gpsStatusReceiver, filter)
startActivity(Intent(Settings.ACTION_LOCATION_SOURCE_SETTINGS))
dialog.dismiss()
}
.setNegativeButton(
"取消"
) { dialog, which -> // 用户点击“取消”按钮后的逻辑
dialog.dismiss()
}
.setCancelable(false) // 禁止用户通过点击外部关闭弹窗
.show() // 显示弹窗
}
/**
* 请求打开蓝牙回调
* @param requestCode
* @param resultCode
* @param data
*/
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
super.onActivityResult(requestCode, resultCode, data)
LogUtils.d("onActivityResult:$requestCode")
if (requestCode == BleConstants.ACTION_ENABLE_BLUETOOTH && resultCode == RESULT_OK) {
isNeedPermission = true
permissionCallback?.invoke()
}
}
/**
* 表示检查设备是否支持低功耗蓝牙(BLE)
*/
fun isSupportBle(): Boolean {
return this.packageManager.hasSystemFeature(PackageManager.FEATURE_BLUETOOTH_LE)
}
/**
* 检查设备是否支持蓝牙(包含BLE和经典蓝牙)
*/
fun isSupportBluetooth(): Boolean {
val bluetoothAdapter = BluetoothAdapter.getDefaultAdapter()
return bluetoothAdapter != null
}
/**
* 蓝牙是否开启
*/
fun isBluetoothEnable(): Boolean {
val bluetoothAdapter = BluetoothAdapter.getDefaultAdapter()
return bluetoothAdapter != null && bluetoothAdapter.isEnabled
}
override fun onDestroy() {
super.onDestroy()
// 注销广播接收器
unregisterReceiver(gpsStatusReceiver)
}
}
GpsUtil .java
public class GpsUtil {
/**
* GPS 或网络定位服务是否启用
*/
public static boolean isGpsEnable(Context context) {
if (context == null) {
return false;
}
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
// Android 10+ 无需检查位置服务状态,只需要检查位置权限
return true;
} else {
// Android 10 及以下需要检查位置服务
LocationManager locationManager = (LocationManager) context.getSystemService(Context.LOCATION_SERVICE);
// List<String> providers = locationManager.getAllProviders();
// LogUtils.d("位置信息:" + providers.toString());
boolean gps = locationManager.isProviderEnabled(LocationManager.GPS_PROVIDER);
boolean network = locationManager.isProviderEnabled(LocationManager.NETWORK_PROVIDER);
boolean fused = locationManager.isProviderEnabled(LocationManager.FUSED_PROVIDER);//融合多种数据源(GPS、Wi-Fi、基站、传感器)计算位置,由 Google Play 服务提供。
return gps || network || fused;
}
}
}