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
安卓 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())
}
}


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
import http from '@ohos.net.http';

async function login() {
let httpRequest = http.createHttp();

try {
let response = await httpRequest.request(
'https://www.baidu.com/access/auth/login',
{
method: http.RequestMethod.POST,
header: {
'Content-Type': 'application/json'
},
extraData: JSON.stringify({
username: 'admin',
password: '123456'
})
}
);

console.info('Response Code:', response.responseCode);
console.info('Response Data:', response.result.toString());

// 处理响应数据
let result = JSON.parse(response.result.toString());
if (result.code === 200) {
console.info('登录成功');
} else {
console.info('登录失败:', result.message);
}
} catch (err) {
console.error('请求失败:', err);
} finally {
httpRequest.destroy();
}
}

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
1️⃣ 什么是 JNI

JNI(Java Native Interface)是 Java 调用 C/C++ 代码 的接口。
在 Android 上,JNI 通常用于:

大量数学运算、图像处理、音视频编解码、机器学习推理等 视频滤镜、OpenCV 图像处理
比如操作蓝牙、摄像头底层驱动、DSP 加速等

调用现有 C/C++ 库

访问底层硬件或系统 API(超出 Java 层能力)

Android 本身用 Java 访问底层功能不方便,或者第三方库只提供 C 接口。
典型例子:BaiduMap、腾讯 IM、游戏引擎。


2️⃣ JNI 的基本流程
步骤 1:创建 native 方法

在 Java/Kotlin 中声明一个 native 方法:

public class NativeLib {
static {
System.loadLibrary("mylib"); // 加载 .so
}

// 声明 native 方法
public native int addNumbers(int a, int b);
}

步骤 2:生成 C/C++ 函数签名

JNI 方法在 C/C++ 层定义时,需要 特定签名:

#include <jni.h>

extern "C"
JNIEXPORT jint JNICALL
Java_com_example_myapp_NativeLib_addNumbers(JNIEnv *env, jobject thiz, jint a, jint b) {
return a + b;
}


JNIEXPORT 和 JNICALL 是宏,确保函数正确导出

JNIEnv* 是 JNI 环境指针,用于调用 Java 方法或访问对象

jobject thiz 对应 Java 的实例对象(非静态方法)

参数类型对应 jint, jstring, jobject 等 JNI 类型

步骤 3:构建 .so 库

使用 CMake 或 ndk-build:

CMake 示例(CMakeLists.txt):

cmake_minimum_required(VERSION 3.4.1)

add_library(mylib SHARED native-lib.cpp)

find_library(log-lib log)

target_link_libraries(mylib ${log-lib})


然后在 build.gradle 中集成:

externalNativeBuild {
cmake {
path "CMakeLists.txt"
}
}

步骤 4:调用
NativeLib lib = new NativeLib();
int sum = lib.addNumbers(3, 5);
Log.d("JNI", "sum = " + sum);

3️⃣ 常用 JNI 类型映射
Java 类型 JNI 类型
int jint
long jlong
float jfloat
double jdouble
boolean jboolean
byte[] jbyteArray
String jstring
Object jobject
int[] jintArray
4️⃣ JNI 常用函数

字符串转换

const char* str = env->GetStringUTFChars(jstringObj, 0);
// 使用完释放
env->ReleaseStringUTFChars(jstringObj, str);


数组操作

jint *arr = env->GetIntArrayElements(jintArrayObj, 0);
// 操作数组
env->ReleaseIntArrayElements(jintArrayObj, arr, 0);


调用 Java 方法

jclass cls = env->GetObjectClass(obj);
jmethodID mid = env->GetMethodID(cls, "callback", "(I)V");
env->CallVoidMethod(obj, mid, 100);

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
保活的核心思路

Android 限制后台运行,但以下机制能最大限度保活:

启动一个前台服务(Foreground Service)
系统认为“前台服务 = 用户主动任务”,不会轻易杀。

应用被杀后,可通过 BOOT_COMPLETED 或 WORKMANAGER 自动重启



创建service
/**
* 前台保活服务 + 告警通知
*/
class KeepAliveService : Service() {

override fun onCreate() {
super.onCreate()
startForegroundNotification()
}

private fun startForegroundNotification() {
val channelId = "keep_alive_channel"
val channelName = "后台保活服务"

if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
val channel = NotificationChannel(
channelId,
channelName,
NotificationManager.IMPORTANCE_LOW
)
getSystemService(NotificationManager::class.java)?.createNotificationChannel(channel)
}

val notification = NotificationCompat.Builder(this, channelId)
.setContentTitle("应用后台运行中")
.setContentText("保持应用活动")
.setSmallIcon(R.mipmap.logo) // 替换成你的图标
.setOngoing(true)
.build()

startForeground(1001, notification)
}

override fun onBind(intent: Intent?): IBinder? = null
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
// 保活策略
return START_STICKY
}
companion object {
/**
* 启动前台保活服务
*/
fun start(context: Context) {
val intent = Intent(context, KeepAliveService::class.java)
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
context.startForegroundService(intent)
} else {
context.startService(intent)
}
}

/**
* 发送告警通知
*/
fun showAlarmNotification(context: Context, title: String, content: String) {
// Android 13+ 需要检查通知权限
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
if (ContextCompat.checkSelfPermission(context, Manifest.permission.POST_NOTIFICATIONS)
!= PackageManager.PERMISSION_GRANTED
) {
// 没权限,直接返回
return
}
}

val channelId = "alarm_channel"

if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
val channel = NotificationChannel(
channelId,
"告警通知",
NotificationManager.IMPORTANCE_HIGH
)
context.getSystemService(NotificationManager::class.java)?.createNotificationChannel(channel)
}

// 创建点击通知跳转的 Intent
val intent = Intent(context, NotificationActivity::class.java) // 替换为你要跳转的 Activity
intent.flags = Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TOP
val pendingIntent = PendingIntent.getActivity(
context,
0,
intent,
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S)
PendingIntent.FLAG_IMMUTABLE
else
0
)

val notification = NotificationCompat.Builder(context, channelId)
.setContentTitle(title)
.setContentText(content)
.setSmallIcon(R.mipmap.logo)
.setAutoCancel(true)
.setPriority(NotificationCompat.PRIORITY_HIGH)
.setContentIntent(pendingIntent)
.build()

NotificationManagerCompat.from(context).notify(System.currentTimeMillis().toInt(), notification)
}
}
}

添加权限
<uses-permission android:name="android.permission.POST_NOTIFICATIONS" />
<uses-permission android:name="android.permission.FOREGROUND_SERVICE"/>


配置service
<service
android:name=".service.KeepAliveService"
android:enabled="true"
android:exported="false"
android:foregroundServiceType="connectedDevice|dataSync" />

启动服务
// 启动前台保活服务
KeepAliveService.start(this)
KeepAliveService.showAlarmNotification(
this@CottonApplication,
"",
""
)


进阶保活方案

开机自启

<receiver android:name=".receiver.BootReceiver" android:enabled="true" android:exported="true">
<intent-filter>
<action android:name="android.intent.action.BOOT_COMPLETED" />
</intent-filter>
</receiver>

class BootReceiver : BroadcastReceiver() {
override fun onReceive(context: Context, intent: Intent) {
if (intent.action == Intent.ACTION_BOOT_COMPLETED) {
val serviceIntent = Intent(context, MqttForegroundService::class.java)
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
context.startForegroundService(serviceIntent)
} else {
context.startService(serviceIntent)
}
}
}
}


自恢复(守护)

可以每隔一段时间用 WorkManager 检查服务是否存活。

若服务被杀死则自动重启。

应用白名单

提醒用户在系统设置中“电池优化”里将你的应用加入白名单。

例如华为/小米设备,可在首次启动时引导用户开启。


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
docker 启动容器  docker start nginx-rtmp
docker 检查状态 docker ps
docker 检查日志 docker logs nginx-rtmp
docker 停止容器 docker stop nginx-rtmp
docker 重启容器 docker restart nginx-rtmp

服务器: rtmp://localhost:1935/live
流密钥: 任意名称(如 test)
播放地址

RTMP: rtmp://localhost:1935/live/流密钥
HLS: http://localhost:5080/hls/流密钥.m3u8
状态页面: http://localhost:5080/stat


使用 tiangolo/nginx-rtmp 镜像

docker run -d \
-p 1935:1935 \
-p 8080:80 \
--name nginx-rtmp \
tiangolo/nginx-rtmp
使用 alfg/nginx-rtmp 镜像

docker run -d \
-p 1935:1935 \
-p 80:80 \
--name rtmp-server \
alfg/nginx-rtmp
测试 RTMP 服务器

1. 检查服务器状态

# 查看容器运行状态
docker ps

# 查看日志
docker logs nginx-rtmp
2. 推流测试

使用 OBS Studio 或其他推流工具:

服务器: rtmp://localhost/live
流密钥: 任意名称(如 test)
完整的推流 URL:rtmp://localhost/live/test

3. 拉流测试

使用 VLC 或其他播放器:

URL: rtmp://localhost/live/test
4. 查看状态页面

访问 http://localhost/stat 查看服务器状态和活动流。

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
安卓 启动流程 可以分成两个角度看:

系统层面:点击桌面图标 → 应用进程启动

应用层面:Application → Activity 启动

我给你按顺序拆开讲 👇

1️⃣ 系统层面(从桌面图标开始)

用户点击应用图标

Launcher(桌面应用)根据 Manifest 里带有

<intent-filter>
<action android:name="android.intent.action.MAIN"/>
<category android:name="android.intent.category.LAUNCHER"/>
</intent-filter>


的 Activity(一般是 LoginActivity / MainActivity)来启动。

Launcher 通过 AMS(ActivityManagerService)发起启动请求

发送一个 startActivity() 调用给系统的 ActivityManagerService。

Zygote 进程 fork 新进程

如果应用进程还没启动,AMS 会通过 Zygote fork 出一个新的应用进程。

新进程里第一个类是 ActivityThread,它是应用的主线程(UI 线程)。

加载应用代码

ActivityThread 通过 Binder IPC 通知 AMS:进程已启动,可以加载应用的入口组件。

系统会调用 Application.onCreate() → 然后再启动目标 Activity。

2️⃣ 应用层面(进程内)

Application 初始化

Application.attachBaseContext()

Application.onCreate()(初始化 SDK、日志、三方库等)

Activity 启动

Activity.attach() → 绑定窗口

Activity.onCreate()(初始化视图、数据)

Activity.onStart()

Activity.onResume()(进入前台,可交互)

👉 这时候用户看到的就是你的界面。

3️⃣ 简化流程图
点击图标

Launcher 发送 Intent 给 AMS

AMS 检查应用进程是否存在
├─ 否 → Zygote fork 进程 → 启动 ActivityThread
└─ 是 → 直接用已有进程

加载 Application

启动目标 Activity (onCreate → onStart → onResume)

用户看到界面

4️⃣ 举个例子

比如你点开 微信 图标:

Launcher 找到 MainActivity 的入口声明(MAIN + LAUNCHER)。

AMS 检查进程是否在 → 不在就 fork 新进程。

Application 初始化,SDK 注册(如微信的 MMKV、本地数据库、日志等)。

MainActivity 执行生命周期,显示界面。



再比如:Zygote是框架,每个点击app图标,只是给app一个空间,公用Zygote提供的框架。
Zygote (公共图书馆)
├─ 预加载 Android 框架类、资源(共享)
└─ fork → 每个 App 有自己的房间
├─ Application 实例
├─ Activity 实例
└─ 私有数据
好处

启动快 → 不用每个 App 都加载整个框架
内存省 → 共享只读类库和资源
进程隔离 → 每个应用有独立空间,互不干扰