环境要求与权限配置
本文档说明 Jieli Bluetooth(杰理蓝牙)SDK 示例工程 PiHome_V1.13.0_SDK_V4.2.0 的构建/运行环境要求,以及蓝牙、定位、存储等运行时权限的声明与配置方式,帮助集成方快速完成工程接入前的环境准备。
Purpose and Scope
本页覆盖以下内容:
- 编译构建环境要求(
compileSdk/minSdk/targetSdk、JDK 版本、NDK ABI 支持) AndroidManifest.xml中声明的全部权限、uses-feature硬件能力要求- Android 各版本(6.0 运行时权限、12+ 蓝牙细分权限、13+ 通知权限)的权限适配要点
build.gradle中与权限、环境相关的配置项(applicationId、manifestPlaceholders、flavor 等)
与本页相邻但由其他页面覆盖的内容:SDK 的具体 API 调用、设备连接流程、固件升级流程等不在本页展开,参见相关页面。
Overview
Android 系统对蓝牙应用的权限要求随版本不断收紧。本示例工程作为杰理蓝牙 SDK 的完整参考实现,其权限声明覆盖了:
- 经典蓝牙 / BLE 通信:
BLUETOOTH、BLUETOOTH_ADMIN(Android 12 以下)与BLUETOOTH_SCAN、BLUETOOTH_CONNECT(Android 12+)。 - BLE 扫描所依赖的定位权限:
ACCESS_COARSE_LOCATION/ACCESS_FINE_LOCATION(Android 11 及以下扫描 BLE 必须)。 - 业务功能权限:相机(扫码配网)、录音、通知、外部存储、精确闹钟、悬浮窗、前台服务(含
connectedDevice/location类型)等。 - 硬件能力声明:通过
uses-feature声明 BLE 与摄像头为必需能力,过滤不支持设备。
工程侧(build.gradle)则通过 minSdk 21(Android 5.0)到 targetSdk 35(Android 15)的区间,覆盖了绝大多数存量设备,并通过 compileSdk 36 使用最新的编译 API。
Architecture
下图展示了环境要求与权限配置在工程中的整体结构:构建配置决定权限声明与 SDK 版本,清单文件声明静态权限与能力,系统运行时权限模型决定动态授权行为。
flowchart TD
subgraph sg_Build["构建环境 (build.gradle)"]
CompileSdk["compileSdk 36"]
MinSdk["minSdk 21"]
TargetSdk["targetSdk 35"]
JDK8["Java 8 兼容"]
ABI["ABI: armeabi-v7a / arm64-v8a"]
end
subgraph sg_Manifest["清单声明 (AndroidManifest.xml)"]
FeatureBLE["uses-feature bluetooth_le"]
FeatureCam["uses-feature camera"]
PermLegacy["BLUETOOTH / BLUETOOTH_ADMIN"]
Perm12["BLUETOOTH_SCAN / BLUETOOTH_CONNECT"]
PermLoc["ACCESS_COARSE/FINE_LOCATION"]
PermBiz["相机/录音/存储/通知/前台服务等"]
Services["FloatingViewService / APSService 等前台服务"]
end
subgraph sg_Runtime["系统运行时权限模型"]
DangPerm["危险权限动态申请 (Android 6.0+)"]
BT12["蓝牙细分权限 (Android 12+)"]
Notify13["通知权限 (Android 13+)"]
FGS14["前台服务类型 (Android 14+)"]
end
sg_Build -->|"决定 targetSdk 行为"| sg_Manifest
sg_Manifest -->|"静态声明"| sg_Runtime
PermLegacy --> BT12
PermLoc --> DangPerm
Perm12 --> DangPerm
PermBiz --> DangPerm
PermBiz --> Notify13
Services --> FGS14
各层说明:
- 构建环境层:
build.gradle中的compileSdk、minSdk、targetSdk直接决定权限语义——尤其是targetSdk切换 Android 12/13 后系统会采用新的蓝牙细分权限与通知权限模型,这是集成时最容易踩坑的地方。 - 清单声明层:
AndroidManifest.xml集中声明全部静态权限、硬件能力与前台服务。所有权限在这里声明后,危险权限还需在运行时动态申请。 - 系统运行时模型层:Android 系统按版本对权限进行分组(普通/危险/特殊),示例工程通过
targetSdk 35触发新版权限行为,需要配套的运行时申请逻辑。
构建环境要求
以下配置摘自 btsmart/build.gradle:
android {
namespace 'com.jieli.btsmart'
compileSdk 36
defaultConfig {
applicationId "com.jieli.btsmart"
minSdk 21
targetSdk 35
versionCode 113126
versionName "1.13.0"
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
multiDexEnabled true
...
ndk {
// 设置支持的SO库架构
abiFilters 'armeabi-v7a', 'arm64-v8a'
}
}
...
compileOptions {
sourceCompatibility JavaVersion.VERSION_1_8
targetCompatibility JavaVersion.VERSION_1_8
}
}
Source: btsmart/build.gradle
关键环境参数:
| 参数 | 值 | 说明 |
|---|---|---|
compileSdk | 36 | 使用 Android 16 编译 API |
minSdk | 21 | Android 5.0(Lollipop)起支持 |
targetSdk | 35 | Android 15,启用新版权限行为 |
Java | 1.8 | sourceCompatibility / targetCompatibility 均为 VERSION_1_8 |
abiFilters | armeabi-v7a、arm64-v8a | 仅打包 32/64 位 ARM 架构的 SO 库 |
multiDexEnabled | true | 方法数超过 64K,需开启 multidex |
targetSdk 的权限语义
targetSdk 35 意味着在 Android 12+(API 31+)设备上,系统将应用视为面向新版权限模型:
- Android 12+:
BLUETOOTH_SCAN/BLUETOOTH_CONNECT成为必需,旧权限BLUETOOTH/BLUETOOTH_ADMIN仅起到兼容作用。 - Android 13+:
POST_NOTIFICATIONS作为危险权限需要动态申请;READ_MEDIA_AUDIO/READ_MEDIA_IMAGES取代旧的存储权限读写媒体文件。 - Android 14+:前台服务必须声明类型(
foregroundServiceType),本工程声明了connectedDevice与location。
NDK ABI 与硬件依赖
abiFilters 只保留 armeabi-v7a 与 arm64-v8a,将打包体积控制在最小且覆盖主流机型;因此运行环境必须是 ARM 架构 Android 设备(x86 模拟器上蓝牙相关 SO 库不可用,BLE 功能在模拟器上不可验证)。
权限清单详解
完整权限声明位于 AndroidManifest.xml 的 uses-feature 与 uses-permission 段,按用途分类如下。
硬件能力声明
<uses-feature
android:name="android.hardware.bluetooth_le"
android:required="true" />
<uses-feature
android:name="android.hardware.camera"
android:required="true" />
Source: AndroidManifest.xml
required="true" 表示这两个能力是 App 运行的必要条件:没有 BLE 的设备(或 ROM 关闭了 BLE)将无法安装应用;摄像头用于扫码配网,同样是强依赖。
权限分组表
| 权限 | 类型 | 用途 |
|---|---|---|
INTERNET | 普通 | 网络通信、OTA 资源下载 |
BLUETOOTH / BLUETOOTH_ADMIN | 普通(≤ Android 11) | 经典蓝牙连接与扫描(旧版模型) |
BLUETOOTH_SCAN | 危险(Android 12+) | 扫描 BLE 设备 |
BLUETOOTH_CONNECT | 危险(Android 12+) | 连接已配对的蓝牙设备 |
ACCESS_COARSE_LOCATION / ACCESS_FINE_LOCATION | 危险 | Android 11 及以下 BLE 扫描必须;高德定位 |
CAMERA | 危险 | 二维码扫码配网 |
RECORD_AUDIO | 危险 | 录音功能 |
READ_PHONE_STATE | 危险 | 读取设备信息,用于问题排查 |
READ/WRITE_EXTERNAL_STORAGE | 危险(≤ Android 12) | 旧版外部存储读写 |
READ_MEDIA_AUDIO / READ_MEDIA_IMAGES | 危险(Android 13+) | 新版媒体读取 |
POST_NOTIFICATIONS | 危险(Android 13+) | 发送通知 |
FOREGROUND_SERVICE | 普通 | 启动前台服务 |
FOREGROUND_SERVICE_LOCATION | 危险 | 定位型前台服务(高德 APSService) |
FOREGROUND_SERVICE_CONNECTED_DEVICE | 普通 | 连接设备型前台服务(FloatingViewService 等) |
WAKE_LOCK | 普通 | 保持屏幕/CPU 唤醒 |
VIBRATE | 普通 | 振动提醒 |
CHANGE_WIFI_STATE / CHANGE_NETWORK_STATE / ACCESS_WIFI_STATE / ACCESS_NETWORK_STATE | 普通 | Wi-Fi 配网与网络状态监听 |
GET_TASKS / REORDER_TASKS | 普通 | 任务栈管理(Launcher 场景) |
SYSTEM_ALERT_WINDOW | 特殊 | 悬浮窗(设备弹窗、FloatingViewService) |
SCHEDULE_EXACT_ALARM | 特殊 | 精确闹钟调度 |
HIGH_SAMPLING_RATE_SENSORS | 普通 | 高采样率传感器(手势识别) |
应用级配置与前台服务声明
Application 级属性
AndroidManifest.xml 的 <application> 标签声明了多个与运行环境相关的属性:
<application
android:name=".MainApplication"
android:allowBackup="true"
android:allowNativeHeapPointerTagging="false"
android:icon="@mipmap/ic_btsmart_logo"
android:label="@string/app_name"
android:requestLegacyExternalStorage="true"
android:roundIcon="@mipmap/ic_btsmart_logo"
android:supportsRtl="true"
android:theme="@style/AppTheme"
android:usesCleartextTraffic="true"
tools:ignore="GoogleAppIndexingWarning"
tools:replace="android:allowBackup">
Source: AndroidManifest.xml
值得注意的设计意图:
android:requestLegacyExternalStorage="true":在 Android 10/11 上继续使用旧的外部存储模型,配合READ/WRITE_EXTERNAL_STORAGE权限读写共享存储;Android 13+ 则改由READ_MEDIA_*权限接管媒体文件。android:usesCleartextTraffic="true":允许明文 HTTP 流量,用于局域网设备通信与 OTA 资源服务器(https://cam.jieliapp.com等),集成方应评估安全策略。android:allowNativeHeapPointerTagging="false":关闭 Native 堆指针标签,兼容部分老型号 SoC 的蓝牙协议栈。
前台服务与系统组件
工程声明了若干系统组件,均与蓝牙/定位业务强相关:
<service
android:name=".ui.widget.product_dialog.FloatingViewService"
android:enabled="true"
android:exported="true"
android:foregroundServiceType="connectedDevice" /> <!-- 定位需要的服务 使用2.0的定位需要加上这个 -->
<service
android:name="com.amap.api.location.APSService"
android:enabled="true"
android:exported="true"
android:foregroundServiceType="location" />
<service
android:name=".ui.widget.DevicePopDialog.DevicePopDialog"
android:enabled="true"
android:exported="true"
android:foregroundServiceType="connectedDevice"
android:permission="android.permission.BIND_NOTIFICATION_LISTENER_SERVICE">
<intent-filter>
<action android:name="android.service.notification.NotificationListenerService" />
</intent-filter>
</service>
Source: AndroidManifest.xml
FloatingViewService与DevicePopDialog声明为connectedDevice类型前台服务,保持与已连接蓝牙设备的常驻通道,同时满足 Android 14+ 对前台服务类型的强制要求。APSService(高德定位)声明为location类型,因此需要FOREGROUND_SERVICE_LOCATION权限。DevicePopDialog还是通知监听服务(NotificationListenerService),需要用户在系统设置中手动授权"通知使用权"——这是系统特殊授权,无法通过运行时权限弹窗获取。FileProvider(${applicationId}.provider)用于跨应用共享文件(如扫码后的图片裁剪),grantUriPermissions="true"配合provider_paths控制可共享路径。
运行时权限申请流程
清单声明只是第一步;危险权限必须在运行时申请。下图描述了 BLE 场景下的典型权限流转:
sequenceDiagram
participant U as 用户
participant A as LauncherActivity/CommonActivity
participant S as Android 系统
participant BT as 蓝牙协议栈
A->>S: 检查 targetSdk 对应权限模型
alt Android 12+ (API 31+)
A->>S: 申请 BLUETOOTH_SCAN + BLUETOOTH_CONNECT
else Android 6~11 (API 23-30)
A->>S: 申请 ACCESS_FINE_LOCATION
end
S-->>A: 弹出授权对话框
U->>S: 授权/拒绝
S-->>A: onRequestPermissionsResult
alt 已授权
A->>BT: 开启扫描/连接设备
else 被拒绝
A->>U: 提示引导去设置页手动开启
end
设计意图说明:
- 版本分叉:Android 12 起系统将蓝牙权限从定位权限中剥离。若应用仍只申请定位权限,在 Android 12+ 上将无法扫描/连接;反之,在 Android 11 及以下系统只认定位权限。因此兼容区间(
minSdk 21→targetSdk 35)的集成方必须同时声明两组权限并做运行时分支。 - 位置开关:即使定位权限已授权,Android 11 及以下还要求系统"位置服务"总开关打开,否则 BLE 扫描静默失败——这是最常见的中文 ROM 排查点。
- 特殊权限:
SYSTEM_ALERT_WINDOW(悬浮窗)与通知使用权需要跳转系统设置页由用户手动开启,代码中只能通过Settings.canDrawOverlays()等 API 检查状态后引导。
Usage Examples
示例一:声明蓝牙与定位权限(清单文件)
<uses-permission android:name="android.permission.BLUETOOTH" />
<uses-permission android:name="android.permission.BLUETOOTH_ADMIN" />
<uses-permission android:name="android.permission.BLUETOOTH_SCAN" /> <!-- Dangerous Permissions -->
<uses-permission android:name="android.permission.BLUETOOTH_CONNECT" />
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
Source: AndroidManifest.xml
注意 BLUETOOTH_SCAN 与 BLUETOOTH_CONNECT 注释为 "Dangerous Permissions"——它们必须在运行时动态申请,单纯声明无法在 Android 12+ 上获得授权。
示例二:多 flavor 构建注入密钥占位符
productFlavors {
btsmart {
...
addManifestPlaceholders([amap_key: "8a5a32e0e1b72bd104a68ceb976688d4"])
}
pilink {
...
addManifestPlaceholders([amap_key: "7b12da60620e2fee98dfb00404f2825d"])
}
}
Source: btsmart/build.gradle
清单中对应使用 ${amap_key} 占位符(高德地图 Key):
<meta-data
android:name="com.amap.api.v2.apikey"
android:value="${amap_key}" />
Source: AndroidManifest.xml
这种"占位符 + flavor 注入"的模式让同一套代码通过不同 applicationId、隐私政策 URL、地图 Key 打包成多个品牌应用,权限声明与业务代码完全复用。
Configuration Options
下表汇总本主题涉及的全部可配置项(来自 build.gradle 与 AndroidManifest.xml):
| 配置项 | 类型 | 默认值 | 说明 |
|---|---|---|---|
compileSdk | int | 36 | 编译所用 SDK 版本,需 AGP 与 Android Studio 支持 |
minSdk | int | 21 | 最低支持 Android 5.0 |
targetSdk | int | 35 | 决定新版权限/前台服务行为 |
versionCode | int | 113126 | 版本号(btsmart flavor) |
versionName | string | "1.13.0" | 版本名 |
applicationId | string | com.jieli.btsmart(pilink: com.jieli.pilink) | 包名,随 flavor 切换 |
multiDexEnabled | boolean | true | 开启 multidex,支撑 64K+ 方法数 |
abiFilters | list | armeabi-v7a, arm64-v8a | SO 库架构过滤 |
sourceCompatibility / targetCompatibility | enum | VERSION_1_8 | Java 8 语言特性 |
amap_key(manifestPlaceholder) | string | flavor 各自注入 | 高德地图 Key |
user_agreement_url / app_privacy_policy | string res | https://cam.jieliapp.com/... | 用户协议/隐私政策 URL |
update_resource_version | string res | 113000 | OTA 资源版本号 |
OPEN_LAUNCHER_ANIM(BuildConfig) | boolean | btsmart: true / pilink: false | 启动动画开关 |
requestLegacyExternalStorage | boolean | true | Android 10/11 旧外部存储模式 |
usesCleartextTraffic | boolean | true | 允许明文 HTTP |
foregroundServiceType | enum | connectedDevice / location | 前台服务类型声明 |
Failure Modes、Edge Cases 与并发注意
典型失败场景
| 场景 | 现象 | 根因与对策 |
|---|---|---|
| Android 12+ 无法扫描设备 | 扫描回调无结果或 SecurityException | 未动态申请 BLUETOOTH_SCAN;需在运行时按版本分支申请 |
| Android 11 及以下扫描无结果 | 扫描静默失败 | 定位权限未授权或系统"位置服务"开关关闭 |
| Android 13+ 收不到推送通知 | 通知不显示 | POST_NOTIFICATIONS 未动态申请 |
| Android 14+ 前台服务崩溃 | ForegroundServiceStartNotAllowedException / SecurityException | 缺少对应 FOREGROUND_SERVICE_* 权限或未在清单声明 foregroundServiceType |
| 悬浮窗不显示 | 设备弹窗无法弹出 | SYSTEM_ALERT_WINDOW 为特殊权限,需跳设置页手动开启 |
| 通知栏设备弹窗无数据 | 通知使用权未授权 | DevicePopDialog 是 NotificationListenerService,需用户手动开启"通知使用权" |
| x86 模拟器崩溃 | so 库加载失败 | abiFilters 不含 x86,需在 ARM 真机验证 |
| 安装被拒 | "设备不兼容" | 设备缺少 BLE/摄像头硬件能力(uses-feature required=true) |
边界与一致性注意
- 权限持久化:危险权限授权状态可被用户随时在设置中撤销,且 Android 11+ 存在"仅此一次"授权模式;应用应在每次执行扫描/连接前检查权限,而非假设一次申请终身有效。
- 请求并发:系统同一时间只允许一个权限请求对话框,多个页面同时触发
requestPermissions会导致部分请求被系统丢弃。建议在MainApplication或单例的权限管理器中串行化请求。 - 多 flavor 一致性:
btsmart与pilink共用同一份AndroidManifest.xml,但注入不同的amap_key与隐私政策地址;集成方新增 flavor 时必须同步注入占位符,否则清单合并失败。 tools:replace="android:allowBackup":通过tools命名空间强制覆盖依赖库中的同名属性,说明工程存在依赖冲突时清单合并的仲裁策略,修改时需谨慎。
扩展点与集成建议
- 权限模型扩展:如集成方需要新增功能(如
BLUETOOTH_ADVERTISE广播、NEARBY_WIFI_DEVICES附近 Wi-Fi 设备),只需在uses-permission追加声明并在运行时申请,targetSdk 35的框架已就绪。 - flavor 扩展:参照
btsmart/pilink结构新增 flavor,用addManifestPlaceholders注入各自的applicationId、amap_key与隐私政策链接即可复用全部权限与代码。 - 国内 ROM 适配:小米/华为等 ROM 有额外的自启动、后台定位、省电策略限制,建议在权限引导之外增加"电池优化白名单"引导(
REQUEST_IGNORE_BATTERY_OPTIMIZATIONS为可选扩展,当前工程未声明)。