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.01.012008年9月23日不支持
Android 1.11.122009年2月9日不支持
Android Cupcake1.532009年4月27日不支持
Android Donut1.642009年9月15日不支持
Android Eclair2.0 – 2.15 – 72009年10月26日不支持
Android Froyo2.2 – 2.2.382010年5月20日不支持
Android Gingerbread2.3 – 2.3.79 – 102010年12月6日不支持
Android Honeycomb3.0 – 3.2.611 – 132011年2月22日不支持
Android Ice Cream Sandwich4.0 – 4.0.414 – 152011年10月18日不支持
Android Jelly Bean4.1 – 4.3.116 – 182012年7月9日不支持
Android KitKat4.4 – 4.4.419 – 202013年10月31日不支持
Android Lollipop5.0 – 5.1.121 – 222014年11月12日不支持
Android Marshmallow6.0 – 6.0.1232015年10月5日不支持
Android Nougat7.0 – 7.1.224 – 252016年8月22日不支持
Android Oreo8.0 – 8.126 – 272017年8月21日不支持
Android Pie9282018年8月6日不支持
Android 1010292019年9月3日不支持
Android 1111302020年9月8日不支持
Android 1212 – 12L31–322021年10月4日不支持
Android 1313332022年8月15日支持
Android 1414342023年10月4日支持
Android 1515352024年10月15日支持
Android 1616362025年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)
 

主要在

  1. include_directories 设置头文件的目录
  2. add_library 这项目的目标; 生成静态库或动态库, 这里是 SHARED 动态库
  3. 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 versionNDK Version
1.26.xr25c
1.24.xr25c
1.22.xr21
1.20.xr21
1.18.xr21
1.16.xr18b

The Android APIs targeted by our stable release(s) are:

ArchitectureAPI Targeted
(GStreamer 1.20)
API Targeted
(GStreamer >= 1.22)
armv7v16 (Jelly Bean)v21 (Lollipop)
x86v16 (Jelly Bean)v21 (Lollipop)
arm64v21 (Lollipop)v21 (Lollipop)
x86_64v21 (Lollipop)v21 (Lollipop)

Installing for Android development

https://gstreamer.freedesktop.org/documentation/installing/for-android-development.html?gi-language=c

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).

参考

N_Cmake

官方示例是基于 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

FFmpeg for Android & 图传Web

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) 不学也罢