Android Ndk开发进阶

  • Post author:
  • Post category:其他


前言

上一篇文章,我们讲了Ndk的基本用法即Java调用C方法返回基本类型或String类型(引用类型)。接下来,我们就要讲到C调用Java类里面的方法包括静态方法,然后用C来处理,Java代码中传入的对象,通过C代码处理过后,返回该对象。好了,话不多说,新建项目。

创建工程

这里写图片描述

通过上一篇blog介绍,大家都应该会创建项目了吧。不会的回去看看上一篇文章。这里多创建了一个MyInfo类。用于在创建对象Java与C中相互传递。如图:

这里写图片描述

实体类里面的字段有基本类型,也有引用类型(String类型)。对于NDK开发来说,接收一个MyInfo实体类对象和该对象内的引用类型属性来说,都是jobject类型。所以大家不要搞混了。

项目中H文件与C文件,根据命令和提示创建,创建的依据是,调用jni时写的方法。如下图:

这里写图片描述

这个大家一看就明白,创建JNI方法。目前Android studio 对于Ndk开发编程不能够很好的支持,所以,你会看到,即便是项目完成后成功运行,JNI方法也会标红报错。不过只要能运行,不管那么多了。再就是创建出H文件,作用类似于Java的interface(接口),C文件“实现”H文件,include进去。

创建h文件,在工程目录下我们用的是javah -jni 全类名(包名➕雷明),自动创建H文件。这里的全类名就是com.china.MainActivity。这样我们就生成了。如下图:

这里写图片描述

在MainActivity中通过提示创建出C文件,并引用(include)刚才我们生成的h文件。如下图:
这里写图片描述

接下来,就开始我们的数据操作了。

C调用Java

首先,我们要确定在C中要调用java中的什么方法。这样我们就在MainActivity中写个加法运算方法add(int x,int y)。

public int add(int x, int y) {
        Toast.makeText(this, "x+y=" + (x + y), Toast.LENGTH_SHORT).show();
        return x + y;
    }

同理,在C中有对应的调用方法

/*
 * Class:     com_china_MainActivity
 * Method:    getInt
 * Signature: (II)I
 */
JNIEXPORT jint
JNICALL Java_com_china_MainActivity_getInt
        (JNIEnv *env, jobject jobj, jint ji, jint jj) {
        //TODO
};

我们要使C中能够调用java,那么就需要先去调用c的方法。然后c中的方法再调用java类里面暴露给C调用的方法。(有点绕啊,��)Java->C->Java. 对应的,在java中调用 public native int getInt(int x, int y);//。然后在C代码中操作使其调用MainActivity中的add(x,y)方法。开始吧。

JNIEXPORT jint JNICALL Java_com_china_MainActivity_getInt
        (JNIEnv *env, jobject jobj, jint ji, jint jj) {
    jclass jclazz = (*env)->GetObjectClass(env, jobj);
//    jclass jclazz = (*env)->FindClass(env, "com/china/MainActivity");
//2.得到方法
//jmethodID   (*GetMethodID)(JNIEnv*, jclass, const char*, const char*);
//最后一个参数是方法签名
    jmethodID jmethodIDs = (*env)->GetMethodID(env, jclazz, "add", "(II)I");
//3.实例化该类
// jobject     (*AllocObject)(JNIEnv*, jclass);
//    jobject jobject = (*env)->AllocObject(env, jclazz);
//4.调用方法
//jint        (*CallIntMethod)(JNIEnv*, jobject, jmethodID, ...);
    jint value = (*env)->CallIntMethod(env, jobj, jmethodIDs, ji, jj);
    return value;
}

这里要注意的是,我们让c调用java代码,那么就要让c知道是调用哪个类的哪个方法。哪个类呢?注意

jclass jclazz = (*env)->FindClass(env, “com/china/MainActivity”); 这句话表示,获取到MainActivity类是“静态的”,我们知道,Activity是有生命周期的类,普通类是没有的。这里理解为“静态的”。所以还有一个方法,得到这个类的引用:jclass jclazz = (*env)->GetObjectClass(env, jobj); 传入的jobj就是该方法传入的jobject(类似于java中this引用之类的意思)。得到这个类后,我们还要通过操作调用到MainActivity中的add方法。

这里的操作就是获得该方法的签名。获得方法签名,我们会用到javap命令。javap -s 全类名(包名加类名)。通过GetMethodID(xx,xx,xx,xx),第一二个参数就是env(C中的指针引用),获得的java类的引用。第三个参数就是C要调用的方法名。第四个就是该方法签名。然后,就是最后一步了。调用。(

CallIntMethod)(JNIEnv

, jobject, jmethodID, …);方法前几个参数。都有了,后面还有个可变参数。就是我们调用java方法的传入之。这里,java与C中int等基本类型通用的。所以可以直接丢进去。完了。

简单吧。接下来更难的来了。

传递java对象

在MAinActivity中先初始化一个MyInfo对象出来,并且赋值。

private void init() {
        mInfo = new MyInfo();
        mInfo.setAge(20);
        mInfo.setName("renk");
    }

调用jni方法。 Log.e(“renk”, “age=” + getAge(mInfo));当然在点击事件里面调用了哦。在来看C中怎么操作对象,并且将对象里面的age取出来,加上100,返回给mainActivity。打印出来。

刚才,也讲了。c中是不能直接操作java对象或者类的。但是,我们可以换个思路,在C中通过该对象的类得到相应的属性。然后在C中创建一个结构体,来存传进来的对象里面的值。好,先把结构图创建出来。

当然如果不知道结构体是什么的同学,建议复习一下C知识。

typedef struct {
    _Bool gen;
    int ag;
    char str[255];
} MyCInfo;
//创建一个结构
MyCInfo myCInfo;

创建出一个结构体后,我们就会在c方法中使用它,并且对其赋值,这里我们创建的时候,由于C没有String类型,故使用了char[]来处理字符串类型的数据。下面看具体取值、赋值、计算并返回结果的代码:

这里写图片描述

还是刚才的操作,先拿到MainActivity的对象的引用。然后,再获取传入对象的的类的相关jclass,属性id(fieldId),当然这里面同样有用到方法签名,属性签名。属性名称。。不用多做解释。主要看取值。myCInfo.gen = (

env)->GetBooleanField(env, obj, jfiGen);取值是个个的来,有不同方法,这顾名思义是去boolean值得性别值。还有,GetIntField()/GetLongField()/Get…等等。要去String属性值,就用到GetObjectField(),第一二个参数不多说,第三个参数就是该属性的属性Id值。这里还要讲jstring转换为C可操作的字符串。类型为char

。通过这句话myCInfo.ag += 100;我们就实现了。将传入的年龄加上100.这边就可直接返回了。Ok。相关Get系列方法包括静态的见下图:

这里写图片描述

这里还有多说一点的,就是,怎样将C中的结构体内容赋给java对象,让其返回呢。同理如下代码。

//赋值
    jobject joInfo = (*env)->AllocObject(env, jclazz);
    (*env)->SetBooleanField(env, joInfo, jfiGen, myCInfo.gen);
    (*env)->SetIntField(env, joInfo, jfiAG, myCInfo.ag);
    jstring jstrTmp = (*env)->NewStringUTF(env, myCInfo.str);
    (*env)->SetObjectField(env, joInfo, jfiNam, jstrTmp);
    (*env)->SetObjectField(env, jobj, jInfofieldid, joInfo);

很好理解了,通过上面的学习,这个很容易看懂的额。先通过类,创建其对象,然后通过Set系列方法,分别赋值给该jobject对象。然后return joInfo,就over了。不多说了。

最开始讲到的还要通过C调用java静态方法,这个也很简单,就是把调用的方法CallStatic***Method系列方法,就完成了,其他都不变。相关方法下面给出:

这里写图片描述

还有一个功能就是,怎样让C去改变activity中的某个属性的值呢。哈哈,如果看了上面的东西,到这里都应该会了哈。代码如下:

/*
 * Class:     com_china_MainActivity
 * Method:    changeString
 * Signature: ()Ljava/lang/String;
 */
JNIEXPORT jstring
JNICALL Java_com_china_MainActivity_changeString
        (JNIEnv *env, jobject jobj) {
    jclass jclazz = (*env)->GetObjectClass(env, jobj);
    jfieldID jfieldid = (*env)->GetFieldID(env, jclazz, "mString", "Ljava/lang/String;");
    jstring jstr = (jstring)(*env)->GetObjectField(env, jobj, jfieldid);
    char *mString = jString2cString(env, jstr);
    char *add = " add c";
    strcat(mString, add);
    jstring encoding = (*env)->NewStringUTF(env, mString);
    (*env)->SetObjectField(env, jobj, jfieldid, encoding);
    return encoding;
}

同样的通过传入的jobject,获得该jclass的引用。获取该类的属性Id(fieldID),然后转换赋值或追加都可以,随你。最后别忘啦,调用Set系列方法。就实现了我们想要的功能。

好了,讲完了,有什么不懂的额,在线留言哦 。最后贴一下,C中的代码。谢谢。

#include <jni.h>
#include <string.h>
#include "com_china_MainActivity.h"


char *jString2cString(JNIEnv *env, jstring jstr) {
    char *mStr = "";
    jclass jclazz = (*env)->FindClass(env, "java/lang/String");
    jmethodID jmethodid = (*env)->GetMethodID(env, jclazz, "getBytes", "(Ljava/lang/String;)[B");
    jstring encode = (*env)->NewStringUTF(env, "GB2312");
    jbyteArray jarry = (jbyteArray)(*env)->CallObjectMethod(env, jstr, jmethodid, encode);
    jsize length = (*env)->GetArrayLength(env, jarry);
    jbyte *jby = (*env)->GetByteArrayElements(env, jarry, JNI_FALSE);
    if (length > 0) {
        mStr = (char *) malloc(length + 1);
        memcpy(mStr, jby, length);
        mStr[length] = 0;
    }
    (*env)->ReleaseByteArrayElements(env, jarry, jby, 0);
    return mStr;
}

typedef struct {
    _Bool gen;
    int ag;
    char str[255];
} MyCInfo;

MyCInfo myCInfo;

/*
 * Class:     com_china_MainActivity
 * Method:    getString
 * Signature: (Lcom/china/MyInfo;)Ljava/util/List;
 * public native List<String> getString(MyInfo info);
 */
JNIEXPORT void

JNICALL Java_com_china_MainActivity_getString
        (JNIEnv *env, jobject jobj, jobject obj) {
    MyCInfo jinfo ;
    jclass jclazz = (*env)->FindClass(env,"com/china/MyInfo");
    jfieldID jinfoGender = (*env)->GetFieldID(env,jclazz,"gender","Z");
    jfieldID jinfoage = (*env)->GetFieldID(env,jclazz,"age","I");
    jfieldID jinfoname = (*env)->GetFieldID(env,jclazz,"name","Ljava/lang/String;");
    jinfo.gen = (*env)->GetBooleanField(env,obj,jinfoGender);
    jinfo.ag = (*env)->GetIntField(env,obj,jinfoage);
    jstring jstr = (jstring)(*env)->GetObjectField(env, obj, jinfoname);
    char *str = (*env)->GetStringUTFChars(env, jstr, 0);
    strcpy(jinfo.str, str);
    (*env)->ReleaseStringUTFChars(env, jstr, str);
    str ="";
    jclass jclazz2 = (*env)->GetObjectClass(env, jobj);
    jmethodID jmethodIDs = (*env)->GetMethodID(env, jclazz2, "getInfoName", "(Ljava/lang/String;)V");
    (*env)->CallVoidMethod(env,jobj,jmethodIDs,jstr);
};

/*
 * Class:     com_china_MainActivity
 * Method:    getAge
 * Signature: (Lcom/china/MyInfo;)I
 * public native int getAge(MyInfo info);
 */
JNIEXPORT jint

JNICALL Java_com_china_MainActivity_getAge
        (JNIEnv *env, jobject jobj, jobject obj) {
    jclass jclaz = (*env)->GetObjectClass(env, jobj);

    jclass jclazz = (*env)->FindClass(env, "com/china/MyInfo");
    jfieldID jfiGen = (*env)->GetFieldID(env, jclazz, "gender", "Z");
    jfieldID jfiAG = (*env)->GetFieldID(env, jclazz, "age", "I");
    jfieldID jfiNam = (*env)->GetFieldID(env, jclazz, "name", "Ljava/lang/String;");
    jfieldID jInfofieldid = (*env)->GetFieldID(env, jclaz, "mInfo", "Lcom/china/MyInfo;");
    //取值
    myCInfo.gen = (*env)->GetBooleanField(env, obj, jfiGen);
//  myCInfo.ag = (*env)->GetIntField(env,obj,jfiAG);
    jstring jstr = (jstring)(*env)->GetObjectField(env, obj, jfiNam);
    char *str = (*env)->GetStringUTFChars(env, jstr, 0);
    strcpy(myCInfo.str, str);
    (*env)->ReleaseStringUTFChars(env, jstr, str);
    str = "";
    myCInfo.ag += 100;

    //赋值
    jobject joInfo = (*env)->AllocObject(env, jclazz);
    (*env)->SetBooleanField(env, joInfo, jfiGen, myCInfo.gen);
    (*env)->SetIntField(env, joInfo, jfiAG, myCInfo.ag);
    jstring jstrTmp = (*env)->NewStringUTF(env, myCInfo.str);
    (*env)->SetObjectField(env, joInfo, jfiNam, jstrTmp);
    (*env)->SetObjectField(env, jobj, jInfofieldid, joInfo);

    return myCInfo.ag;
};

/*
 * Class:     com_china_MainActivity
 * Method:    changeString
 * Signature: ()Ljava/lang/String;
 */
JNIEXPORT jstring

JNICALL Java_com_china_MainActivity_changeString
        (JNIEnv *env, jobject jobj) {
    jclass jclazz = (*env)->GetObjectClass(env, jobj);
    jfieldID jfieldid = (*env)->GetFieldID(env, jclazz, "mString", "Ljava/lang/String;");
    jstring jstr = (jstring)(*env)->GetObjectField(env, jobj, jfieldid);
    char *mString = jString2cString(env, jstr);
    char *add = " add c";
    strcat(mString, add);
    jstring encoding = (*env)->NewStringUTF(env, mString);
    (*env)->SetObjectField(env, jobj, jfieldid, encoding);
    return encoding;
};

/*
 * Class:     com_china_MainActivity
 * Method:    changeNumber
 * Signature: ()I
 */
JNIEXPORT jint

JNICALL Java_com_china_MainActivity_changeNumber
        (JNIEnv *env, jobject jobj) {
    jclass jclazz = (*env)->GetObjectClass(env, jobj);
    jfieldID jfieldid = (*env)->GetFieldID(env, jclazz, "mNumber", "I");
    jint aa = (*env)->GetIntField(env, jobj, jfieldid);
    aa += 100;
    (*env)->SetIntField(env, jobj, jfieldid, aa);
    return aa;
};

/*
 * Class:     com_china_MainActivity
 * Method:    testGetString
 * Signature: (Ljava/lang/String;)Ljava/lang/String;
 * public native String testGetString(String test);
 */
JNIEXPORT jstring

JNICALL Java_com_china_MainActivity_testGetString
        (JNIEnv *env, jobject jobj, jstring jstr) {

};

/*
 * Class:     com_china_MainActivity
 * Method:    getInt
 * Signature: (II)I
 */
JNIEXPORT jint

JNICALL Java_com_china_MainActivity_getInt
        (JNIEnv *env, jobject jobj, jint ji, jint jj) {
    jclass jclazz = (*env)->GetObjectClass(env, jobj);
//    jclass jclazz = (*env)->FindClass(env, "com/china/MainActivity");
//2.得到方法
//jmethodID   (*GetMethodID)(JNIEnv*, jclass, const char*, const char*);
//最后一个参数是方法签名
    jmethodID jmethodIDs = (*env)->GetMethodID(env, jclazz, "add", "(II)I");
//3.实例化该类
// jobject     (*AllocObject)(JNIEnv*, jclass);
//    jobject jobject = (*env)->AllocObject(env, jclazz);
//4.调用方法
//jint        (*CallIntMethod)(JNIEnv*, jobject, jmethodID, ...);
    jint value = (*env)->CallIntMethod(env, jobj, jmethodIDs, ji, jj);
    return value;
}



版权声明:本文为liguochina原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。