一.介绍
1.什么是ndk技术?
在学习ndk技术前,我们需要先了解一下JNI(Java Native Interface)技术,JNI技术是一种实现Java代码和C/C++代码之间交互的技术,它提供了一组编程接口,使得Java程序可以调用C/C++代码并与其进行通信。通过JNI技术,开发者可以将C/C++代码嵌入Java项目中,并在Java代码中调用这些C/C++函数。那么,NDK技术和它有什么关系呢?NDK是一种用于开发Android应用程序的工具集,它允许开发者使用C/C++编写部分或全部的Android应用程序代码,以便提高性能和访问底层系统功能。这样看起来NDK技术和JNI技术是一回事,就是为了实现Java调用C/C++或C/C++调用Java。确实如此,用一句话概括它们之间的关系就是:开发者使用NDK技术在Android应用程序中编写C/C++代码,并将其编译成共享库(如.so文件),然后使用JNI技术在Java代码中加载并与这些C/C++代码进行交互。
2.为什么要学习ndk?
第一点的话就是提高性能了,这个显而易见,C/C++的性能肯定比Java高,如果有些功能用Java实现性能不行,就可以把这部分代码用C/C++实现。第二点的话就是C/C++语言可以直接访问底层系统功能和硬件资源,如摄像头和传感器等,这是Java做不到的。最后一点是保密性,Java代码是编译成字节码,而C/C++代码是直接编译成机器码,反编译的难度比Java大的多。所以,如果哪部分功能需要保密,也可以用C/C++来实现。
3.编写C/C++代码并编译出.so文件
我们要在Android项目中调用C/C++代码,首先要将写好的C/C++代码编译成.so共享库,下面我会以Android Studio 2021来详细讲解编译出.so文件的过程。
第一步:打开Android Studio,新建一个Native C++项目,如下图所示:
项目新建完成后是下面这个样子:
我们可以看到main目录下面有一个cpp目录,这里就是我们编写C++代码的地方,我们先来看一下自动生成的CmakeLists.txt文件,代码如下:
# 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.18.1) //cmake的最低版本是3.18.1 # Declares and names the project. project("ndkstudy") # Creates and names a library, sets it as either STATIC # or SHARED, and provides the relative paths to its source code. # You can define multiple libraries, and CMake builds them for you. # Gradle automatically packages shared libraries with your APK. add_library( # Sets the name of the library. ndkstudy //生成的库的名称 # Sets the library as a shared library. SHARED //设置生成的库为共享库.so # Provides a relative path to your source file(s). native-lib.cpp //c++源文件的相对路径
) # Searches for a specified prebuilt library and stores the path as a # variable. Because CMake includes system libraries in the search path by # default, you only need to specify the name of the public NDK library # you want to add. CMake verifies that the library exists before # completing its build. 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) //使用find_library来查找log库,并把找到的log库存储在变量log-lib中 # Specifies libraries CMake should link to your target library. You # can link multiple libraries, such as libraries you define in this # build script, prebuilt third-party libraries, or system libraries. target_link_libraries( # Specifies the target library. ndkstudy # Links the target library to the log library # included in the NDK. ${log-lib}) //将ndkstudy库和log库进行链接
然后,我们再来看一下自动生成的native-lib.cpp文件,代码如下:
#include <jni.h> #include <string> extern "C" JNIEXPORT jstring JNICALL Java_com_example_ndkstudy_MainActivity_stringFromJNI( JNIEnv* env, jobject /* this */) { std::string hello = "Hello from C++"; return env->NewStringUTF(hello.c_str()); }//函数的功能就是返回一个"Hello from C++"字符串
我们发现这个函数名特别长,其实就是对应java目录下com.example.ndkstudy包下MainActivity类下的stringFromJNI()这个函数。了解了这些之后,我们只需要Make Project即可,如下图所示:
然后就可以看到所生成的.so文件了,如果没有的话,可以刷新一下项目
接下来,我们建一个新的项目,然后把上面所生成的不同CPU架构的.so文件复制到新项目的main/jniLibs目录,jniLibs目录需要自己新建。
然后在app/build.gradle文件下添加以下的代码:
android { namespace 'com.example.ndkstudy' compileSdk 32 defaultConfig { applicationId "com.example.ndkstudy" minSdk 21 targetSdk 32 versionCode 1 versionName "1.0" testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" ndk { // 设置支持的SO库架构(开发者可以根据需要,选择一个或多个平台的so) abiFilters "armeabi", "armeabi-v7a", "arm64-v8a", "x86","x86_64" }//新增代码 } buildTypes { release { minifyEnabled false proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' } } compileOptions { sourceCompatibility JavaVersion.VERSION_1_8 targetCompatibility JavaVersion.VERSION_1_8 } } dependencies { implementation 'androidx.appcompat:appcompat:1.4.1' implementation 'com.google.android.material:material:1.5.0' implementation 'androidx.constraintlayout:constraintlayout:2.1.3' testImplementation 'junit:junit:4.13.2' androidTestImplementation 'androidx.test.ext:junit:1.1.3' androidTestImplementation 'androidx.test.espresso:espresso-core:3.4.0' }
接下来,就可以调用so库中的C++函数了,这里也是特别容易出错的地方,我先贴出代码,然后再详细讲解。
public class MainActivity extends AppCompatActivity { private TextView tv_display; { System.loadLibrary("ndkstudy");//第一步,加载动态库,放到静态代码块里就行 } @SuppressLint("MissingInflatedId") @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); tv_display=findViewById(R.id.tv_display); tv_display.setText(stringFromJNI());//第三步,调用本地函数,实际上调用的函数是Java_com_example_ndkstudy_MainActivity_stringFromJNI();
} public native String stringFromJNI();//第二步,本地方法声明,也就是说这个方法由c++实现,这里作个声明 }
一个特别需要注意的点是我们在加载动态库和作本地方法声明的时候,需要在com.example.ndkstudy包下,MainActivity类下进行操作,也就是要对应那个特别长的函数名。如果以上的步骤都没有错的话,就可以在手机屏幕上看到输出的”Hello from C++”字符串了。