0%

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
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
1. Android 5.0 Lollipop (API 21 / 2014)
主要更新

引入 Material Design 视觉风格

ART 替代 Dalvik 作为默认运行时,提升性能

多用户支持,尤其平板

通知栏可展开,带操作按钮

适配点

新 UI 样式需适配 Material Design

RecyclerView 替代 ListView 提升性能

ART 可能导致一些老旧 Dalvik 字节码异常

2. Android 6.0 Marshmallow (API 23 / 2015)
主要更新

运行时权限机制

Doze / App Standby 节电模式

指纹识别 API

适配点

所有敏感权限需在运行时请求

后台服务 / 定时任务需适配 Doze 节电策略

如果用指纹解锁,需要调用 FingerprintManager / BiometricPrompt

3. Android 7.0 Nougat (API 24 / 2016)
主要更新

多窗口 / 分屏模式

FileProvider 替代直接文件 URI

通知增强:直接回复消息

适配点

UI 需考虑多窗口布局

文件共享需使用 FileProvider,避免 FileUriExposedException

通知交互要支持远程输入

4. Android 8.0 Oreo (API 26 / 2017)
主要更新

后台限制:后台服务受限

通知渠道(Notification Channel)

Adaptive Icon(自适应图标)

Picture-in-Picture 支持

适配点

后台服务需改为 前台服务 + 通知

所有通知需设置 渠道 ID,否则不显示

App 图标需提供适配 圆形 / 方形 的资源

5. Android 9.0 Pie (API 28 / 2018)
主要更新

安全与隐私增强:限制后台访问摄像头、麦克风

App Actions / Slices

Gesture Navigation

适配点

后台访问摄像头或麦克风会报错

手势导航影响全屏布局和 UI 交互

网络安全配置(Network Security Config)可限制明文 HTTP

6. Android 10 (API 29 / 2019)
主要更新

Scoped Storage,限制外部存储访问

暗黑模式

位置权限细化:前台/后台分开

适配点

文件读写要适配 MediaStore / SAF / App-specific storage

UI 适配暗黑模式

权限请求要区分 前台/后台定位

7. Android 11 (API 30 / 2020)
主要更新

一次性权限(临时访问摄像头/位置/麦克风)

包可见性限制:限制查询其他应用

前台服务限制更严格

适配点

权限申请增加“一次性”选项

访问其他应用需在 AndroidManifest.xml 中声明 <queries>

前台服务启动需考虑 延迟 / 弹窗 提示

8. Android 12 (API 31 / 2021)
主要更新

隐私仪表盘 / 麦克风、摄像头指示器

Splash Screen 官方支持

通知动画 / 圆角按钮

适配点

摄像头、麦克风访问时必须用户感知

SplashScreen API 替代自定义启动页

Notification UI 需适配 Material You 动画

9. Android 13 (API 33 / 2022)
主要更新

蓝牙权限细化:BLUETOOTH_CONNECT / BLUETOOTH_SCAN

通知权限必须显式请求

多语言 / 可变主题支持

适配点

蓝牙扫描、连接需申请新权限

发送通知需请求 POST_NOTIFICATIONS 权限

App 需要支持动态语言切换

10. Android 14 (API 34 / 2023)
主要更新

蓝牙 MTU 协商行为更严格

应用后台启动限制更严

隐私限制增强

适配点

BLE MTU 超过外围设备能力会导致数据收发异常,需要控制 MTU 大小或拆分包

后台服务、JobScheduler、WorkManager 适配新的限制

访问敏感信息、剪贴板、文件等操作需注意权限

11.Android 15 (API 级别 35)
✅ 重要新特性

引入或增强隐私、健康、安全相关功能,如 Health Connect 新数据类型支持。

强化大屏/折叠屏/平板设备支持,多设备体验增强。

新的性能/热管理 API:如热头空间 (thermal headroom) 预测、GPU/CPU hint 会话。

媒体与摄像头能力升级:低光增强、闪光强度精调、虚拟 MIDI 2.0 支持。

⚠ 适配要点

如果你的 App 涉及健康数据、营养、传感器等,检查是否使用了 Health Connect 或新 API。

对于大屏/折叠屏设备,UI 要支持可变化布局、分屏、多窗口。

性能密集型应用(游戏、图像处理)应考虑新的热管理 API,检查是否引入性能 hint。

媒体/摄像头应用要测试低光模式、闪光强度是否受影响。

12. Android 16 (API 级别 36)
✅ 重要新特性

引入或重构运行时 (ART) 性能和新 Java 特性支持。

支持更广泛的设备类型和用途,比如外接显示器、桌面模式增强。

强化隐私沙箱 (Privacy Sandbox)、健康记录 (FHIR 格式) 等新 API。

新 UI 设计趋势:如 Material 3 Expressive、强制自动主题图标、暗模式/图标适配(虽部分为后续 QPR 更新)


⚠ 适配要点

若 App 用到了蓝牙/媒体/外接显示器,则需测试在大屏幕、外接显示器环境下的交互、窗口适配。

若使用 Java / Kotlin 新特性或依赖 ART 行为,应测试在 Android 16 上的兼容性。

UI 元素(图标、主题、暗模式)应准备好适配系统自动图标主题、暗模式强制应用的场景。

健康、隐私相关功能(如医疗记录、用户敏感数据)需确认权限和用户同意流程是否变更。

💡 总结适配建议

权限变化是最大坑:运行时权限 / 后台权限 / 特定功能权限

后台限制:Oreo 后后台服务受限,必须用前台服务或 WorkManager

存储变化:Scoped Storage 后要改 MediaStore / SAF

蓝牙/网络:Android 12+ 权限更严格,Android 14+ BLE MTU 注意

UI:Material Design / 多窗口 / 手势导航 / 暗黑模式 / SplashScreen

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



//百度->gps
public static LatLng bdToWgs(LatLng bdLatLng) {
if (bdLatLng == null) return null;

// 百度坐标转高德坐标
double x = bdLatLng.longitude - 0.0065;
double y = bdLatLng.latitude - 0.006;
double z = Math.sqrt(x * x + y * y) - 0.00002 * Math.sin(y * Math.PI);
double theta = Math.atan2(y, x) - 0.000003 * Math.cos(x * Math.PI);
double gcjLon = z * Math.cos(theta);
double gcjLat = z * Math.sin(theta);

// 高德坐标转 GPS
return gcjToWgs(new LatLng(gcjLat, gcjLon));
}

// GCJ-02 → WGS84
public static LatLng gcjToWgs(LatLng gcjLatLng) {
if (gcjLatLng == null) return null;
double dLat = transformLat(gcjLatLng.longitude - 105.0, gcjLatLng.latitude - 35.0);
double dLon = transformLon(gcjLatLng.longitude - 105.0, gcjLatLng.latitude - 35.0);
double radLat = gcjLatLng.latitude / 180.0 * Math.PI;
double magic = Math.sin(radLat);
magic = 1 - 0.00669342162296594323 * magic * magic;
double sqrtMagic = Math.sqrt(magic);
dLat = (dLat * 180.0) / ((6378245.0 * (1 - 0.00669342162296594323)) / (magic * sqrtMagic) * Math.PI);
dLon = (dLon * 180.0) / (6378245.0 / sqrtMagic * Math.cos(radLat) * Math.PI);
double mgLat = gcjLatLng.latitude + dLat;
double mgLon = gcjLatLng.longitude + dLon;
return new LatLng(gcjLatLng.latitude * 2 - mgLat, gcjLatLng.longitude * 2 - mgLon);
}

private static double transformLat(double x, double y) {
double ret = -100.0 + 2.0 * x + 3.0 * y + 0.2 * y * y + 0.1 * x * y + 0.2 * Math.sqrt(Math.abs(x));
ret += (20.0 * Math.sin(6.0 * x * Math.PI) + 20.0 * Math.sin(2.0 * x * Math.PI)) * 2.0 / 3.0;
ret += (20.0 * Math.sin(y * Math.PI) + 40.0 * Math.sin(y / 3.0 * Math.PI)) * 2.0 / 3.0;
ret += (160.0 * Math.sin(y / 12.0 * Math.PI) + 320 * Math.sin(y * Math.PI / 30.0)) * 2.0 / 3.0;
return ret;
}

private static double transformLon(double x, double y) {
double ret = 300.0 + x + 2.0 * y + 0.1 * x * x + 0.1 * x * y + 0.1 * Math.sqrt(Math.abs(x));
ret += (20.0 * Math.sin(6.0 * x * Math.PI) + 20.0 * Math.sin(2.0 * x * Math.PI)) * 2.0 / 3.0;
ret += (20.0 * Math.sin(x * Math.PI) + 40.0 * Math.sin(x / 3.0 * Math.PI)) * 2.0 / 3.0;
ret += (150.0 * Math.sin(x / 12.0 * Math.PI) + 300.0 * Math.sin(x / 30.0 * Math.PI)) * 2.0 / 3.0;
return ret;
}


//GPS-百度

public static LatLng wgsToBd(LatLng sourceLatLng) {
if (sourceLatLng == null) {
return null;
}

CoordinateConverter converter = new CoordinateConverter();
converter.from(CoordinateConverter.CoordType.GPS);
converter.coord(sourceLatLng);

return converter.convert();
}




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
从 Linux 内核到应用层

引言

Android 不仅仅是一个应用开发平台,它是一个完整的操作系统,基于 Linux 内核,拥有丰富的系统服务和运行环境。理解 Android 系统架构对开发高性能应用、优化系统资源以及调试底层问题非常重要。

1. Android 系统架构概览

Android 系统可以分为 五层结构:

应用层(Applications)
应用框架层(Application Framework)
系统运行库(Libraries & Android Runtime)
硬件抽象层(HAL)
Linux 内核(Linux Kernel)

2. Linux 内核(Linux Kernel)

作用:

驱动管理:管理硬件设备,如摄像头、显示屏、传感器

进程管理:提供多任务调度

内存管理:虚拟内存与物理内存管理

安全与网络:权限控制、网络通信

特点:

Android 使用的通常是 定制化 Linux 内核

提供 Binder IPC 机制,支撑应用层与系统服务通信

3. 硬件抽象层(HAL)

作用:

将硬件接口统一抽象

各种硬件模块通过 HAL 与 Android 框架交互,例如摄像头 HAL、音频 HAL

特点:

每个硬件模块都有对应的 HAL 模块(Camera HAL、GPS HAL 等)

开发者无需直接访问底层驱动,系统调用 HAL 接口即可

4. 系统运行库(Libraries & Android Runtime)

组成:

C/C++ 系统库

如 libc、SurfaceFlinger、OpenGL ES 等

Android Runtime(ART)

Java/Kotlin 应用运行环境

负责将 APK 中的字节码转换为机器码(JIT/AOT 编译)

提供垃圾回收机制和多线程管理

特点:

ART 替代了早期的 Dalvik 虚拟机

运行效率更高,内存管理更智能

5. 应用框架层(Application Framework)

作用:

为应用提供丰富的 API 和系统服务

核心组件:

Activity Manager:管理应用生命周期

Package Manager:应用安装和权限管理

Window Manager:界面窗口管理

Content Providers:应用数据共享

Location Manager:位置服务

Notification Manager:通知服务

特点:

应用通过调用框架 API 获取系统服务

框架层负责调度底层 HAL 和内核资源

6. 应用层(Applications)

作用:

直接运行用户应用,包括系统应用和第三方应用

应用通过 Java/Kotlin + Android SDK 调用框架层功能

特点:

每个应用运行在独立的 进程和虚拟机实例 中

利用 Linux 内核的权限机制进行隔离和安全管理

7. 总结

Android 系统是一套复杂的多层架构,从内核到应用层形成完整生态

开发者理解系统架构可以帮助:

性能优化

系统服务调用理解

调试底层问题

核心概念:内核提供基础,HAL封装硬件,运行库执行应用,框架层提供服务,应用层直接面向用户


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
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
一、前言

在物联网设备开发中,蓝牙通信(Bluetooth Low Energy, BLE) 是最常见的无线数据传输方式之一。
很多 Android 项目(如健康手环、传感模块等)都需要实现 BLE 的扫描、连接、收发数据、断线重连等功能。

本文将分享一个我在项目中实际使用的 BLE 工具类 —— BluetoothBleUtil,
它将 Android 原生的蓝牙 API 封装成简单易用的接口,让开发者可以几行代码就完成 BLE 通信。

二、功能概述

本工具类实现了以下 BLE 功能模块:

功能 说明
蓝牙初始化 初始化 BluetoothAdapter、BluetoothManager
扫描设备 支持超时自动停止扫描
自动连接目标设备 根据设备名过滤并自动连接
读写 GATT 特征 支持发送指令与接收通知
断开与释放 断线自动清理,防止内存泄漏
重连功能 自动重连上次已连接设备
状态监听 支持 LiveData 与回调接口
三、核心架构设计

BluetoothBleUtil 使用 Kotlin 单例模式 (object) 设计,保证 BLE 连接全局唯一。
核心流程如下:

startScan() → onScanResult() → connect() → onServicesDiscovered()
↓ ↓
stopScan() enableNotify()
↓ ↓
sendBytes() ←→ onCharacteristicChanged()

四、代码实现详解

import android.annotation.SuppressLint
import android.bluetooth.*
import android.bluetooth.le.*
import android.content.Context
import android.os.Build
import android.os.Handler
import android.os.Looper
import android.util.Log
import androidx.lifecycle.MutableLiveData
import com.comm.library.utils.LogUtils
import com.comm.library.utils.SPUtils
import com.htnova.gasdetection.model.SpConstance
import java.util.*

/**
* 低功耗蓝牙
*/
@SuppressLint("MissingPermission")
object BluetoothBleUtil {

private const val TAG = "gasdetection"

// ------------------------- BLE参数配置 -------------------------
private const val DEVICE_NAME = "GCPID"
private val SERVICE_UUID = UUID.fromString("0000a002-0000-1000-8000-00805f9b34fb")
private val WRITE_UUID = UUID.fromString("0000c303-0000-1000-8000-00805f9b34fb")
private val NOTIFY_UUID = UUID.fromString("0000c305-0000-1000-8000-00805f9b34fb")

// ------------------------- 回调接口 -------------------------
interface Callback {
fun onScanStarted() {}
fun onDeviceFound(device: BluetoothDevice) {}
fun onScanFinished() {}
fun onConnected(device: BluetoothDevice) {}
fun onDisconnected(device: BluetoothDevice) {}
fun onMessageReceived(device: BluetoothDevice, data: ByteArray) {}
fun onError(msg: String) {}
}

// ------------------------- 内部状态 -------------------------
private var callback: Callback? = null
private lateinit var appContext: Context
private var adapter: BluetoothAdapter? = null
private var scanner: BluetoothLeScanner? = null
private var bluetoothGatt: BluetoothGatt? = null
private var writeCharacteristic: BluetoothGattCharacteristic? = null
private var notifyCharacteristic: BluetoothGattCharacteristic? = null
private val handler = Handler(Looper.getMainLooper())

private var isScanning = false
private var isConnected = false

val connectedDeviceLiveData = MutableLiveData<BluetoothDevice?>()

// ------------------------- 初始化 -------------------------
fun init(context: Context, cb: Callback) {
appContext = context.applicationContext
callback = cb
adapter = (context.getSystemService(Context.BLUETOOTH_SERVICE) as BluetoothManager).adapter
scanner = adapter?.bluetoothLeScanner
}

// ------------------------- 扫描 -------------------------
fun startScan(timeoutMs: Long = 8000) {
if (isScanning) return
if (adapter == null || !adapter!!.isEnabled) {
callback?.onError("蓝牙未开启")
return
}

// val filters = listOf(ScanFilter.Builder().setDeviceName(DEVICE_NAME).build())
val settings = ScanSettings.Builder().setScanMode(ScanSettings.SCAN_MODE_LOW_LATENCY).build()

isScanning = true
callback?.onScanStarted()
scanner?.startScan(null, settings, scanCallback)

handler.postDelayed({
stopScan()
}, timeoutMs)
}

fun stopScan() {
if (!isScanning) return
isScanning = false
try { scanner?.stopScan(scanCallback) } catch (_: Exception) {}
callback?.onScanFinished()
}

private val scanCallback = object : ScanCallback() {
override fun onScanResult(callbackType: Int, result: ScanResult) {
val device = result.device
callback?.onDeviceFound(device)
if (device.name == DEVICE_NAME) {
callback?.onDeviceFound(device)
stopScan()
connect(device)
}
}

override fun onScanFailed(errorCode: Int) {
callback?.onError("扫描失败: $errorCode")
isScanning = false
}
}

// ------------------------- 连接 -------------------------
fun connect(device: BluetoothDevice) {
Log.d(TAG, "正在连接: ${device.name} ${device.address}")
SPUtils.putString(SpConstance.BLUE_ADDRESS,device.address)
bluetoothGatt = device.connectGatt(appContext, false, gattCallback)
}

fun disconnect() {
isConnected = false
try { bluetoothGatt?.disconnect() } catch (_: Exception) {}
try { bluetoothGatt?.close() } catch (_: Exception) {}
bluetoothGatt = null
connectedDeviceLiveData.postValue(null)
}

// ------------------------- GATT回调 -------------------------
private val gattCallback = object : BluetoothGattCallback() {

override fun onConnectionStateChange(gatt: BluetoothGatt, status: Int, newState: Int) {
when (newState) {
BluetoothProfile.STATE_CONNECTED -> {
isConnected = true
connectedDeviceLiveData.postValue(gatt.device)
callback?.onConnected(gatt.device)
Log.d(TAG, "已连接,开始发现服务")
gatt.discoverServices()
}
BluetoothProfile.STATE_DISCONNECTED -> {
isConnected = false
connectedDeviceLiveData.postValue(null)
callback?.onDisconnected(gatt.device)
Log.d(TAG, "已断开: ${gatt.device.name}")
// try { bluetoothGatt?.close() } catch (_: Exception) {}
// bluetoothGatt = null
// connectedDeviceLiveData.postValue(null)
}
}
}

override fun onServicesDiscovered(gatt: BluetoothGatt, status: Int) {
if (status == BluetoothGatt.GATT_SUCCESS) {
val service = gatt.getService(SERVICE_UUID)
if (service != null) {
writeCharacteristic = service.getCharacteristic(WRITE_UUID)
notifyCharacteristic = service.getCharacteristic(NOTIFY_UUID)

// 启用通知
//注意:BLE 特征通知的描述符(Descriptor)一般是 Client Characteristic Configuration Descriptor (CCCD),UUID 固定为:
//00002902-0000-1000-8000-00805f9b34fb
//这是 BLE 规范规定的,用来启用特征通知或指示。
if (notifyCharacteristic != null) {
gatt.setCharacteristicNotification(notifyCharacteristic, true)
val descriptor = notifyCharacteristic!!.getDescriptor(
UUID.fromString("00002902-0000-1000-8000-00805f9b34fb")
)
descriptor?.let {
it.value = BluetoothGattDescriptor.ENABLE_NOTIFICATION_VALUE
gatt.writeDescriptor(it)
}
}
} else {
callback?.onError("未发现指定服务UUID")
}
}
}

override fun onCharacteristicChanged(
gatt: BluetoothGatt,
characteristic: BluetoothGattCharacteristic
) {
if (characteristic.uuid == NOTIFY_UUID) {
val data = characteristic.value
callback?.onMessageReceived(gatt.device, data)
}
}

override fun onCharacteristicWrite(
gatt: BluetoothGatt,
characteristic: BluetoothGattCharacteristic,
status: Int
) {
if (status != BluetoothGatt.GATT_SUCCESS) {
callback?.onError("写入失败: $status")
}
}
}

// ------------------------- 写数据 -------------------------
fun sendBytes(data: ByteArray) {
if (!isConnected || writeCharacteristic == null || bluetoothGatt == null) {
callback?.onError("未连接设备")
return
}
writeCharacteristic!!.value = data
val result = bluetoothGatt!!.writeCharacteristic(writeCharacteristic)
if (!result) {
callback?.onError("发送失败: writeCharacteristic返回false")
}
}

fun sendMessage(msg: String) = sendBytes(msg.toByteArray())

// ------------------------- 工具 -------------------------
fun isBluetoothEnabled(): Boolean = adapter?.isEnabled == true
fun enableBluetooth() = adapter?.enable()
fun disableBluetooth() = adapter?.disable()

fun release() {
stopScan()
disconnect()
callback = null
adapter = null
scanner = null
}

fun getConnectedBleDevices(): List<BluetoothDevice> {
val manager = appContext.getSystemService(Context.BLUETOOTH_SERVICE) as BluetoothManager
return manager.getConnectedDevices(BluetoothProfile.GATT)
}

//连接上次连接的
fun tryReconnectLastDevice() {
val address = SPUtils.getString(SpConstance.BLUE_ADDRESS,"") ?: return
val device = adapter?.getRemoteDevice(address)
if (device != null) {
Log.d(TAG, "尝试重连上次设备: ${device.address}")
// connect(device)
bluetoothGatt?.connect()
}
}
}

五、权限配置

Android 12+ 必须在 AndroidManifest.xml 中声明以下权限:

<uses-permission android:name="android.permission.BLUETOOTH" />
<uses-permission android:name="android.permission.BLUETOOTH_ADMIN" />
<uses-permission android:name="android.permission.BLUETOOTH_CONNECT" />
<uses-permission android:name="android.permission.BLUETOOTH_SCAN" />
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />


同时,在运行时动态申请:

if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
requestPermissions(arrayOf(
Manifest.permission.BLUETOOTH_CONNECT,
Manifest.permission.BLUETOOTH_SCAN
), 100)
}

🚀 Android 性能优化全攻略:从卡顿到丝滑的系统性思考

一、前言

性能优化,是一个 Android 开发者从“能写”到“写好”的分水岭。
在业务迭代和功能膨胀的背景下,App 卡顿、耗电、内存暴涨、启动慢等问题屡见不鲜。
本文结合实际项目经验,总结 Android 性能优化的核心思路与落地方案,帮助开发者构建一套系统化的优化思维。


二、性能优化的核心目标

优化的最终目的,不是“让分数变高”,而是提升用户体验。

在有限资源下,让应用运行得更快、更稳、更省。

主要指标包括:

  • 启动速度(冷启动、热启动)
  • 页面流畅度(帧率 / 掉帧率 / 渲染时间)
  • 内存占用(内存泄漏、对象复用、Bitmap管理)
  • CPU & 电量(过度计算、线程滥用)
  • 网络效率(请求合并、缓存策略、压缩传输)

三、启动优化:用户等待的第一印象

1️⃣ 延迟非关键初始化

很多 App 启动慢,根本原因在于 Application#onCreate 里做了太多事。

优化思路:

  • 把非关键初始化延迟到首屏渲染后(如延迟 2s 执行)
  • 例如:日志系统、推送、统计、广告 SDK 都可懒加载
new Handler(Looper.getMainLooper()).postDelayed(() -> {
    initPush();
    initAnalytics();
}, 2000);

2️⃣ 启动页轻量化

SplashActivity 只负责检查必要权限与路由逻辑,禁止做业务初始化

图片资源压缩至 WebP

避免复杂动画

尽量复用已加载资源

四、渲染优化:从掉帧到丝滑
1️⃣ 分析工具

使用 adb shell dumpsys gfxinfo 查看帧渲染耗时
每个帧耗时 <16ms 才能保持 60fps。

2️⃣ 优化建议

减少主线程阻塞(IO、网络、复杂运算应放后台)

使用 ViewStub、include、merge 优化布局层级

大量 RecyclerView Item 使用 DiffUtil 替代 notifyDataSetChanged()

动画尽量用 Lottie 或硬件加速的 PropertyAnimator

五、内存优化:让 App 更持久
1️⃣ 常见问题

静态持有 Context

Bitmap 未回收

Handler / Thread 未移除消息

监听器泄漏(RxJava / LiveData)

2️⃣ 调优工具

Android Profiler

LeakCanary(自动检测内存泄漏)

3️⃣ 示例
@Override
protected void onDestroy() {
    super.onDestroy();
    handler.removeCallbacksAndMessages(null);
    binding = null;
}

六、线程与异步优化

原则:CPU 要忙得刚刚好

避免线程池滥用,每个模块不要新建线程池

建议统一封装 ThreadPoolExecutor:

ExecutorService executor = new ThreadPoolExecutor(
        2, 4, 60, TimeUnit.SECONDS,
        new LinkedBlockingQueue<>(),
        new ThreadFactory() {
            @Override
            public Thread newThread(Runnable r) {
                Thread t = new Thread(r);
                t.setName("AppThreadPool-" + t.getId());
                return t;
            }
        });

七、网络优化

开启 HTTP 压缩(GZIP)

使用 OKHttp 缓存机制

合并请求、批量上传

图片加载用 Glide / Coil 并开启缓存策略:

Glide.with(context)
    .load(url)
    .diskCacheStrategy(DiskCacheStrategy.ALL)
    .into(imageView);

八、监控与持续优化

优化不是一次性行为,而是持续工程。

建议在项目中引入以下监控指标:

启动耗时监控

卡顿帧率监控(BlockCanary)

内存泄漏监控(LeakCanary)

崩溃收集(Firebase / Bugly)

持续监控 + 自动上报 + 定期分析,
才能让优化成为开发流程的一部分。

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
安卓 Handler 机制是 Android 中用于解决线程间通信和异步消息处理的核心机制,尤其在处理 UI 更新时至关重要。

核心概念与作用 💡
作用: 允许在非 UI 线程(如子线程)中向主线程(UI 线程)发送消息或执行任务,从而实现线程间通信和更新 UI(因为 Android 要求 UI 操作必须在主线程进行)。

核心原则: 异步通信和消息队列。


主要组成部分 🧱
Handler 机制主要由以下四个核心组件构成:

1. Handler(处理者)
作用: 负责向 MessageQueue 中发送 Message 或 Runnable,以及在关联的 Looper 接收到消息后,执行相应的处理逻辑(即 handleMessage() 方法)。

创建: Handler 实例会关联到创建它的线程的 Looper。通常在主线程创建,使其能处理主线程的消息。

2. Message(消息)
作用: 线程间传递的信息载体。可以携带数据(如通过 what、arg1、arg2、obj 字段)。

发送: 通过 Handler.sendMessage() 或 Handler.post() 方法发送。

3. MessageQueue(消息队列)
作用: 存储 Handler 发送的所有待处理的消息(Message 或 Runnable)。它是一个先进先出 (FIFO) 的队列。

特点: 每个线程最多只有一个 Looper,每个 Looper 会对应一个 MessageQueue。

4. Looper(循环器)
作用: 负责从 MessageQueue 中不断地取出消息,并将取出的消息分发给对应的 Handler 进行处理。

工作方式: 使用一个无限循环 (loop()) 来实现。

主线程 Looper: Android 应用启动时,系统会自动为主线程创建一个 Looper。

工作流程 🔄
一个完整的 Handler 流程如下:

发送消息: 在一个子线程(或其他线程)中,通过 Handler 的 sendMessage() 或 post() 方法将一个 Message 或 Runnable 放入 MessageQueue。

消息循环: 与 Handler 关联的线程(通常是主线程)的 Looper 开始工作,在无限循环中不断地从 MessageQueue 中取出消息。

消息分发: Looper 将取出的消息分发回给发送该消息的 Handler。

消息处理: Handler 接收到消息后,在其 handleMessage() 方法中执行具体的逻辑,完成任务(例如更新 UI)。


简单示例 📝
发送消息:

Java

// 在子线程中
new Thread(() -> {
// ... 执行耗时操作
Message msg = handler.obtainMessage(WHAT_CODE, data);
handler.sendMessage(msg); // 将消息发送到主线程的消息队列
}).start();
处理消息(在主线程 Handler 中):

Java

// 在主线程中定义 Handler
private final Handler handler = new Handler(Looper.getMainLooper()) {
@Override
public void handleMessage(@NonNull Message msg) {
super.handleMessage(msg);
if (msg.what == WHAT_CODE) {
// ... 在主线程中更新 UI
textView.setText("数据加载完成: " + msg.obj);
}
}
};

1
2
3
4
5
google()
mavenCentral()
maven { url 'https://www.jitpack.io' }
maven { url "https://repo.osgeo.org/repository/release/" }
maven { url 'https://maven.aliyun.com/repository/gradle-plugin' }

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
whereis nginx   查看nginx在哪个位置

http {
include mime.types;
default_type application/octet-stream;

server {
listen 80;
server_name codehelps.online; 配置域名


sudo /usr/local/nginx/sbin/nginx -s reload 重启nginx


nginx make install 后,目录在 /usr/local/nginx 下,/usr/local/src是源码


sudo 是 “superuser do” 的缩写,用来 以管理员(root)权限执行命令。在 Linux 或 macOS 系统中,普通用户默认没有权限修改系统文件或启动/停止服务,用 sudo 可以临时提升权限执行操作。

基本用法
sudo <命令>


例如:

编辑系统文件

sudo nano /etc/hosts


nano 是编辑器,修改 /etc/hosts 需要 root 权限,所以前面加 sudo。

安装软件

sudo apt install nginx # Ubuntu/Debian
sudo yum install nginx # CentOS/RedHat


重启服务

sudo systemctl restart nginx

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
1.在项目目录res/values/themes,修改为:
<resources xmlns:tools="http://schemas.android.com/tools">
<!-- Base application theme. -->
<style name="Base.Theme.HtGasDetection" parent="Theme.Material3.DayNight.NoActionBar">
<!-- Customize your light theme here. -->
<!-- <item name="colorPrimary">@color/my_light_primary</item> -->
<!-- 主色 -->
<item name="colorPrimary">@color/common_primary_color</item>
<item name="colorPrimaryVariant">@color/common_primary_dark_color</item>
<item name="colorOnPrimary">@color/common_text_color</item>

<!-- 状态栏透明 -->
<item name="android:statusBarColor">@android:color/transparent</item>
<item name="android:windowTranslucentStatus">true</item>
<!-- 内容延伸到状态栏 -->
<item name="android:fitsSystemWindows">false</item>

<!-- 可选:导航栏透明 -->
<item name="android:navigationBarColor">@android:color/transparent</item>

<!-- 默认文本颜色 -->
<item name="android:textColor">@color/common_text_color</item>
<item name="android:textSize">14sp</item>

<!-- 状态栏字体 -->
<item name="android:windowLightStatusBar">true</item>

</style>

<style name="Theme.HtGasDetection" parent="Base.Theme.HtGasDetection" />
</resources>
2.在AndroidManifest.xml中的application标签中设置:
android:theme="@style/Theme.HtGasDetection"
android:forceDarkAllowed="false"



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
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
安卓蓝牙分为:
经典蓝牙和低功耗蓝牙

经典蓝牙 BR/EDR (Bluetooth Basic Rate / Enhanced Data Rate) 面向流,数据吞吐高(~2~3 Mbps),功耗高 音频、打印机、串口设备、手柄、POS机
低功耗蓝牙 BLE (Bluetooth Low Energy / Bluetooth Smart) 面向属性(GATT),数据吞吐低(单次有效载荷 ≤ 20B 默认),功耗低 健康设备、IoT 传感器、可穿戴设

经典蓝牙

适合大数据流、串口通信。

需要配对才能连接。

读写阻塞,需独立线程或协程。

UUID 必须正确,否则 connect() 会失败。

异常:read failed, socket might closed or timeout 常因 UUID 错误、设备拒绝或被系统占用。

BLE

适合短小数据,低功耗应用。

扫描、连接、读写全异步。

单次数据量有限,需要分包处理大数据。

可以设置 MTU(requestMtu())增加单次传输量。

特征值通知(Notify/Indicate)是常用的数据推送方式。

不一定需要配对,但安全通信可使用加密。


蓝牙连接状态安卓系统机制没有给出,需自己获取判断:
1.通过反射方式调用隐藏 API(稳定但非官方公开):

fun isDeviceConnected(device: BluetoothDevice): Boolean {
return try {
val method = device.javaClass.getMethod("isConnected")
method.invoke(device) as Boolean
} catch (e: Exception) {
false
}
}


✅ 这个方法在 Android 6.0+ 都可用,实测能判断系统层面蓝牙是否连接成功(包括从“设置”界面连接的情况)。

2.监听系统广播(推荐结合使用)
val filter = IntentFilter().apply {
addAction(BluetoothDevice.ACTION_ACL_CONNECTED)
addAction(BluetoothDevice.ACTION_ACL_DISCONNECTED)
}
context.registerReceiver(receiver, filter)


配合反射 isConnected() 使用最准确。





import android.annotation.SuppressLint
import android.bluetooth.*
import android.content.*
import android.os.Handler
import android.os.Looper
import android.util.Log
import androidx.lifecycle.MutableLiveData
import java.io.IOException
import java.util.*

@SuppressLint("MissingPermission")
object BluetoothUtil {

private const val TAG = "BluetoothUtil"
private val SPP_UUID: UUID = UUID.fromString("00001101-0000-1000-8000-00805F9B34FB")

interface Callback {
fun onScanStarted() {}
fun onDeviceFound(device: BluetoothDevice) {}
fun onScanFinished() {}
fun onBondStateChanged(device: BluetoothDevice, state: Int) {}
fun onConnected(device: BluetoothDevice) {}
fun onDisconnected(device: BluetoothDevice) {}
fun onMessageReceived(device: BluetoothDevice, data: ByteArray) {}
fun onError(message: String) {}
}

// ------------------------- 内部状态 -------------------------
private var callback: Callback? = null
private var adapter: BluetoothAdapter? = BluetoothAdapter.getDefaultAdapter()
private var socket: BluetoothSocket? = null
private var currentDevice: BluetoothDevice? = null
private var connectThread: Thread? = null
private var readThread: Thread? = null
private var isConnecting = false
private var isConnected = false
private var stopReading = false
private lateinit var appContext: Context
private val handler = Handler(Looper.getMainLooper())

// 已连接设备状态
val connectedDevicesLiveData = MutableLiveData<MutableSet<BluetoothDevice>>(mutableSetOf())

// ------------------------- 扫描 -------------------------
private var discoveryReceiver: BroadcastReceiver? = null

fun init(context: Context, cb: Callback) {
appContext = context.applicationContext
callback = cb
adapter = BluetoothAdapter.getDefaultAdapter()
registerSystemConnectionReceiver()
}

fun startDiscovery() {
if (adapter == null || !adapter!!.isEnabled) {
callback?.onError("蓝牙未开启")
return
}
registerDiscoveryReceiver()
adapter?.bondedDevices?.forEach { callback?.onDeviceFound(it) }
adapter?.startDiscovery()
callback?.onScanStarted()
}

fun stopDiscovery() {
adapter?.takeIf { it.isDiscovering }?.cancelDiscovery()
discoveryReceiver?.let {
try { appContext.unregisterReceiver(it) } catch (_: Exception) {}
discoveryReceiver = null
}
callback?.onScanFinished()
}

private fun registerDiscoveryReceiver() {
if (discoveryReceiver != null) return
discoveryReceiver = object : BroadcastReceiver() {
override fun onReceive(context: Context, intent: Intent) {
when(intent.action){
BluetoothDevice.ACTION_FOUND -> {
intent.getParcelableExtra<BluetoothDevice>(BluetoothDevice.EXTRA_DEVICE)
?.let { callback?.onDeviceFound(it) }
}
BluetoothAdapter.ACTION_DISCOVERY_FINISHED -> {
callback?.onScanFinished()
try { appContext.unregisterReceiver(this) } catch (_: Exception){}
discoveryReceiver = null
}
BluetoothDevice.ACTION_BOND_STATE_CHANGED -> {
val device = intent.getParcelableExtra<BluetoothDevice>(BluetoothDevice.EXTRA_DEVICE)
val state = intent.getIntExtra(BluetoothDevice.EXTRA_BOND_STATE,-1)
device?.let { callback?.onBondStateChanged(it, state) }
}
}
}
}
val filter = IntentFilter().apply {
addAction(BluetoothDevice.ACTION_FOUND)
addAction(BluetoothAdapter.ACTION_DISCOVERY_FINISHED)
addAction(BluetoothDevice.ACTION_BOND_STATE_CHANGED)
}
appContext.registerReceiver(discoveryReceiver, filter)
}

// ------------------------- 配对 -------------------------
fun bondDevice(device: BluetoothDevice) {
try {
if (device.bondState == BluetoothDevice.BOND_NONE) {
device.javaClass.getMethod("createBond").invoke(device)
} else {
callback?.onBondStateChanged(device, device.bondState)
}
} catch (e: Exception) {
callback?.onError("配对失败: ${e.message}")
}
}

fun removeBond(device: BluetoothDevice) {
try {
device.javaClass.getMethod("removeBond").invoke(device)
} catch (e: Exception) {
callback?.onError("取消配对失败: ${e.message}")
}
}

// ------------------------- 连接 -------------------------
fun connect(device: BluetoothDevice) {
if (isConnecting || isConnected) return
isConnecting = true
currentDevice = device
stopDiscovery()

connectThread = Thread {
try {
val uuid = device.uuids?.firstOrNull()?.uuid ?: SPP_UUID
socket = device.createRfcommSocketToServiceRecord(uuid)
socket?.connect()

isConnected = true
isConnecting = false
addConnectedDevice(device)
callback?.onConnected(device)
startReading()
} catch (e: IOException) {
isConnecting = false
isConnected = false
callback?.onError("连接失败: ${e.message}")
disconnect()
}
}.apply { start() }
}

fun disconnect() {
stopReading = true
isConnected = false
try { socket?.close() } catch (_: IOException) {}
socket = null
currentDevice?.let {
removeConnectedDevice(it)
callback?.onDisconnected(it)
}
}

private fun startReading() {
stopReading = false
readThread = Thread {
try {
val buffer = ByteArray(1024)
val input = socket?.inputStream
while (!stopReading && input != null) {
val len = input.read(buffer)
if (len != -1) {
val data = buffer.copyOf(len)
currentDevice?.let { callback?.onMessageReceived(it, data) }
}
}
} catch (e: IOException) {
if (!stopReading) {
callback?.onError("读取异常: ${e.message}")
disconnect()
}
}
}.apply { start() }
}

fun sendMessage(msg: String) = sendBytes(msg.toByteArray())
fun sendBytes(data: ByteArray) {
if (!isConnected || socket == null) {
callback?.onError("未连接设备")
return
}
try {
socket?.outputStream?.apply {
write(data)
flush()
}
} catch (e: IOException) {
callback?.onError("发送失败: ${e.message}")
}
}

// ------------------------- 系统蓝牙状态 -------------------------
private var systemReceiver: BroadcastReceiver? = null

private fun registerSystemConnectionReceiver() {
if (systemReceiver != null) return
systemReceiver = object : BroadcastReceiver() {
override fun onReceive(context: Context?, intent: Intent?) {
val action = intent?.action ?: return
val device = intent.getParcelableExtra<BluetoothDevice>(BluetoothDevice.EXTRA_DEVICE) ?: return
when(action){
BluetoothDevice.ACTION_ACL_CONNECTED -> {
Log.d(TAG, "系统连接: ${device.name}")
addConnectedDevice(device)
callback?.onConnected(device)
}
BluetoothDevice.ACTION_ACL_DISCONNECTED -> {
Log.d(TAG, "系统断开: ${device.name}")
removeConnectedDevice(device)
callback?.onDisconnected(device)
}
}
}
}
val filter = IntentFilter().apply {
addAction(BluetoothDevice.ACTION_ACL_CONNECTED)
addAction(BluetoothDevice.ACTION_ACL_DISCONNECTED)
}
appContext.registerReceiver(systemReceiver, filter)
}

private fun addConnectedDevice(device: BluetoothDevice) {
val set = connectedDevicesLiveData.value ?: mutableSetOf()
set.add(device)
connectedDevicesLiveData.postValue(set)
}

private fun removeConnectedDevice(device: BluetoothDevice) {
val set = connectedDevicesLiveData.value ?: mutableSetOf()
set.remove(device)
connectedDevicesLiveData.postValue(set)
}

// ------------------------- 工具 -------------------------
fun isBluetoothEnabled(): Boolean = adapter?.isEnabled == true
fun enableBluetooth() { adapter?.enable() }
fun disableBluetooth() { adapter?.disable() }

fun release() {
stopDiscovery()
disconnect()
currentDevice = null
socket = null
systemReceiver?.let { try { appContext.unregisterReceiver(it) } catch (_: Exception){} }
systemReceiver = null
connectedDevicesLiveData.postValue(mutableSetOf())
}
}