对于部分android开发可能没有使用过NDK,ndk只是工具,我们真正要实现的功能还是需要c/c++来编写我们的实现代码。对于没有使用或学习过c/c++的同学,可能一听到c/c++编码的字眼就会有点头晕,想放弃的想法了。但是其实我们平时在NDK中编写C/C++会用到的语言特有特性很少,或者选择不去使用。完全将其当成java来编写,然后慢慢的会发现其实也就这样嘛。。。
我们首先可以选择使用C++来开始,因为C++相较于C来说更贴近Java,同样能够以面向对象的思想来编写我们的代码。首先我们来创建一个NDK工程。
我们在AS中新建工程:
一直到下一步最后一页
这里让as是否自动为我们设置好前一篇(配置篇)中提的C++11支持。如果作为初学者我们这里可以暂时不去考虑,直接finish创建完成。最后创建出来的的工程目录结构是这样的:
打开MainActivity:
// Used to load the 'native-lib' library on application startup.
static{
System.loadLibrary("native-lib");
}
@Override
protected voidonCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
// Example of a call to a native method
TextView tv = (TextView) findViewById(R.id.sample_text);
tv.setText(stringFromJNI());
}
/*** A native method that is implemented by the 'native-lib' native library,* which is packaged with this application.*/
public nativeStringstringFromJNI();
在这个Activity中像一个abstract方法一样没有方法体
native方法:stringFromJNI()。而在MainActivity中有一个static静态代码块中使用System.loadLibrary加载了一个C/C++的动态库。这样当调用这个native方法的时候就会从动态库中查找对应的C/C++方法。
这个方法在C++中声明为
Java_com_dongnao_hello_1jni_MainActivity_stringFromJNI
我们的native-lib.cpp中全部内容如下:
#include
#include
extern"C"
JNIEXPORTjstringJNICALL
Java_com_dongnao_hello_1jni_MainActivity_stringFromJNI(
JNIEnv*env,
jobject/* this */) {
std::stringhello ="Hello from C++";
returnenv->NewStringUTF(hello.c_str());
可以看到方法声明由extern “C”开始, extern "C"的主要作用就是为了能够正确实现C++代码调用其他C语言代码。加上extern "C"后,会指示编译器这部分代码按C语言的进行编译,而不是C++的。这是由于C++会将函数的参数类型也加到编译后的文件,而C不会。所以如果我们需要在C++中使用C编写的代码、开源库就需要使用extern “C”声明。
然后是JNIEXPORT jstring JNICALL声明,jstring对应了java中的java.lang.String,而在jstring之前的JNIEXPORT则是由jni定义的一个宏(这一部分不感兴趣可以跳过)。JNIEXPORT的定义如下:
#defineJNIIMPORT
#defineJNIEXPORT__attribute__((visibility ("default")))
#defineJNICALL
这个宏是设置属性visibility(可见性),这个属性的作用是防止重复符号的问题。比如说编译的时候指定了-fvisibility=hidden那么没有这个声明的函数会被隐藏,无法让外部调用。而JNICALL宏从上面可以看到是一个空。一般的我们的java native方法的实现会在返回值前后加上这两个宏。当然对于自己app库直接使用而不需要让别的库link(依赖)来说不加这两个宏也可以。
在返回值后是函数名,函数名格式为:Java_包名_类名_方法名。包名的‘.’替换成‘_’,如果包名、类名或方法名本身存在‘_’则用1来区分
如我们的包名是com.dongnao.hello_jni则需要写成:com_dongnao_hello_1jni
我们的这个java native方法是没有任何参数的,但是在jni中我们可以在函数中接受一个JNIEnv与一个jobject。
JNIEnv在c与c++中有不同的定义(这里我们只看C++前面也说了没接触过C/C++的java开发,C++入门更简单)的。C++中定义为_JNIEnv结构体(其实结构体中存在一个JNINativeInterface指针,而C是直接将JNIEnv定义为JNINativeInterface指针)。在C++中需要通过这个结构体去调用JNI函数,如:调用java方法、创建java类对象等,也就是通过JNIEnv来达到与java交互的作用。
而jobject则是当前类的jni对象,如果native方法是static的则jobject对应了MainActivity的class对象,如果不是static则对应了MainActivity的实例对象。
在然后就是方法体中的实现。
嗯,基本的还有java与jni对象的对应关系,这个再我看来不用管,一个是用的多了自然记住了,如果记不住可以利用ide提供的indexer进去查看一眼就明白了,比如我们看jchar:
typedefuint16_tjchar;
typedef__uint16_t uint16_t;
typedef unsigned short__uint16_t;
所以java当中的char对应了C/C++中的unsigned short(无符号short, -1是有符号,1是无符号)。char在java是两个字节,short在C/C++也是两个字节。而java中的byte占1个字节,对应了jni中的jbyte->signed char。
其次还有关于C/C++调用Java方法时候关于java方法签名的问题。这个也没什么可说的,一张图
如果实在不知道,javap命令查看class文件就行了。比如在as中我们cd到
然后执行:
现在可以让我们开始在android中利用ndk,在C++中编写我们的“java”代码。
9�|.��