Android进阶之路(二) -- NDK初探

继续学习NDK开发,今天来实现一个简单的计算器功能,NativeUtil类中有一个静态的native方法,它接收三个参数,分别是两个操作数和一个操作符,并且返回C的计算结果。


NativeUtil类定义如下

public class NativeUtil {
    static {
        System.loadLibrary("native-lib");
    }

    public static int ADD = 0;
    public static int SUB = 1;
    public static int MULTI = 2;
    public static int DIVISION = 3;

    public static native int calculate(int arg0,int arg1,int symbol);
}
其中,ADD,SUB,MULTI,DIVISION,分别表示加减乘除,C中取出arg0和arg1的值,根据symbol和NativeUtil的静态成员变量对比,判断进行哪种操作,然后返回计算结果。

在动手之前,我们先要解决几个问题:
1、C怎么获取Java层的数据类型?
2、C怎么访问NativeUtil的成员变量?


要解决第一个问题,我们得先了解C与Java数据类型的转换,转换表如下:

Java 类型 本地类型 描述
boolean jboolean C/C++8位整型
byte jbyte C/C++带符号的8位整型
char jchar C/C++无符号的16位整型
short jshort C/C++带符号的16位整型
int jint C/C++带符号的32位整型
long jlong C/C++带符号的64位整型e
float jfloat C/C++32位浮点型
double jdouble C/C++64位浮点型
Object jobject 任何Java对象,或者没有对应java类型的对象
Class jclass Class对象
String jstring 字符串对象
Object[] jobjectArray 任何对象的数组
boolean[] jbooleanArray 布尔型数组
byte[] jbyteArray 比特型数组
char[] jcharArray 字符型数组
short[] jshortArray 短整型数组
int[] jintArray 整型数组
long[] jlongArray 长整型数组
float[] jfloatArray 浮点型数组
double[] jdoubleArray 双浮点型数组

Java中int型的数据,在C中就会用jint表示。

还有很关键的一点是方法签名,方法签名的作用是唯一确定一个方法,因为方法可以有重载,所以仅靠方法的名称是无法准确定位一个方法的,这个时候就需要方法签名了,方法签名实际上就是方法返回值和参数表的组合,它的规则如下:

Android进阶之路(二) -- NDK初探_第1张图片
使用时的形式是(参数类型1参数类型2……)返回类型
比如:
public int calculate(int arg0,int arg1,int symbol);
它的方法签名就是(III)I

C如何访问NativeUtil的成员变量呢?
这就需要请出JNI大总管JNIEnv了,JNIEnv负责Java与C的交互,比如从调用Java层的方法、访问Java层的变量,都需要JNIEnv的参与。
大致阅读源码可知JNIEnv在C和C++中分别有不同实现,在C中JNIEnv是JNINativeInterface的一个指针类型,在C++中JNIEnv是一个结构体,它们的共同点是都与JNINativeInterface有关,JNINativeInterface中定义了很多指针,C/C++就通过这些指针调用Java层函数,详细的内容会在后面源码部分介绍。
查阅文档可以知道,JNIEnv中有一个 GetStaticIntField(jclass clazz, jfieldID fieldID)函数,第一个参数是需要获取成员变量的类,第二个参数是字段ID。

字段ID如何获取呢?继续查阅文档,我们又找到了GetStaticFieldID(jclass clazz, const char* name, const char* sig)函数,它的三个参数分别是需要获取成员变量的类,成员变量名和签名。

与此相同,JNIEnv中还提供了很多很多获取变量信息、获取方法信息的函数。


由此,就可以开始编写C代码了,写出的的代码如下:
#include 

extern "C"
JNIEXPORT jint JNICALL
Java_com_tustcs_ndktest_NativeUtil_calculate(JNIEnv *env, jclass type, jint arg0, jint arg1,
                                             jint symbol) {
    if(symbol == env->GetStaticIntField(type,env->GetStaticFieldID(type,"ADD","I")))
        return arg0 + arg1;
    else if(symbol == env->GetStaticIntField(type,env->GetStaticFieldID(type,"SUB","I")))
        return arg0 - arg1;
    else if(symbol == env->GetStaticIntField(type,env->GetStaticFieldID(type,"MULTI","I")))
        return arg0 * arg1;
    else if(symbol == env->GetStaticIntField(type,env->GetStaticFieldID(type,"DIVISION","I")))
        return arg0 / arg1;
}

然后在CMakeLists.txt中添加路径,再去MainActivity调用。。嗯,大功告成。

Android进阶之路(二) -- NDK初探_第2张图片


今天附上关于静态块和成员变量初始化的小知识:

public class InitTest {
	public static int sInt1 = 10;
	public static int sInt2;
	public int mInt1 = 30;
	public int mInt2;
	//局部代码块
	{
		if(mInt1 == 30) {
			System.out.println("成员变量mInt1已完成初始化:mInt1 = " + mInt1);
		}
		mInt2 = 40;
		System.out.println("代码块mInt2初始化:mInt2 = " + mInt2);
	}
	//静态块
	static{
		sInt2 = 20;
		System.out.println("静态块sInt2初始化:sInt2 = " + sInt2);
	}
	public static void print() {
		System.out.println("静态方法:print()调用");
	}	
}
主方法执行:
public static void main(String[] args) {
		InitTest.print();
		//InitTest test = new InitTest(); 
}
在主函数调用InitTest的静态方法,结果如下:

Android进阶之路(二) -- NDK初探_第3张图片

我们可以看到,调用类的静态方法不会调用局部代码块,而会先调用静态块。

去掉注释,实例化一个类试试

public static void main(String[] args) {
		//InitTest.print();
		InitTest test = new InitTest(); 
}
运行结果如下:

Android进阶之路(二) -- NDK初探_第4张图片

我们可以看到,实例化一个类时,会优先调用静态块,再调用代码块和给成员变量赋值。

那么,代码块和给成员变量赋值哪个先执行呢?

答案是从头开始执行,程序运行到哪里,就先调用哪里,按顺序执行。

你可能感兴趣的:(安卓进阶)