NDK概念及疑问

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
1.ndk是什么
NDK是一个工具集,允许你的App使用一些底层语言代码,例如C和C++。针对特定类型的应用,能使用C和C++的代码,
将会很有用,因为这样你可以复用已经存在的C和C++库代码。
2.jni是什么
- Java Native Interface Java本地接口,
- Java层调用本地C,C++等
- JNI是Java调用本Native语言的一种特性,与Android无直接关系
- 实际中的驱动都是C/C++开发的,通过JNI,Java可以调用C/C++实现的驱动,从而扩展Java虚拟机的能力。另外,
在高效的数学运算、游戏的实时渲染、音视频的编码和解码等方面,一般都是用C开发的。
3.为什么用ndk
- 进一步提升设备性能,以降低延迟或运行游戏或物理模拟等计算密集型应用。
- 重复使用您自己或其他开发者的 C 或 C++ 库。
4.ndk的开发资料
- 官方文档:https://developer.android.google.cn/ndk?hl=zh_cn
5.ndk的学习方法
- 参考官方文档学习
6.交叉编译概念
- 交叉编译就是程序的编译环境和实际运行环境不一致,即在一个平台上生成另一个平台上的可执行代码。
- 比如NDK,你在Mac、Win或者Linux上生成的C/C++的代码要在Android平台上运行,就需要使用到交叉编译了。
-通俗点说就是你的电脑和手机使用的CPU不同,所以CPU的指令集就不同,比如arm的指令集在X86上就不能运行。
7.预编译库及非预编译库是什么
- 预编译库是指已经经过编译的二进制文件,可以直接在 Android 应用中使用如静态库(.a)或共享库(.so)。
预编译库可以直接加载到应用程序中,并通过JNI与 Java 层进行交互
- 非预编译库是指将 C/C++ 代码直接放在 Android 项目中,并在构建时进行编译,这种方式会将 C/C++ 代码与 Java 代码一起
编译成最终的应用程序,而不是单独生成预编译库。
8.cmake是什么以及它的常用语法
- CMake 是一个跨平台的构建工具,用于管理和构建 C/C++ 项目。它提供了简洁易用的语法和命令来描述项目的构建过程,
并自动生成与构建环境相关的构建脚本,使项目能够方便地在不同的平台上进行构建。
- 见 CMake语法.md文件

常用CMake 语法

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

一. cmake 版本号声明
# 声明一个我们使用的最小版本
cmake_minimum_required(VERSION 3.10.2)

二. 设置项目名称
# 这个项目名称一般和生成的库名称相同
project(test)

三. 添加头文件搜索目录
include_directories(../../../include)

四. 添加源文件
# 使用变量添加
set(SOURCES test.cpp xxx.cpp)
# 添加所有
FILE(GLOB SRCS "*.CPP" "*.h")

五. 生成一个库
动态库和静态库的区别:
动态库是共享的
静态库是独占的
举个例子就是,有两个应用程序,A和B,他们两个都依赖 test.so
,这个test.so在内存中就只有一份,占用也是一份内存

如果我们给test库打包成test.a,那么A和B会各自独占一份test.a那么在内存上,就会占用两份空间

add_library( # Sets the name of the library.
srt-lib # 库的名字

# Sets the library as a shared library.
SHARED # 动态库

# Provides a relative path to your source file(s).
srt-lib.cpp srt1-lib.cpp) # 源文件列表

六. 搜索一个库(预构建库)
find_library( # Sets the name of the path variable.
log-lib # 可以理解别名

# Specifies the name of the NDK library that
# you want CMake to locate.
log # 这个是liblog.so 在ndk目录中自带的一个库
)

七. 设置一个变量
# 设置LIBDIR为 ${CMAKE_CURRENT_SOURCE_DIR}/../../../libs/${ANDROID_ABI}
set(LIBDIR ${CMAKE_CURRENT_SOURCE_DIR}/../../../libs/${ANDROID_ABI})

八. 导入预构建库
# 设置LIBDIR为 ${CMAKE_CURRENT_SOURCE_DIR}/../../../libs/${ANDROID_ABI}
set(LIBDIR ${CMAKE_CURRENT_SOURCE_DIR}/../../../libs/${ANDROID_ABI})

九. 链接库
target_link_libraries( # Specifies the target library.
test

# Links the target library to the log library
# included in the NDK.
${log-lib})


十. 设置库的输出目录
set_target_properties(test PROPERTIES LIBRARY_OUTPUT_DIRECTORY ${CMAKE_LIBRARY_OUTPUT_DIRECTORY_${ARCH}})

NDK中打印日志

1
2
3
4
5
6
7
8
9
10
11
#include <android/log.h>
#define LOG_TAG "tyl"
#define LOGI(...) __android_log_print(ANDROID_LOG_INFO, LOG_TAG, __VA_ARGS__)
//通过#define的宏定义组装我们的__android_log_print方法

extern "C"
JNIEXPORT jint JNICALL
Java_com_tyl_ndk_1study_MainActivity_add(JNIEnv *env, jobject thiz, jint a, jint b) {
LOGI("a+b=%d",a+b); //输出则为a+b=3,tag=tyl
return a+b;
}

NDK开发实际集成源码的场景

1.使用Android Studio 源码直接集成

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

- 新建项目时选择Table的时候滑倒最后选择Native C++,main目录下会自动添加一个cpp目录里面就是c++文件;
- 使用示例:
public native int add(int a, int b);//在MainActivity中新增方法,新建的方法会报红,
//鼠标放到报红的地方alt+enter出现Create JNI function for add按钮,点击会自动在cpp文件中创建jni的add方法;
extern "C"
JNIEXPORT jint JNICALL //java+包名+方法名
Java_com_tyl_myjnistudy_MainActivity_add(JNIEnv *env, jobject thiz, jint a, jint b) {
return a+b;
}
//上面是cpp文件中的代码,在方法中实现自己的逻辑代码即可;
//activity中使用再加载了库后 System.loadLibrary("ndkdemo"),直接调用方法执行即可;

完整代码:
//MainActivity
public class MainActivity extends AppCompatActivity {
static {
System.loadLibrary("ndkdemo");
}

@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
Log.e("tyl","add="+add(1,2));
}
public native String stringFromJNI();
public native int add(int a, int b);
}

//native-lib.cpp
#include <jni.h>
#include <string>
#include <android/log.h>
#define LOG_TAG "tyl"
#define LOGI(...) __android_log_print(ANDROID_LOG_INFO, LOG_TAG, __VA_ARGS__)

extern "C" JNIEXPORT jstring JNICALL
Java_com_tyl_ndkdemo_MainActivity_stringFromJNI(
JNIEnv* env,
jobject /* this */) {
std::string hello = "Hello from C++";
return env->NewStringUTF(hello.c_str());
}
extern "C"
JNIEXPORT jstring JNICALL
Java_com_tyl_ndk_1study_MainActivity_stringFromJNI(JNIEnv *env, jobject thiz) {
// TODO: implement stringFromJNI()
}
extern "C"
JNIEXPORT jint JNICALL
Java_com_tyl_ndk_1study_MainActivity_add(JNIEnv *env, jobject thiz, jint a, jint b) {
LOGI("a+b=%d",a+b);
return a+b;
}

2.使用命令编译出符合平台相关的预编译库

配置ndk环境

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
1.下载并安装 NDK

打开 Android Studio。
点击菜单栏中的 “File”,选择 “Project Structure”。
在弹出的窗口中,选择 “SDK Location”。
在 “Android NDK location” 的文本框中,点击 “Download” 按钮。
在弹出的 “SDK Components Setup” 窗口中,勾选 “LLDB” 和 “CMake”,然后点击 “Next”。
在 “SDK Platforms” 窗口中,选择你需要的 NDK 版本,并点击 “Next”。
在 “SDK Tools” 窗口中,选择 “LLDB” 和 “CMake”,然后点击 “Finish”。
Android Studio 将自动下载并安装 NDK。
4. 配置 Android Studio 的 NDK 路径
现在,我们需要配置 Android Studio 的 NDK 路径,以便在项目中正确使用 NDK。

2.打开 Android Studio。
点击菜单栏中的 “File”,选择 “Project Structure”。
在弹出的窗口中,选择 “SDK Location”。
在 “Android NDK location” 的文本框中,填入你的 NDK 路径。
如果你使用默认的 Android Studio 安装路径,NDK 路径应为:<Android Studio 安装路径>/ndk/<NDK 版本号>。
例如:C:\android\android-studio\ndk\22.0.7026061
点击 “OK” 保存配置。

2.1.test.cpp 测试的c++文件

1
2
3
4
5
6
7
8
#include <iostream>
#include "test.h"
using namespace std;
int test::add(int a, int b) {

std::cout << "a: " << a << " b: " << b << std::endl;
return a + b;
}

2.2.test.h 测试的c++头文件

1
2
3
4
5
6
7
#ifndef TEMPLATE_TEST_H
#define TEMPLATE_TEST_H
class test {
public:
int add(int a, int b);
};
#endif

2.3.CMakeLists.txt cmake编译文件

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

cmake_minimum_required(VERSION 3.22.1)

project(test)
set(CMAKE_CXX_STANDARD 17)

# 设置NDK路径
set(ANDROID_NDK "/home/jiangc/Android/Sdk/ndk/25.1.8937393" CACHE PATH "Android NDK path")

# 设置架构,用来设置输出目录
set(ARCHS "armeabi-v7a" "arm64-v8a" "x86" "x86_64")

# 设置Android API级别
set(ANDROID_API_LEVEL 22)

# 添加每个架构的输出目录
foreach(ARCH ${ARCHS})
set(CMAKE_LIBRARY_OUTPUT_DIRECTORY_${ARCH} ${CMAKE_CURRENT_SOURCE_DIR}/libs/${ARCH})
endforeach()

# 设置头文件
include_directories(.)

# 设置源文件
set(SOURCES test.cpp)

# 编译某一个架构的
function(build_library ARCH)
set(CMAKE_ANDROID_ARCH_ABI ${ARCH})
set(CMAKE_ANDROID_NDK ${ANDROID_NDK})
set(CMAKE_SYSTEM_NAME Android)
set(CMAKE_SYSTEM_VERSION ${ANDROID_API_LEVEL})
add_library(test SHARED ${SOURCES})
set_target_properties(test PROPERTIES LIBRARY_OUTPUT_DIRECTORY ${CMAKE_LIBRARY_OUTPUT_DIRECTORY_${ARCH}})
endfunction()

message("--------------------------------------------- ${ANDROID_ABI}")
build_library(${ANDROID_ABI})

2.4.build.sh

1
2
3
4
5
6
7
8
9
10
11
//遇到的问题:
1. bash: ./build.sh: /bin/bashsM: bad interpreter: No such file or directory
原因是window环境过去的文件,用vim -b 打开会看到很多^M的字符
解决方式:vim -b 打开,shift+: 组合键输入: %s/\r//g 回车后后:wq保存退出
2./build.sh: line 2: cmake: command not found
原因是未安装cmake,
解决方式:sudo apt install cmake
3.Make Error at CMakeLists.txt:1 (cmake_minimum_required):
CMake 3.22.1 or higher is required. You are running version 3.16.3
原因是CMakeLists.txt文件中的最低版本号过高,
解决方案是:打开CMakeLists.txt将cmake_minimum_required(VERSION 3.22.1)修改为cmake_minimum_required(VERSION 3.16.3)
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
#!/bin/bash

rm build -rf
rm libs -rf

mkdir build
cd build

ANDROID_NDK="/home/jiangc/Android/Sdk/ndk/25.1.8937393"

ARCHS=('armeabi-v7a' 'arm64-v8a' 'x86' 'x86_64' )

function compile(){

for i in ${ARCHS[@]}; do
cmake -DCMAKE_TOOLCHAIN_FILE=$ANDROID_NDK/build/cmake/android.toolchain.cmake \
-DANDROID_ABI="$i" \
-DANDROID_NDK=$ANDROID_NDK \
-DANDROID_PLATFORM=android-22 \
..
make
done
}

compile

:<<!

cmake -DCMAKE_TOOLCHAIN_FILE=$ANDROID_NDK/build/cmake/android.toolchain.cmake \
-DANDROID_ABI="armeabi-v7a" \
-DANDROID_NDK=$ANDROID_NDK \
-DANDROID_PLATFORM=android-22 \
..

make

cmake -DCMAKE_TOOLCHAIN_FILE=$ANDROID_NDK/build/cmake/android.toolchain.cmake \
-DANDROID_ABI="arm64-v8a" \
-DANDROID_NDK=$ANDROID_NDK \
-DANDROID_PLATFORM=android-22 \
..

make

cmake -DCMAKE_TOOLCHAIN_FILE=$ANDROID_NDK/build/cmake/android.toolchain.cmake \
-DANDROID_ABI="x86" \
-DANDROID_NDK=$ANDROID_NDK \
-DANDROID_PLATFORM=android-22 \
..

make


cmake -DCMAKE_TOOLCHAIN_FILE=$ANDROID_NDK/build/cmake/android.toolchain.cmake \
-DANDROID_ABI="x86_64" \
-DANDROID_NDK=$ANDROID_NDK \
-DANDROID_PLATFORM=android-22 \
..

make

!

1
2
./build.sh //执行编译
成功后生成lib包,里面包含有各个版本的so文件

3.使用Android Studio 直接集成预编译库

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
//1.导入资源
app目录下新建include文件夹,将test.h文件导入
将so包复制到lib路径下

//2.修改CMakeLists.txt添加资源

cmake_minimum_required(VERSION 3.16.3)
project("ndkdemo")

# 包含头文件
include_directories(../../../include)
# 设置依赖库路径
set(LIBDIR ${CMAKE_CURRENT_SOURCE_DIR}/../../../libs/${ANDROID_ABI})
# 库的导入
add_library(test SHARED IMPORTED)
set_target_properties(test PROPERTIES IMPORTED_LOCATION ${LIBDIR}/libtest.so)

add_library( # Sets the name of the library.
ndkdemo
SHARED
native-lib.cpp)

foreach(item RANGE 1 5 2)
message("item = ${item}")
endforeach(item)


find_library( # Sets the name of the path variable.
log-lib
log)
#添加 test
target_link_libraries( # Specifies the target library.
ndkdemo
test
${log-lib})

//3.make androidStudio同步文件后,导入test.h及使用示例
#include <jni.h>
#include <string>
#include <android/log.h>
#include "../../../include/test.h"

#define LOG_TAG "tyl"
#define LOGI(...) __android_log_print(ANDROID_LOG_INFO, LOG_TAG, __VA_ARGS__)
extern "C"
JNIEXPORT jint JNICALL
Java_com_tyl_ndk_1study_MainActivity_add(JNIEnv *env, jobject thiz, jint a, jint b) {
LOGI("a+b=%d",a+b);
test t;
int c= t.add(3,4);
LOGI("A+B=%d",c);
return a+b;
}