JNI基础

AS新建C++工程

在新建项目的面板,勾选Include C++ support,新建的项目自动包含C++配置。

C++生成Java中定义的native方法

新生成的项目已包含C++生成Java的native方法范本,新建的native方法,只需使用AS快捷键Alt+Enter自动生成。如:

public native String stringFromJNI();

在c++中注册的方法为:

extern "C"
JNIEXPORT jstring JNICALL Java_com_example_myjnitest_MainActivity_stringFromJNI(
        JNIEnv *env,
        jobject /* this */) {
    std::string hello = "Hello from C++";
    return env->NewStringUTF(hello.c_str());
}

C++生成的方法为:extern "C" JNIEXPORT {返回类型} JNICALL {方法名} {方法体}
方法名对应格式为:Java_全路径名类名_方法名
参数必须包含两个默认参数JNIEnv *env,jobject instance
若Java方法包含参数,则c++方法在上面两个参数后面新增对应类型的参数

C++中获取方法中的参数

获取Java参数

  • 获取int和String参数
    native String test0(int i,String s);
extern "C"
JNIEXPORT jstring JNICALL
Java_com_example_myjnitest_MainActivity_test0(JNIEnv *env, jobject instance, jint i, jstring s_) {
    LOGE("获取java中int数据:%d",i);
    //获取java的String
    const char* s = env->GetStringUTFChars(s_,0);
    LOGE("获取java中String数据:%s",s);
    env->ReleaseStringUTFChars(s_, s);
    return env->NewStringUTF(s);
}
  • 获取int数组和String数组参数
    native void test1(int[] i,String[] j);
extern "C"
JNIEXPORT void JNICALL
Java_com_example_myjnitest_MainActivity_test1(JNIEnv *env, jobject instance, jintArray i_,
                                              jobjectArray j) {
    //遍历int数组
    jint *i = env->GetIntArrayElements(i_,NULL);
    int32_t length = env->GetArrayLength(i_);
    for (int k = 0; k < length; ++k) {
        LOGE("遍历java的int数组:%d",*(i+ k));
    }
    env->ReleaseIntArrayElements(i_,i,0);

    //遍历String数组
    int32_t strLength = env->GetArrayLength(j);
    for (int k = 0; k < strLength; ++k) {
        jstring str = static_cast<jstring>(env->GetObjectArrayElement(j, k));
        const char* s = env->GetStringUTFChars(str,0);
        LOGE("获取java的String:%s",s);
        env->ReleaseStringUTFChars(str,s);
    }
}
  • 获取Java中的自定义Bean参数
native void test2(Bean bean);
extern "C"
JNIEXPORT void JNICALL
Java_com_example_myjnitest_MainActivity_test2(JNIEnv *env, jobject instance, jobject bean) {
    //1.反射获取java对应的对象
    jclass beanCls = env->GetObjectClass(bean);
    //2.反射获取要调用的方法,第三个参数为签名
    jmethodID getName = env->GetMethodID(beanCls,"getName","()Ljava/lang/String;");
    //3.调用
    jstring name = static_cast<jstring>(env->CallObjectMethod(bean, getName));
    const char* nameStr = env->GetStringUTFChars(name,NULL);
    LOGE("获取到Bean的name:%s" ,nameStr);
    env->ReleaseStringUTFChars(name,nameStr);

    //获取setAge方法修改age参数,调用getAge方法获取age的值
    jmethodID setAge = env->GetMethodID(beanCls,"setAge","(I)V");
    env->CallVoidMethod(bean,setAge,88);
    jmethodID getAge = env->GetMethodID(beanCls,"getAge","()I");
    int32_t age = env->CallIntMethod(bean,getAge);
    LOGE("修改Bean的age:%d",age);
}

参数签名对照表:

Java类型 签名
boolean Z
short S
float F
byte B
int I
double D
char C
long J
void V
引用类型 L + 全限定名 + ;
数组 [+类型签名

C++中调用Java类的方法

extern "C"
JNIEXPORT void JNICALL
Java_com_example_myjnitest_MainActivity_test3(JNIEnv *env, jobject instance) {
    //构造方法创建对象
    jclass beanCls = env->FindClass("com/example/myjnitest/Bean");
    jmethodID constuct = env->GetMethodID(beanCls,"<init>","()V");
    jobject bean = env->NewObject(beanCls,constuct);
    //修改属性值
    jfieldID nameId = env->GetFieldID(beanCls,"name","Ljava/lang/String;");
    jstring nameStr = env->NewStringUTF("张学友");
    env->SetObjectField(bean,nameId,nameStr);
    jfieldID ageId = env->GetFieldID(beanCls,"age","I");
    env->SetIntField(bean,ageId,666);
    //调用Bean中的方法
    jmethodID print = env->GetMethodID(beanCls,"print","()V");
    env->CallVoidMethod(bean,print);
}

JNI_OnLoad

调用System.loadLibrary()方法时,内部会查找so中的JNI_OnLoad方法,存在则调用。

动态注册

之前Java方法与C++方法匹配的方式,叫做静态注册。
另外,还可以使用动态注册,自定义方法名。

    native void test4(int i);
    native String test5(String s);
//对应Java的test4方法
void test4444(JNIEnv *env, jobject instance, jint i) {
    LOGE("调用test4:%d", i);
}

//对应Java的test5方法
jstring test55555(JNIEnv *env, jobject instance, jstring s) {
    return env->NewStringUTF("返回test5");
}

//需要动态注册的方法数组
static const JNINativeMethod mMethods[] = {
        {"test4", "(I)V",                                   (void *) test4444},
        {"test5", "(Ljava/lang/String;)Ljava/lang/String;", (jstring *) test55555}
};

//需要动态注册native方法的类名
static const char *mClassName = "com/example/myjnitest/MainActivity";

jint JNI_OnLoad(JavaVM *vm, void *reserved) {
    JNIEnv *env = NULL;
    //获取JNIEnv
    int r = vm->GetEnv((void **) &env, JNI_VERSION_1_4);
    if (r != JNI_OK) {
        return -1;
    }
    jclass mainActivityCls = env->FindClass(mClassName);
    r = env->RegisterNatives(mainActivityCls, mMethods, sizeof(mMethods) / sizeof(JNINativeMethod));
    if (r != JNI_OK) {
        return -1;
    }
    return JNI_VERSION_1_4;
}

native线程

native调用java需要使用JNIEnv这个结构体,而JNIEnv是由Jvm传入与线程相关的变量。可以通过JavaVM的AttachCurrentThread方法来获取到当前线程中的JNIEnv指针。
c++多线程调用MainActivity更新UI方法:

    native void testThread();

    public void updateUI() {
        if (Looper.myLooper() == Looper.getMainLooper()) {
            Toast.makeText(this, "C++更新UI", Toast.LENGTH_SHORT).show();
        }else {
            runOnUiThread(new Runnable() {
                @Override
                public void run() {
                    Toast.makeText(MainActivity.this, "C++更新UI", Toast.LENGTH_SHORT).show();
                }
            });
        }
    }
struct Context {
    jobject activity;
};

JavaVM *_vm;  //JNI_OnLoad中赋值

void* threadTask(void *args) {
    //native线程 附加到 Java虚拟机
    JNIEnv *env;
    jint i = _vm->AttachCurrentThread(&env,0);
    if(i != JNI_OK){
        return 0;
    }
    //获取传入的MainActivity对象,调用MainActiviy中的updateUI方法
    Context *context = static_cast<Context *>(args);
    jclass cls = env->GetObjectClass(context->activity);
    jmethodID updateUI = env->GetMethodID(cls,"updateUI","()V");
    env->CallVoidMethod(context->activity,updateUI);


    env->DeleteGlobalRef(context->activity);
    //释放资源
    delete(context);
    context = 0;

    //分离
    _vm->DetachCurrentThread();

    return 0;
}

//动态注册
void testThread(JNIEnv *env,jobject instance) {
    pthread_t pid;
    Context *context = new Context;
    context->activity = env->NewGlobalRef(instance);
    pthread_create(&pid,0,threadTask,context);
}
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 162,825评论 4 377
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 68,887评论 2 308
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 112,425评论 0 255
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 44,801评论 0 224
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 53,252评论 3 299
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 41,089评论 1 226
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 32,216评论 2 322
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 31,005评论 0 215
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,747评论 1 250
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,883评论 2 255
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,354评论 1 265
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,694评论 3 265
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,406评论 3 246
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,222评论 0 9
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,996评论 0 201
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 36,242评论 2 287
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 36,017评论 2 281

推荐阅读更多精彩内容