SDK 与 API版本
Android build 中的 Java 版本 - https://developer.android.google.cn/build/jdks?hl=zh_cn 平台代号、版本、API 级别和 NDK 版本 - https://source.android.google.cn/docs/setup/about/build-numbers?hl=zh-cn#platform-code-names-versions-api-levels-and-ndk-releases 维基百科 Android - https://zh.wikipedia.org/wiki/Android
| 名称 | 版本号 | API等级 | 发行日期 | 安全性更新状态 |
|---|---|---|---|---|
| Android 1.0 | 1.0 | 1 | 2008年9月23日 | 不支持 |
| Android 1.1 | 1.1 | 2 | 2009年2月9日 | 不支持 |
| Android Cupcake | 1.5 | 3 | 2009年4月27日 | 不支持 |
| Android Donut | 1.6 | 4 | 2009年9月15日 | 不支持 |
| Android Eclair | 2.0 – 2.1 | 5 – 7 | 2009年10月26日 | 不支持 |
| Android Froyo | 2.2 – 2.2.3 | 8 | 2010年5月20日 | 不支持 |
| Android Gingerbread | 2.3 – 2.3.7 | 9 – 10 | 2010年12月6日 | 不支持 |
| Android Honeycomb | 3.0 – 3.2.6 | 11 – 13 | 2011年2月22日 | 不支持 |
| Android Ice Cream Sandwich | 4.0 – 4.0.4 | 14 – 15 | 2011年10月18日 | 不支持 |
| Android Jelly Bean | 4.1 – 4.3.1 | 16 – 18 | 2012年7月9日 | 不支持 |
| Android KitKat | 4.4 – 4.4.4 | 19 – 20 | 2013年10月31日 | 不支持 |
| Android Lollipop | 5.0 – 5.1.1 | 21 – 22 | 2014年11月12日 | 不支持 |
| Android Marshmallow | 6.0 – 6.0.1 | 23 | 2015年10月5日 | 不支持 |
| Android Nougat | 7.0 – 7.1.2 | 24 – 25 | 2016年8月22日 | 不支持 |
| Android Oreo | 8.0 – 8.1 | 26 – 27 | 2017年8月21日 | 不支持 |
| Android Pie | 9 | 28 | 2018年8月6日 | 不支持 |
| Android 10 | 10 | 29 | 2019年9月3日 | 不支持 |
| Android 11 | 11 | 30 | 2020年9月8日 | 不支持 |
| Android 12 | 12 – 12L | 31–32 | 2021年10月4日 | 不支持 |
| Android 13 | 13 | 33 | 2022年8月15日 | 支持 |
| Android 14 | 14 | 34 | 2023年10月4日 | 支持 |
| Android 15 | 15 | 35 | 2024年10月15日 | 支持 |
| Android 16 | 16 | 36 | 2025年6月10日 | 支持 |
- 关于ABI ( Application Binary Interface) 说明
| ABI 名称 | 对应 CPU |
|---|---|
| arm64-v8a | 表示第 8 代 64 位 ARM 处理器 |
| armeabi-v7a | 表示第 7 代及以上 32 位 ARM 处理器 |
| armeabi | 表示第 5 代和第 6 代 32 位 ARM 处理器 |
| x86-64 | 表示 Intel 64 位处理器(主要平板和虚拟机使用) |
| x86 | 表示 Intel 32 位处理器(主要平板和虚拟机使用) |
Gradlex SDK 版本配置
https://developer.android.google.cn/ndk/guides/sdk-versions?hl=zh_cn
compileSdkVersion
需要强调的是 修改 compileSdkVersion 不会改变运行时的行为 。当你修改了 compileSdkVersion 的时候,可能会出现新的编译警告、编译错误,但新的 compileSdkVersion 不会被包含到 APK 中:它纯粹只是在编译的时候使用。(你真的应该修复这些警告,他们的出现一定是有原因的)
因此我们强烈推荐 总是使用最新的 SDK 进行编译 。
minSdkVersion
定义应用程序支持的最低API版本
targetSdkVersion
targetSdkVersion 是 Android 提供向前兼容的主要依据 一个targetSdkVersion的属性值表示创建的Android项目使用哪个API版本,一个API版本使用一个整型数字表示,API的全称是Application Programming Interface,即应用程序编程接口,API 19对应的编程接口和API 23定义的编程接口存在差别,因为使用整型数字表示targetSdkVersion的属性值,容易记忆和便于比较它们之间的大小,高版本API编程接口可以兼容低版本API编程接口,反之则不行。
一个 NDK 调用示例
参考 N_Cmake
CMakeLists.txt
# For more information about using CMake with Android Studio, read the
# documentation: https://d.android.com/studio/projects/add-native-code.html.
# For more examples on how to use CMake, see https://github.com/android/ndk-samples.
# Sets the minimum CMake version required for this project.
cmake_minimum_required(VERSION 3.22.1)
# Declares the project name. The project name can be accessed via ${ PROJECT_NAME},
# Since this is the top level CMakeLists.txt, the project name is also accessible
# with ${CMAKE_PROJECT_NAME} (both CMake variables are in-sync within the top level
# build script scope).
project("pathplaning")
# 设置 C++ 标准和相关标志
set(CMAKE_CXX_STANDARD 14)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -stdlib=libc++ -Wall -Wextra")
#set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -stdlib=libc++")
# boost_1_74_0 和 rapidjson 都是包含头文件就能用
set(BOOST_ROOT ${CMAKE_CURRENT_SOURCE_DIR}/boost_1_74_0)
set(RAPIDJSON_ROOT ${CMAKE_CURRENT_SOURCE_DIR}/rapidjson)
# 设置头文件目录
include_directories(
${BOOST_ROOT}
${RAPIDJSON_ROOT}
)
# 生成 静态库、动态库或对象库
add_library(${CMAKE_PROJECT_NAME} SHARED
pathplan/canvas.cpp
pathplan/dubins.cpp
pathplan/line.cpp
pathplan/point.cpp
pathplan/polygon.cpp
pathplan/MainWindow.cpp
android_interface.cpp)
# 链接库和依赖项
target_link_libraries(${CMAKE_PROJECT_NAME}
android
log)
主要在
- include_directories 设置头文件的目录
- add_library 这项目的目标; 生成静态库或动态库, 这里是 SHARED 动态库
- target_link_libraries 链接 android 和 log
android_interface.cpp
注意函数名是有规则的 Java_{包名}_{类名}_{函数名}
#include <jni.h>
#include <string>
#include "rapidjson/document.h"
#include "rapidjson/stringbuffer.h"
#include "rapidjson/writer.h"
#include <boost/date_time/gregorian/gregorian.hpp>
#include <boost/multiprecision/number.hpp>
#include "pathplan/MainWindow.h"
extern "C"
JNIEXPORT jstring JNICALL
Java_cn_simae_tny_njzj_web_action_PathAction_planning(JNIEnv *env, jobject thiz, jstring input) {
const char* p_input_char = env->GetStringUTFChars(input, nullptr);
rapidjson::Document doc;
doc.Parse(p_input_char);
if(doc.HasParseError()) {
env->ReleaseStringUTFChars(input, p_input_char);
return env->NewStringUTF("{\"msg\": \"Parse Error\"}");
}
// 如果 MW.path_obs 为空
rapidjson::Document output;
//空字符串
std::string obs_path_area = doc["obs_path_area"].GetString();
if(obs_path_area.empty() || obs_path_area.size() < 20) {
//无障碍规划
output = Single_Machine_Planner_Without_Obstacle_path_planner(doc);
}else{
output = Single_Machine_Planner_With_Obstacle_path_planner(doc);
}
auto inputString = std::string(p_input_char);
// 序列化 Document 为字符串
rapidjson::StringBuffer buffer;
rapidjson::Writer<rapidjson::StringBuffer> writer(buffer);
output.Accept(writer);
std::string outputString = buffer.GetString();
//
env->ReleaseStringUTFChars(input, p_input_char);//释放
std::string message = "planning: input = " + inputString + ", output = " + outputString;
std::cout << message << std::endl;
return env->NewStringUTF(outputString.c_str());//返回字符串
}
PathAction.kt
package cn.simae.tny.njzj.web.action
import android.content.Intent
import cn.simae.tny.njzj.activity.QRCodeActivity
import cn.simae.tny.njzj.utils.JacksonUtils
import cn.simae.tny.njzj.web.WebActivity
import cn.simae.tny.njzj.web.dto.InteractiveMessage
import cn.simae.tny.njzj.web.inter.CoroutinesProcess
import cn.simae.tny.njzj.web.js.WebInteractiveMediation
import pub.devrel.easypermissions.EasyPermissions
import pub.devrel.easypermissions.PermissionRequest
import java.util.Map
/**
* @description
*/
class PathAction(private val wim: WebInteractiveMediation): CoroutinesProcess<InteractiveMessage> {
// 区域规划算法模块
companion object {
init {
System.loadLibrary("pathplaning")
}
}
external fun planning(paths: String): String
}gradle.build.kt
plugins {
id("com.android.application")
id("org.jetbrains.kotlin.android")
id("com.google.devtools.ksp")
}
android {
namespace = "cn.simae.tny.njzj"
compileSdk = 35
defaultConfig {
applicationId = "cn.simae.tny.njzj"
minSdk = 29
targetSdk = 34
versionCode = 1
versionName = "1.3.7"
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
vectorDrawables {
useSupportLibrary = true
}
ndk {
// ABI 过滤
// abiFilters.addAll(listOf( "arm64-v8a","armeabi-v7a", "x86_64", "x86"))
abiFilters.addAll(listOf( "arm64-v8a", "x86_64"))
}
externalNativeBuild {
cmake {
cppFlags += ""
arguments += "-DCMAKE_VERBOSE_MAKEFILE:BOOL=ON"
}
}
}
....
// NDK
externalNativeBuild {
cmake {
path = file("src/main/cpp/CMakeLists.txt")
version = "3.22.1"
}
}
}
dependencies {
}CPP 本地调用 Java
Java 定义
// 在 com/example/MyJniHelper.kt 中
class MyJniHelper(private val context: Context) {
// 要调用的实例方法
fun showToast(message: String) {
Toast.makeText(context, message, Toast.LENGTH_SHORT).show()
}
// 要调用的静态方法
companion object {
@JvmStatic
fun logDebug(tag: String, message: String) {
Log.d(tag, message)
}
}
// 加载 native 库
init {
System.loadLibrary("my-native-lib")
}
// 声明 native 方法
external fun nativeInit()
}
本地代码
本地调用需要有 JNIEnv指针(env)和jobject实例(如果调用实例方法)
// cpp/native-lib.cpp
#include <jni.h>
#include <string>
// 全局引用缓存
jobject gJniHelperInstance = nullptr;
jobject gContext = nullptr;
extern "C" JNIEXPORT void JNICALL
Java_com_example_MyJniHelper_nativeInit(
JNIEnv* env,
jobject thiz, // MyJniHelper 实例
jobject context // 传入的 Context
) {
// 缓存实例和 Context
gJniHelperInstance = env->NewGlobalRef(thiz);
gContext = env->NewGlobalRef(context);
}
// 调用 Java 方法的辅助函数
void callJavaMethod(JNIEnv* env, const char* message) {
if (!gJniHelperInstance) return;
// 1. 获取类引用
jclass clazz = env->GetObjectClass(gJniHelperInstance);
// 2. 获取方法 ID
jmethodID method = env->GetMethodID(
clazz,
"showToast",
"(Ljava/lang/String;)V"
);
// 3. 创建 Java 字符串
jstring jMessage = env->NewStringUTF(message);
// 4. 调用实例方法
env->CallVoidMethod(gJniHelperInstance, method, jMessage);
// 5. 清理局部引用
env->DeleteLocalRef(jMessage);
env->DeleteLocalRef(clazz);
}
// 调用静态方法
void callStaticJavaMethod(JNIEnv* env) {
// 1. 获取类引用 (使用完整类路径)
jclass clazz = env->FindClass("com/example/MyJniHelper");
// 2. 获取静态方法 ID
jmethodID method = env->GetStaticMethodID(
clazz,
"logDebug",
"(Ljava/lang/String;Ljava/lang/String;)V"
);
// 3. 创建参数
jstring tag = env->NewStringUTF("JNI");
jstring msg = env->NewStringUTF("Called from C++");
// 4. 调用静态方法
env->CallStaticVoidMethod(clazz, method, tag, msg);
// 5. 清理局部引用
env->DeleteLocalRef(tag);
env->DeleteLocalRef(msg);
env->DeleteLocalRef(clazz);
}
全局引用
关于 gJniHelperInstance = env->NewGlobalRef(thiz); 在 JNI 中,默认传递的参数是局部引用:
- 局部引用只在当前 JNI 函数调用期间有效
- 当 JNI 函数返回时,局部引用会自动失效
- 如果尝试在后续调用中使用局部引用,会导致崩溃或未定义行为
// 当不再需要时释放全局引用
env->DeleteGlobalRef(gJniHelperInstance);
gJniHelperInstance = nullptr;调用入口
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
val jniHelper = MyJniHelper(this)
jniHelper.nativeInit() // 初始化 JNI 环境
}
}GStreamer
基于 web view 混合开发, 1. H5原生不支持 RTSP, 必须转换, 2. 前端低延迟最好的方案是WebRTC. FFmpeg 不支持WebRTC 需要自己转包; 有个开源库 GStreamer 支持安卓平台, 对于WebRTC的支持 略好于 FFmpeg…
官页 - https://gstreamer.freedesktop.org/ github 仓库 - https://github.com/GStreamer/gstreamer
实例
Download GStreamer
https://gstreamer.freedesktop.org/download/#android
The Android NDKs used by our stable releases are:
| GStreamer version | NDK Version |
|---|---|
| 1.26.x | r25c |
| 1.24.x | r25c |
| 1.22.x | r21 |
| 1.20.x | r21 |
| 1.18.x | r21 |
| 1.16.x | r18b |
The Android APIs targeted by our stable release(s) are:
| Architecture | API Targeted (GStreamer ⇐ 1.20) | API Targeted (GStreamer >= 1.22) |
|---|---|---|
| armv7 | v16 (Jelly Bean) | v21 (Lollipop) |
| x86 | v16 (Jelly Bean) | v21 (Lollipop) |
| arm64 | v21 (Lollipop) | v21 (Lollipop) |
| x86_64 | v21 (Lollipop) | v21 (Lollipop) |
Installing for Android development
In the process of building GStreamer-enabled Android applications, some tools will need to know where you installed the GStreamer binaries. You must define an environment variable called GSTREAMER_ROOT_ANDROID and point it to the folder where you extracted the GStreamer binaries. This environment variable must be available at build time, so maybe you want to make it available system-wide by adding it to your ~/.profile file (on Linux and Mac) or to the Environment Variables in the System Properties dialog (on Windows).
参考
官方示例是基于 Android.mk 管理, 非常旧的 仅参考:
gstreamer-android-samples https://github.com/AndyHa23/gstreamer-android-samples
gstreamer-android-tutorials - https://gstreamer.freedesktop.org/documentation/tutorials/android/link-against-gstreamer.html?gi-language=c
gstreamer-android
基于CMake 管理的 可参考 android-samples - https://github.com/henkeldi/gstreamer-android https://github.com/henkeldi/gstreamer-android/blob/master/app/src/main/cpp/CMakeLists.txt
CMakeLists.txt
cmake_minimum_required(VERSION 3.22.1)
project("gstreamer_cmake")
set(GSTREAMER_ROOT_ANDROID D:/libarry/gstreamer-1.0-android-universal-1.26.5)
message("API 架构是: ${ANDROID_ABI}")
# 根据目标架构设置GStreamer路径
if(${ANDROID_ABI} STREQUAL "armeabi-v7a")
set(GSTREAMER_ROOT ${GSTREAMER_ROOT_ANDROID}/armv7)
elseif(${ANDROID_ABI} STREQUAL "arm64-v8a")
set(GSTREAMER_ROOT ${GSTREAMER_ROOT_ANDROID}/arm64)
elseif(${ANDROID_ABI} STREQUAL "x86")
set(GSTREAMER_ROOT ${GSTREAMER_ROOT_ANDROID}/x86)
elseif(${ANDROID_ABI} STREQUAL "x86_64")
set(GSTREAMER_ROOT ${GSTREAMER_ROOT_ANDROID}/x86_64)
endif()
# 检查 gstreamer 库的路径定义
if(NOT GSTREAMER_ROOT)
message(FATAL_ERROR "GSTREAMER_ROOT environment variable is not set")
endif()
# 设置 GStreamer 库的 链接路径
set(GSTREAMER_LIB_DIR ${GSTREAMER_ROOT}/lib)
## 头文件目录
include_directories(
${GSTREAMER_ROOT}/include/gstreamer-1.0
${GSTREAMER_ROOT}/include/glib-2.0
${GSTREAMER_ROOT}/lib/glib-2.0/include)
# 设置 <链接库搜索目录> 相当于gcc的-L参数
link_directories(
${GSTREAMER_ROOT}/lib
${GSTREAMER_ROOT}/lib/gstreamer-1.0)
# 设置链接库
SET(GST_LIBS gstreamer-1.0 gmodule-2.0 gobject-2.0 glib-2.0 iconv intl ffi)
SET(GST_PLUGINS_CORE_LIBS gstaudioresample gstaudiotestsrc gstaudioconvert)
SET(GST_PLUGINS_BASE gstautodetect gstallocators-1.0 orc-test-0.4 gstapp-1.0 gstaudio-1.0 gstfft-1.0 gstpbutils-1.0 gstriff-1.0 gstrtp-1.0 gstrtsp-1.0 gstsdp-1.0 gsttag-1.0 gstvideo-1.0 gstgl-1.0 orc-0.4 gstopensles OpenSLES gstaudio-1.0 gstbase-1.0)
find_library(log-lib log)
# 生产
add_library(${CMAKE_PROJECT_NAME} SHARED gstreamer_cmake.cpp)
# 链接
target_link_libraries(${CMAKE_PROJECT_NAME}
android log
${log-lib} ${GST_LIBS} ${GST_PLUGINS_CORE_LIBS} ${GST_PLUGINS_BASE})
error: undefined symbol
cmd.exe /C "cd . && F:\android-sdk-windows\ndk\25.1.8937393\toolchains\llvm\prebuilt\windows-x86_64\bin\clang++.exe --target=aarch64-none-linux-android28
--sysroot=F:/android-sdk-windows/ndk/25.1.8937393/toolchains/llvm/prebuilt/windows-x86_64/sysroot -fPIC -g -DANDROID -fdata-sections -ffunction-sections -funwind-tables -fstack-protector-strong -no-canonical-prefixes -D_FORTIFY_SOURCE=2 -Wformat -Werror=format-security -fno-limit-debug-info -static-libstdc++ -Wl,
--build-id=sha1 -Wl,--no-rosegment -Wl,--fatal-warnings -Wl,--gc-sections -Wl,--no-undefined -Qunused-arguments -shared -Wl,-soname,libgstreamer_cmake.so
-o E:\projects-my\gstreamer-cmake\app\build\intermediates\cxx\Debug\2518512v\obj\arm64-v8a\libgstreamer_cmake.so CMakeFiles/gstreamer_cmake.dir/gstreamer_cmake.cpp.o
-LE:/libarry/gstreamer-1.0-android-universal-1.26.6/arm64/lib
-LE:/libarry/gstreamer-1.0-android-universal-1.26.6/arm64/lib/gstreamer-1.0 -landroid
-llog F:/android-sdk-windows/ndk/25.1.8937393/toolchains/llvm/prebuilt/windows-x86_64/sysroot/usr/lib/aarch64-linux-android/28/liblog.so
-lgstreamer-1.0 -lgmodule-2.0 -lgobject-2.0 -lglib-2.0 -liconv -lintl -lffi -lgstaudioresample -lgstaudiotestsrc -lgstaudioconvert -lgstautodetect -lgstallocators-1.0 -lorc-test-0.4 -lgstapp-1.0 -lgstaudio-1.0 -lgstfft-1.0 -lgstpbutils-1.0 -lgstriff-1.0 -lgstrtp-1.0 -lgstrtsp-1.0 -lgstsdp-1.0 -lgsttag-1.0 -lgstvideo-1.0 -lgstgl-1.0 -lorc-0.4 -lgstopensles -lOpenSLES -lgstaudio-1.0 -lgstbase-1.0 -lgstfft-1.0 -lgstpbutils-1.0 -lgstriff-1.0 -lgstrtp-1.0 -lgstrtsp-1.0 -lgstsdp-1.0 -lgsttag-1.0 -lgstvideo-1.0 -lgstgl-1.0 -lorc-0.4 -lgstopensles -lOpenSLES -lgstbase-1.0 -latomic -lm && cd ."
ld: error: undefined symbol: pcre2_match_context_free_8
>>> referenced by gregex.c:1045 (../glib/gregex.c:1045)
>>> gregex.c.o:(g_match_info_unref) in archive E:/libarry/gstreamer-1.0-android-universal-1.26.6/arm64/lib/libglib-2.0.a
啊 放弃了, 符号错误 链接失败, 懒得搞了, 还是用FFmpeg…
Transclude of gstreamer-cmake.zip
FFmpeg for Android
Android.mk
https://developer.android.com/ndk/guides/android_mk?hl=zh-cn
Android.mk 是 Android 构建系统中用于编译 本地代码(C/C++) 的配置文件。它基于 GNU Make 语法,主要用于定义如何编译 JNI 库(.so文件)、可执行文件或静态库(.a)。
Android.mk 文件位于项目 jni/ 目录的子目录中,用于向构建系统描述源文件和共享库。它实际上是一个微小的 GNU makefile 片段,构建系统会将其解析一次或多次。Android.mk 文件用于定义 Application.mk、构建系统和环境变量所未定义的项目级设置。它还可替换特定模块的项目级设置。
in short 类似 CMakeLists 的一个定义文件 管理编译本地代码库的, 新项目建议优先使用 CMake(CMakeLists.txt) 不学也罢