Android ndk 开发入门(01)之 Hello World
前言
重新开始学习 Android ndk,为了以后方便复习和查找,我就把学习过程和资料整理成一个系列笔记吧。
Hello World
我们新建一个 Android Native C++ 项目,就叫 NdkDemo 吧,项目新建完成,我们编译运行一下看看效果。如图:
显示了一行字符串:Hello from C++。我们一行代码没写就已经实现了一个最简单的 ndk 程序,这行字符串是上层的 java 代码通过 jni 调用 native 层的代码显示出来的,也就是说最终实现是在 c++ 层,java 层只是调用,而 jni 就是这两者之间的桥梁。通过 jni, Java层的代码能够调用 c++ 的代码。那我们这篇文章就是来分析这个字符串是怎么显示出来的。
我们先看看 src/main 目录下的文件结构:
可以发现多了一个 cpp 文件夹,里面有两个新文件:CMakeLists.txt, native-lib.cpp。我们等会儿来看这两个文件,先看看 MainActivity.java 的代码:
public class MainActivity extends AppCompatActivity {
// 在使用 native 方法之前,必须使用以下语句加载 jni 库,这里加载的 jni 库文件名为:libnative-lib.so
// 我们写的时候可以省略掉前面的 lib, 系统会自动添加。
static {
System.loadLibrary("native-lib");
}
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
// 将 TextView 的文本设置为 native 方法 stringFromJNI() 返回的字符串。
TextView tv = findViewById(R.id.sample_text);
tv.setText(stringFromJNI());
}
/**
* native 方法声明,由 jni 层代码来实现。
*/
public native String stringFromJNI();
}
看注释应该就能理解了,那我们会疑惑,这个 libnative-lib.so 是从哪儿来的呢?那就要看 cpp 目录下的 native-lib.cpp 文件,这个就是 jni 层的代码:
#include <jni.h>
#include <string>
extern "C" JNIEXPORT jstring JNICALL
Java_com_along_ndkdemo_MainActivity_stringFromJNI(
JNIEnv* env,
jobject /* this */) {
std::string hello = "Hello from C++";
return env->NewStringUTF(hello.c_str());
}
extern “C” : 是让编译器按照 C 的编译规则进行编译。
JNIEXPORT 和 JNICALL : 这两个都是 jni 的关键字,表示此函数是要被JNI调用的。具体含义可以查一查。
jstring : jni 的一种数据类型,也就是方便 java 的 String 和 c++ 的 string 做转换,类似的还有 jint 等。后面文章会讲到数据类型的转换。
Java_com_along_ndkdemo_MainActivity_stringFromJNI : 方法名,也就是我们 java 层 native 方法的包名 + 类名 + 方法名的组合,其中"." 被替换成 “_”。java 层的 native 方法就是通过这种方法找到对应实现的。
JNIEnv : JavaVM 在线程中的代码,每一个线程都有一个, JNI 中可能有非常多个 JNIEnv。我们需要通过它在 jni 中操作 java 层的对象。
jobject : 对应 java 层中的对象。
接下来我们看看配置文件 — CMakeLists.txt,我们编写完 jni 的代码之后需要通过配置文件编译生成 .so 库文件。这里使用的是 CMake, 类似于 Android.mk 文件。
# CMake最低版本。
cmake_minimum_required(VERSION 3.4.1)
# 设置我们需要加载的库。
add_library(
native-lib # 库的名字。
SHARED # 库的类型,这里指定为编译生成动态库。
native-lib.cpp ) # 包含的原文件。
# 加载系统的一些库文件,更深层含义我暂定也不晓得。这里是加载 log 的库。
find_library(
log-lib # Sets the name of the path variable.
log ) # Specifies the name of the NDK library that you want CMake to locate.
# 将生成的库链接到项目中。
target_link_libraries( # Specifies the target library.
native-lib
# Links the target library to the log library
# included in the NDK.
${log-lib} )
解释都写在代码注释里了,之后我们编译一下会发现在 …/app/build/intermediates/cmake/debug/obj 目录下生成了四个平台对应的 .so 文件,如下图所示:
其实我们也可以指定生成指定平台的 .so 库,不用生成全部平台的,这样可以减少打包 app 的体积大小。方法如下:在 app/build.gradle 文件中指定:
android {
...
defaultConfig {
...
externalNativeBuild {
cmake {
arguments '-DANDROID_STL=c++_static'
}
ndk {
abiFilters "armeabi-v7a", "x86"
}
}
...
}
...
}
这样我们重新编译运行就可以看到只生成对应平台的 .so 库了。
在回到 MainActivity.java 中,我们就更能理解其中的代码了。首先在静态代码块中加载 libnative-lib.so 库,然后调用 native 方法:stringFromJNI(), 该方法的实现是在 jni 层,返回一个字符串:Hello from C++,然后在界面上显示该字符串,就是我们运行后看到的了。
参考资料:
1、[-NDK 导引篇 -] 在NDK开发之前你应知道的东西:
https://juejin.im/post/5d984028518825095879e45d
2、关于在Android中使用CMake你所需要了解的一切(该系列共有三篇文章):
https://juejin.im/user/58c382750ce4630054690774/posts