想要进阶高级Android开发工程师,NDK开发是必须要掌握的技能之一,本文以集成第三方SO库为开端,带大家入门NDK开发,主要包含一下内容:
1、NDK工程创建;
2、编写和打包Android标准SO库;
3、集成Android标准SO库;
4、集成Android非标准SO库。
一、NDK工程创建
1、什么是NDK(Native Development Kit)?
NDK是一个为Android平台提供支持的开发工具包,专门用于开发本地(Native)代码的。通过NDK,开发者可以编写C、C++代码并将其编译成可以在Android设备上运行的共享库(.so文件)。简单来说,就是通过NDK让Android可以使用C、C++代码。
2、什么是JNI(Java Native Interface)?
JNI是Java与其他编程语言(如C、C++)之间的接口。它允许Java代码和本地代码(通常是C或C++)进行互相调用。
当开发者需要在Android应用中使用Java与C/C++代码交互时,就需要使用JNI。它提供了一种桥接机制,让Java代码能够调用C/C++库。
3、什么是CMake?
CMake 是一个跨平台的自动化构建系统工具,它主要用于管理和生成项目的构建过程。可以简单的把CMake理解Android工程的Gradle。
4、创建Native Android工程
Native工程创建好后,会自动生成CMake文件,在cpp文件目录想编写C/C++代码;在java目录下编写java代码。
1)简单的了解一下CMake文件代码:
# For more information about using CMake with Android Studio, read the
# documentation: https://d.android.com/studio/projects/add-native-code.html# Sets the minimum version of CMake required to build the native library.#最低支持的版本
cmake_minimum_required(VERSION 3.22.1)#当前工程名
project("nativet0224")#添加一个库(动态库SHARED,静态库STATIC)
add_library(nativet0224 #库的名字--->nativet0224.soSHARED #动态库#cpp的源文件:把cpp源文件编译成libnative-lib.so库native-lib.cpp)#查找一个NDK工具中的动态库(liblog.so)
find_library(log-liblog)#nativet0224是我们的总库,也就是我们在apk/lib/nativet0224.so
#然后把log库链接到总库中去,总库的cpp代码就可以使用android/log.h的库实现代码了
target_link_libraries(nativet0224 #被链接的总库#链接的具体库${log-lib})
2)native-lib代码
native-lib.cpp主要是起到一个桥接作用,使用Java层连接native调用C++层。当我们需要让java层调用C++代码时,需要先在java层定义一个Native方法,让后通过快捷键,在native-lib.cpp自动生成桥接方法。
#include <jni.h>
#include <string>//Java层连接native调用C++层
extern "C" JNIEXPORT jstring JNICALL
Java_com_nativet0224_MainActivity_stringFromJNI(JNIEnv* env,jobject /* this */) {std::string hello = "Hello from C++";return env->NewStringUTF(hello.c_str());
}
3)Java层定义Native方法
Native工程创建好后,会在MainActivity里面自动生成调用Native层的默然方法。
public class MainActivity extends AppCompatActivity {// 加载nativet0224.so库,项目编译后apk的lib目录下会自动打包生成nativet0224.so库static {System.loadLibrary("nativet0224");}private ActivityMainBinding binding;@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);binding = ActivityMainBinding.inflate(getLayoutInflater());setContentView(binding.getRoot());TextView tv = binding.sampleText;// 调用native方法tv.setText(stringFromJNI());}//声明一个native方法,供Java层调用public native String stringFromJNI();
}
4)自定义NativeLib.java
真实的项目开发中,我们会将Java层调用Native方法的定义统一封装到NativeLib.java
public class NativeLib {// 加载nativet0224.so库,项目编译后apk的lib目录下会自动打包生成nativet0224.so库static {System.loadLibrary("nativet0224");}//声明一个native方法,供Java层调用public native String testCallNative();
}
定义testCallNative()方法后,将鼠标放到 testCallNative()方法上,Androidstudio自动提示在native层native-lib.cpp创建桥接方法。
简单的写个字符串“Hello my Native.”返回,这里用的是C++ 语言开发,需要花点时间入门一下,或者直接AI帮写。
#include <jni.h>
#include <string>//Java层连接native调用C++层
extern "C" JNIEXPORT jstring JNICALL
Java_com_nativet0224_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_nativet0224_NativeLib_testCallNative(JNIEnv *env, jobject thiz) {// TODO: implement testCallNative()std::string hello = "Hello my Native.";return env->NewStringUTF(hello.c_str());
}
5)Java层调用
public class MainActivity extends AppCompatActivity {private ActivityMainBinding binding;@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);binding = ActivityMainBinding.inflate(getLayoutInflater());setContentView(binding.getRoot());NativeLib nativeLib = new NativeLib();String result = nativeLib.testCallNative();Log.d("NDK", result);}
}
至此,一个简单的Android NDK工程已完成,实现了Java层与C++层的交互。
二、编写和打包Android标准SO库
将上面的Android NDK工程打包,即可生成Android标准SO库。
编译出的 .so 文件位于以下目录:
app/build/intermediates/cmake/debug/obj/<architecture>/lib<library_name>.so
三、集成Android标准SO库
重新创建一个Android工程,集成我们打包出来的libnativet0224.so;
1)在新的Android工程的main目录下,创建jniLibs文件夹,将打包好的libnativet0224.so复制到该目录下。
2)在build.gradle文件中声明使用
android {//.....sourceSets {main {// 指定 jniLibs 路径jniLibs.srcDirs = ['src/main/jniLibs']}}
}
3) 自定义NativeLib.java
将Java层调用Native方法的定义统一封装到NativeLib.java,同Native工程的NativeLib.java。
但是这里要注意了,NativeLib.java的包名要同打包SO库的NativeLib.java的包名一致,不让对应的Native方法会对应不上。
我们先做一个错误的示范:
将NativeLib.java放在新项目的包名下,
在MainActivity调用:
public class MainActivity extends AppCompatActivity {@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_main);NativeLib nativeLib = new NativeLib();String result = nativeLib.testCallNative();Log.d("NDK", result);}
}
执行报错:
java.lang.UnsatisfiedLinkError: No implementation found for java.lang.String com.nativet0224java.NativeLib.testCallNative() (tried Java_com_nativet0224java_NativeLib_testCallNative and Java_com_nativet0224java_NativeLib_testCallNative__)
原因是,新项目的NativeLib.java是在com.nativet0224java包名下的,故新项目的NativeLib.java的public native String testCallNative()方法生成的Native方法命名是:Java_com_nativet0224java_NativeLib_testCallNative
而SO库中testCallNative()方法生成的Native方法命名是:
Java_com_nativet0224_NativeLib_testCallNative,
导致新项目无法在SO库中找到对应的Native方法,需要将新项目的NativeLib.java写在com.nativet0224包名下。
修改后重新编译,项目正常运行:
四、集成Android非标准SO库
然而在真实项目开发过程中,我们集成的SO库往往是第三方开发的,很大可能并不是标准的SO库(即不存在native-lib.cpp桥接文件,或native-lib.cpp桥接文件里面没有定义符合Android命名(Java_com_nativet0224_NativeLib_testCallNative)的桥接方法)。
这时候我们就需要自己创建一个Native Module去集成非标准的SO库。
1)在上面NDK工程增加test C++文件,并创建一个test方法
test.cpp
//
// Created by mypc on 2025-02-24.
//#include "test.h"Text0224::Text0224() {}Text0224::~Text0224() {}int Text0224::test(int a, int b) {return a + b;
}
test.h
//
// Created by mypc on 2025-02-24.
//#ifndef NATIVET0224_TEST_H
#define NATIVET0224_TEST_Hclass Text0224 {
public:Text0224();~Text0224();int test(int a, int b);};#endif //NATIVET0224_TEST_H
2)在上面NDK工程的CMake中配置将text.cpp打包到SO库中
重新打包,生成的新的libnativet0224.so就会包含test.cpp在里面
3)在Java工程创建一个Native Module
4)在nativelib模块的main目录下,创建jniLibs文件夹,将新打包好的包含text.cpp文件的libnativet0224.so复制到该目录下。
5)在 nativelib模块的CMake文件中添加nativet0224动态库
# For more information about using CMake with Android Studio, read the
# documentation: https://d.android.com/studio/projects/add-native-code.html# Sets the minimum version of CMake required to build the native library.cmake_minimum_required(VERSION 3.22.1)project("nativelib")#添加nativet0224动态库
add_library(nativet0224 SHARED IMPORTED)
#指定nativet0224动态库的路径
set_target_properties(nativet0224 PROPERTIES IMPORTED_LOCATION ${CMAKE_SOURCE_DIR}/../jniLibs/${ANDROID_ABI}/libnativet0224.so)add_library(nativelibSHAREDnativelib.cpp)find_library(log-liblog)target_link_libraries(nativelib#链接的nativet0224库到打包出来的新的nativelib.so库里面nativet0224${log-lib})
6)在nativelib模块自动生成的NativeLib.java文件中增加调用Native层text.cpp类的test方法的桥接方法。参数和返回值要对应上text.cpp类的test方法。
public native int callTest(int a, int b);
package com.nativelib;public class NativeLib {// Used to load the 'nativelib' library on application startup.static {System.loadLibrary("nativelib");}/*** A native method that is implemented by the 'nativelib' native library,* which is packaged with this application.*/public native String stringFromJNI();public native int callTest(int a, int b);
}
快捷键自动创建Native层的桥接方法
#include <jni.h>
#include <string>extern "C" JNIEXPORT jstring JNICALL
Java_com_nativelib_NativeLib_stringFromJNI(JNIEnv* env,jobject /* this */) {std::string hello = "Hello from C++";return env->NewStringUTF(hello.c_str());
}
extern "C"
JNIEXPORT jint JNICALL
Java_com_nativelib_NativeLib_callTest(JNIEnv *env, jobject thiz, jint a, jint b) {// TODO: implement callTest()
}
7)在nativelib模块中cpp目录下创建text.h文件,可以直接复制上面的text.h文件
text.h
//
// Created by mypc on 2025-02-24.
//#ifndef NATIVET0224_TEST_H
#define NATIVET0224_TEST_Hclass Text0224 {
public:Text0224();~Text0224();int test(int a, int b);};#endif //NATIVET0224_TEST_H
8)在 nativelib模块的Native层的桥接方法中谁用text.cpp的test方法
#include <jni.h>
#include <string>//Java层连接native调用C++层
extern "C" JNIEXPORT jstring JNICALL
Java_com_nativet0224_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_nativet0224_NativeLib_testCallNative(JNIEnv *env, jobject thiz) {// TODO: implement testCallNative()std::string hello = "Hello my Native.";return env->NewStringUTF(hello.c_str());
}
9)Java层调用NativeLib.java的callTest桥接方法
在app模块的build.gradle中增加对native模块的依赖
implementation project(path: ':nativelib')
MainActivity中调用
public class MainActivity extends AppCompatActivity {@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_main);com.nativelib.NativeLib nativeLib = new com.nativelib.NativeLib();int result = nativeLib.callTest(2, 3);Log.d("NDK", "result = " + result);}
}
至此,实现了Android集成非标准的SO库。
注意:我这里演示的集成Android标准SO库和集成Android非标准SO库,用的是同一个工程,所以在集成Android非标准SO库时,需要将集成Android标准SO库时在app模块main目录下,创建jniLibs文件夹和libnativet0224.so删除,避免存在来个同名libnativet0224.so。