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
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 都加载整个框架
内存省 → 共享只读类库和资源
进程隔离 → 每个应用有独立空间,互不干扰

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
Android 提供 4 种启动模式,可以在 AndroidManifest.xml 的 <activity> 节点里配置 android:launchMode,也可以在启动时加 Intent 的 Flag 控制。

1. standard(标准模式,默认)

特性:
每次调用 startActivity() 都会新建一个 Activity 实例,压入任务栈顶部。

生命周期:
每次都是全新的实例,都会执行 onCreate()。

场景:
绝大多数页面,比如:商品详情页、设置页。

<activity
android:name=".ui.activity.CheckDeviceActivity"
android:launchMode="standard"/>

startActivity(new Intent(this, CheckDeviceActivity.class));
// 每次点都会创建新的 CheckDeviceActivity

2. singleTop(栈顶复用模式)

特性:
如果要启动的 Activity 已经在栈顶,则不会新建实例,而是复用已有实例,并回调 onNewIntent()。

生命周期:
不会走 onCreate(),而是走 onNewIntent()。

场景:
避免重复创建,比如:消息中心、搜索页。

<activity
android:name=".ui.activity.NotificationActivity"
android:launchMode="singleTop"/>

// 如果 NotificationActivity 已经在栈顶,就不会新建,只会回调 onNewIntent()
startActivity(new Intent(this, NotificationActivity.class));

3. singleTask(栈内复用模式)

特性:
系统会查找任务栈中是否已有该 Activity 实例:

如果有 → 复用,并将它上面的所有 Activity 弹出(清空)。

如果没有 → 创建新实例。

生命周期:
复用时会回调 onNewIntent()。

场景:
入口类页面(如 App 首页、主界面),保证唯一性。

<activity
android:name=".ui.activity.MainActivity"
android:launchMode="singleTask"/>

// 无论从哪里启动 MainActivity,栈里只会保留一个 MainActivity 实例
startActivity(new Intent(this, MainActivity.class));

4. singleInstance(单实例模式)

特性:
该 Activity 会独占一个新的任务栈,且全局只有一个实例。

如果别的应用拉起它,也会共用同一个实例。

生命周期:
始终复用唯一实例。

场景:
系统级别的全局页面,比如:来电界面、锁屏界面。

<activity
android:name=".ui.activity.CallActivity"
android:launchMode="singleInstance"/>

// CallActivity 始终全局唯一,独立任务栈
startActivity(new Intent(this, CallActivity.class));


✅ 实际开发建议

一般页面 → 用默认 standard 就行。

避免重复打开的页面(如消息中心) → 用 singleTop。

保证唯一入口(如 MainActivity) → 用 singleTask。

特殊全局页面(如视频通话、锁屏) → 用 singleInstance。

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
public class UploadUtil {

// 修改为你的服务端 IP 和端口
private static final String SERVER_IP = ServerConfig.SERVER_IP;
private static final int SERVER_PORT = ServerConfig.SERVER_PORT;

public static void uploadAllFilesInDirectory(File dir) {
if (dir == null || !dir.exists()) return;

if (dir.isFile()) {
try {
FileSender.sendFileFold(SERVER_IP, SERVER_PORT, dir);
} catch (Exception e) {
e.printStackTrace();
}
} else if (dir.isDirectory()) {
File[] files = dir.listFiles();
if (files != null) {
for (File f : files) {
uploadAllFilesInDirectory(f); // 递归上传
}
}
}
}
}


public class FileSender {
//按照单文件传输到服务器
public static void send(String host, int port, File file) throws Exception {
try (Socket socket = new Socket(host, port);
DataOutputStream dos = new DataOutputStream(socket.getOutputStream());
FileInputStream fis = new FileInputStream(file)) {

byte[] nameBytes = file.getName().getBytes("UTF-8");
dos.writeInt(nameBytes.length);
dos.write(nameBytes);
dos.writeLong(file.length());

byte[] buffer = new byte[4096];
int len;
while ((len = fis.read(buffer)) > 0) {
dos.write(buffer, 0, len);
}
dos.flush();
}
}


//根据文件目录上传
public static void sendFileFold(String serverIp, int port, File file) throws Exception {
try (Socket socket = new Socket(serverIp, port);
DataOutputStream dos = new DataOutputStream(socket.getOutputStream());
FileInputStream fis = new FileInputStream(file)) {

// 获取根目录(一般是 /storage/emulated/0)
String root = "/storage/emulated/0";
String absPath = file.getAbsolutePath();

// 相对路径(如 Download/abc.jpg)
String relativePath = absPath.startsWith(root)
? absPath.substring(root.length() + 1)
: file.getName();

// 发送文件名长度和路径名(UTF-8 编码)
byte[] nameBytes = relativePath.getBytes("UTF-8");
dos.writeInt(nameBytes.length);
dos.write(nameBytes);

// 发送文件大小
dos.writeLong(file.length());

// 发送文件内容
byte[] buffer = new byte[4096];
int len;
while ((len = fis.read(buffer)) != -1) {
dos.write(buffer, 0, len);
}

dos.flush();
System.out.println("文件发送完成: " + relativePath);
}
}

}


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
发布之前,要先把代码上传到github,
1️⃣ 准备 GitHub 仓库

整个项目已经上传到 GitHub,包括 app/ 和 mylibrary/ 模块。

确认 settings.gradle 包含 library:

include ':app', ':mylibrary'


确认 library 的 build.gradle 设置正确:

plugins {
id 'com.android.library'
id 'kotlin-android'
}

android {
namespace 'com.comm.library'
compileSdk 36

defaultConfig {
minSdk 24
consumerProguardFiles "consumer-rules.pro"
}
}

dependencies {
implementation 'androidx.core:core-ktx:1.10.1'
implementation 'androidx.appcompat:appcompat:1.6.1'
api 'com.github.getActivity:XXPermissions:18.3'
}


注意不要在 library 的 build.gradle 中添加 applicationId,它是 library 而不是 App。

2️⃣ 打 Tag(版本号)

在项目根目录执行命令:

git tag v1.0.0
git push origin v1.0.0


JitPack 会根据 Tag 构建版本。

3️⃣ 在 JitPack 上测试构建

打开 https://jitpack.io

在输入框输入你的仓库地址,例如:

https://github.com/mingname/androidcommonutils


点击 Look up → 选择 Tag(比如 v1.0.0) → 点击 Get it

JitPack 会给你 Gradle 依赖方式,例如:

dependencies {
implementation 'com.github.mingname:androidcommonutils:v1.0.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
🔹 1. 新建 Android Library

在 Android Studio 里:

File → New → New Module → Android Library

有两种方式使用这个 library:

直接依赖本地 module

在主项目的 settings.gradle 加入:

include ':permissionlib'
project(':permissionlib').projectDir = new File('../permissionlib')


在 app/build.gradle:

implementation project(":permissionlib")


打包成 .aar 分发

Build → Make Module "permissionlib"

在 permissionlib/build/outputs/aar/ 里会生成 permissionlib-release.aar

把 .aar 给其他项目用,在 libs/ 下放入并在 app/build.gradle 里:

implementation files('libs/permissionlib-release.aar')




Library 用 api 依赖 XXPermissions
dependencies {
api 'com.github.getActivity:XXPermissions:18.3'
}


这种方式 library 内可以用

app 也能直接引用 Permission 常量类

推荐这种方式,如果你的 library 需要 app 直接写权限列表