理解Android中的JNI

什么是JNI

JNI是Java Native Interface的缩写(Java本地调用),Java程序中的函数可以调用Native语言写的函数(一般指的是C/C++编写的函数),Native语言写的函数可以调用Java层的函数。

JNI结构示意图

为什么要有JNI

Java语言的跨平台是因为在不同平台上可以运行Java虚拟机,而虚拟机是跑在具体平台上的,而本质上Java是通过JNI技术实现的跨平台,很多基层的模块在Java语言诞生之前已经有了比较优秀的实现,为了避免重复造轮子所以我们要使用JNI技术来使用已有的模块。

Mac OS上的环境搭建

在这里说明一下Max OS上的所需环境搭建,Windows和Linux的请搜索相关资料。

  1. 安装JDK(此处省略)。
  2. 安装ADT(Android Develop Tools),包括SDK和ADT插件,下载地址:http://pan.baidu.com/s/1o6OBIHG
  3. 安装Xcode可以去苹果商店下载安装(免费)。
  4. 安装Apache ANT(下载地址:http://ant.apache.org/bindownload.cgi)详细安装过程可以参考:http://blog.csdn.net/song_hui_xiang/article/details/14315529
  5. 安装GNU Make(默认已经安装,所以不用安装)可以使用 make -version命令验证是否安装。
  6. 安装NDK(下载地址:http://pan.baidu.com/s/1i3l5L8T),解压后在用户根目录下新建文件.bash_profile然后添加如下两行(配置环境变量,可以暂时不配置)。

    export ANDROID_NDK_HOME=/Users/lixiaoqiang/Documents/install_tools/ndk/android-ndk-r10c  
    export PATH=${PATH}:${ANDROID_NDK_HOME}  
    

注意:后面的地址是你解压后的目录

关于上面部分开发工具简要介绍:

  1. Apache Ant,是一个将软件编译、测试、部署等步骤联系在一起加以自动化的一个工具,大多用于Java环境中的软件开发。
  2. NDK是Android原生开发工具包,可以支持C/C++等原生编程语言开发Android应用,它提供头文件、库和交叉编译工具链。

第一个示例程序

转载请说明出处:https://lxqxsyu.gitlab.io

  1. 为eclipse指定NDK路径

为eclipse指定NDK路径

  1. 导入Android NDK中的示例代码(导入hello-jni工程),做过Android开发的朋友应该很熟悉,这里就不啰嗦了。

导入hello-jni工程

  1. 向项目中添加原生支持 项目——>右击——>Android Tools——>Add Native Support 该项目其实已经包含了一个原生项目,所以这一步可以跳过,我们直接Finish继续。
  2. 插上手机(模拟器太慢了,建议使用真机)运行项目。在C/C++界面视图我们可以看到如下信息

    **** Build of configuration Default for project HelloJni ****  
    
    /Users/lixiaoqiang/Documents/install_tools/ndk/android-ndk-r10c/ndk-build all   
    Android NDK: WARNING: APP_PLATFORM android-19 is larger than android:minSdkVersion 3 in ./AndroidManifest.xml      
    [arm64-v8a] Gdbserver      : [aarch64-linux-android-4.9] libs/arm64-v8a/gdbserver  
    [arm64-v8a] Gdbsetup       : libs/arm64-v8a/gdb.setup  
    [x86_64] Gdbserver      : [x86_64-4.9] libs/x86_64/gdbserver  
    [x86_64] Gdbsetup       : libs/x86_64/gdb.setup  
    [mips64] Gdbserver      : [mips64el-linux-android-4.9] libs/mips64/gdbserver  
    [mips64] Gdbsetup       : libs/mips64/gdb.setup  
    [armeabi-v7a] Gdbserver      : [arm-linux-androideabi-4.6] libs/armeabi-v7a/gdbserver  
    [armeabi-v7a] Gdbsetup       : libs/armeabi-v7a/gdb.setup  
    [armeabi] Gdbserver      : [arm-linux-androideabi-4.6] libs/armeabi/gdbserver  
    [armeabi] Gdbsetup       : libs/armeabi/gdb.setup  
    [x86] Gdbserver      : [x86-4.6] libs/x86/gdbserver  
    [x86] Gdbsetup       : libs/x86/gdb.setup  
    [mips] Gdbserver      : [mipsel-linux-android-4.6] libs/mips/gdbserver  
    [mips] Gdbsetup       : libs/mips/gdb.setup  
    [arm64-v8a] Compile        : hello-jni <= hello-jni.c  
    [arm64-v8a] SharedLibrary  : libhello-jni.so  
    [arm64-v8a] Install        : libhello-jni.so => libs/arm64-v8a/libhello-jni.so  
    [x86_64] Compile        : hello-jni <= hello-jni.c  
    [x86_64] SharedLibrary  : libhello-jni.so  
    [x86_64] Install        : libhello-jni.so => libs/x86_64/libhello-jni.so  
    [mips64] Compile        : hello-jni <= hello-jni.c  
    [mips64] SharedLibrary  : libhello-jni.so  
    [mips64] Install        : libhello-jni.so => libs/mips64/libhello-jni.so  
    [armeabi-v7a] Compile thumb  : hello-jni <= hello-jni.c  
    [armeabi-v7a] SharedLibrary  : libhello-jni.so  
    [armeabi-v7a] Install        : libhello-jni.so => libs/armeabi-v7a/libhello-jni.so  
    [armeabi] Compile thumb  : hello-jni <= hello-jni.c  
    [armeabi] SharedLibrary  : libhello-jni.so  
    [armeabi] Install        : libhello-jni.so => libs/armeabi/libhello-jni.so  
    [x86] Compile        : hello-jni <= hello-jni.c  
    [x86] SharedLibrary  : libhello-jni.so  
    [x86] Install        : libhello-jni.so => libs/x86/libhello-jni.so  
    [mips] Compile        : hello-jni <= hello-jni.c  
    [mips] SharedLibrary  : libhello-jni.so  
    [mips] Install        : libhello-jni.so => libs/mips/libhello-jni.so  
    
    **** Build Finished ****  
    

这个过程其实就是在构建原生组件并和Java应用程序打包的过程。此时在我们手机上就可以看到一行文字

模拟器显示结果

项目结构及主要目录介绍

项目结构

  1. jni目录:包含原生组件的源代码及描述原生组件构建方法的Make文件(Android.mk),该目录作为NDK构建项目时的构建目录。
  2. libs目录:包含指定目标平台的独立子目录,在打包时该目录被包含在apk文件中。
  3. obj目录:这是一个中间目录,编译源码后产生的目标文件都保存在该目录下,我们最好不用访问该目录。

实例工程解析

Android.mk的内容如下

# Copyright (C) 2009 The Android Open Source Project  
#  
# Licensed under the Apache License, Version 2.0 (the "License");  
# you may not use this file except in compliance with the License.  
# You may obtain a copy of the License at  
#  
#      http://www.apache.org/licenses/LICENSE-2.0  
#  
# Unless required by applicable law or agreed to in writing, software  
# distributed under the License is distributed on an "AS IS" BASIS,  
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.  
# See the License for the specific language governing permissions and  
# limitations under the License.  
#  
LOCAL_PATH := $(call my-dir)  

include $(CLEAR_VARS)  

LOCAL_MODULE    := hello-jni  
LOCAL_SRC_FILES := hello-jni.c  

include $(BUILD_SHARED_LIBRARY) 

有关Makefile的知识请参考我的另一篇博文:http://blog.csdn.net/dawanganban/article/details/38750151

第一行:Android.mk文档必须以LOCAL_PATH变量的定义开头,my-dir是一个系统的宏定义,来定义源文件的目录位置。

第二行:Android构建系统将CLEAR_VARS变量设置为clear-vars.mk片段的位置,更多片段的makefile文件请看ndk\build\core目录,如下:

makefile文件

作用是清除除了LOCALPATH以外的LOCAL变量,这样可以避免冲突。

第三行:每一个原生组件被称为一个模块,LOCAL_MODULE变量用来给这些模块设定一个唯一的名称。

第四行:LOCAL_SRC_FILES变量定义用来建立和组装这个模块的源文件列表,用空格隔开。

第五行:指明了build-shared-library.mk文件的保存位置,该片段包含了将源文件构建成共享库的必要过程。

下面我们来看看HelloJni.java

/* 
* Copyright (C) 2009 The Android Open Source Project 
* 
* Licensed under the Apache License, Version 2.0 (the "License"); 
* you may not use this file except in compliance with the License. 
* You may obtain a copy of the License at 
* 
*      http://www.apache.org/licenses/LICENSE-2.0 
* 
* Unless required by applicable law or agreed to in writing, software 
* distributed under the License is distributed on an "AS IS" BASIS, 
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 
* See the License for the specific language governing permissions and 
* limitations under the License. 
*/  
package com.example.hellojni;  

import android.app.Activity;  
import android.widget.TextView;  
import android.os.Bundle;  


public class HelloJni extends Activity  
{  
    /** Called when the activity is first created. */  
    @Override  
    public void onCreate(Bundle savedInstanceState)  
    {  
        super.onCreate(savedInstanceState);  

        /* Create a TextView and set its content. 
        * the text is retrieved by calling a native 
        * function. 
        */  
        TextView  tv = new TextView(this);  
        tv.setText( stringFromJNI() );  
        setContentView(tv);  
    }  

    /* A native method that is implemented by the 
    * 'hello-jni' native library, which is packaged 
    * with this application. 
    */  
    public native String  stringFromJNI();  

    /* This is another native method declaration that is *not* 
    * implemented by 'hello-jni'. This is simply to show that 
    * you can declare as many native methods in your Java code 
    * as you want, their implementation is searched in the 
    * currently loaded native libraries only the first time 
    * you call them. 
    * 
    * Trying to call this function will result in a 
    * java.lang.UnsatisfiedLinkError exception ! 
    */  
    public native String  unimplementedStringFromJNI();  

    /* this is used to load the 'hello-jni' library on application 
    * startup. The library has already been unpacked into 
    * /data/data/com.example.hellojni/lib/libhello-jni.so at 
    * installation time by the package manager. 
    */  
    static {  
        System.loadLibrary("hello-jni");  
    }  
}  

从上面可以看到调用了原生的stringFromJNI()方法,使用关键字native来通知Java编译器,这个是用另一种语言实现的,再通过加装共享库(static语句块)hello-jni来告诉虚拟机原生方法的具体实现。java.lang.System类提供了两个静态方法,load和loadLibrary用来运行时加载共享库。下面我们来看看具体的实现。

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

/* This is a trivial JNI example where we use a native method 
* to return a new VM String. See the corresponding Java source 
* file located at: 
* 
*   apps/samples/hello-jni/project/src/com/example/hellojni/HelloJni.java 
*/  
jstring  
Java_com_example_hellojni_HelloJni_stringFromJNI( JNIEnv* env,  
                                                jobject thiz )  
{  
#if defined(__arm__)  
#if defined(__ARM_ARCH_7A__)  
    #if defined(__ARM_NEON__)  
    #if defined(__ARM_PCS_VFP)  
        #define ABI "armeabi-v7a/NEON (hard-float)"  
    #else  
        #define ABI "armeabi-v7a/NEON"  
    #endif  
    #else  
    #if defined(__ARM_PCS_VFP)  
        #define ABI "armeabi-v7a (hard-float)"  
    #else  
        #define ABI "armeabi-v7a"  
    #endif  
    #endif  
#else  
#define ABI "armeabi"  
#endif  
#elif defined(__i386__)  
#define ABI "x86"  
#elif defined(__x86_64__)  
#define ABI "x86_64"  
#elif defined(__mips64)  /* mips64el-* toolchain defines __mips__ too */  
#define ABI "mips64"  
#elif defined(__mips__)  
#define ABI "mips"  
#elif defined(__aarch64__)  
#define ABI "arm64-v8a"  
#else  
#define ABI "unknown"  
#endif  

    return (*env)->NewStringUTF(env, "Hello from JNI !  Compiled with ABI " ABI ".");  
}  

第一个参数JNIEnv是指向可用JNI函数表的接口指针,第二个参数jobject是HelloJni类实例的java对象。最后一句代码是用c字符串创建UTF-8的Java字符串。


上面我们了解了Android中有关JNI的使用,其实JNI是很早就有的,不是在Android创造的新技术,是SUN为我们提供的一种Java和本地代码之间相互调用的方法,这一篇我们来建立一个普通的Java工程来具体看一下Java中如何调用C/C++代码。

新建一个普通Java工程

package com.csdn.test;  
  
/** 
* 水寒 http://blog.csdn.net/dawanganban 
* @author lixiaoqiang 
* 
*/  
public class TestJNI {  
    
    public native void saveHello();  

    public static void main(String[] args) {  
        TestJNI jni = new TestJNI();  
        jni.saveHello();  
    }  
}  

在TestJNI中我们声明了一个native方法。

生成C/C++头文件

SUN的JDK中已经为我们提供了一个生成对应C/C++头文件的工具(javah)

lixiaoqiangdeMac-mini:bin lixiaoqiang$ whereis javah  
/usr/bin/javah  
lixiaoqiangdeMac-mini:bin lixiaoqiang$ 

说明:我这里用的是max os系统,如果你是windows系统,在jdk目录下寻找。

找到我们工程文件所在的目录,执行javah命令如下:

lixiaoqiangdeMac-mini:bin lixiaoqiang$ pwd  
/Users/lixiaoqiang/Documents/AndroidWorkspace/TestJNI/bin  
lixiaoqiangdeMac-mini:bin lixiaoqiang$ javah com.csdn.test.TestJNI  
lixiaoqiangdeMac-mini:bin lixiaoqiang$ ls   
com         com_csdn_test_TestJNI.h  
lixiaoqiangdeMac-mini:bin lixiaoqiang$   

可以看到为我们生成了一个com_csdn_test_TestJNI.h的头文件

/* DO NOT EDIT THIS FILE - it is machine generated */  
#include <jni.h>  
/* Header for class com_csdn_test_TestJNI */  

#ifndef _Included_com_csdn_test_TestJNI  
#define _Included_com_csdn_test_TestJNI  
#ifdef __cplusplus  
extern "C" {  
#endif  
/* 
* Class:     com_csdn_test_TestJNI 
* Method:    saveHello 
* Signature: ()V 
*/  
JNIEXPORT void JNICALL Java_com_csdn_test_TestJNI_saveHello  
(JNIEnv *, jobject);  

#ifdef __cplusplus  
}  
#endif  
#endif  

生成动态库文件

下面我们打开C/C++编辑器(我这里使用的时Xcode,在Windows平台上的朋友可以使用VisualStudio)

Xcode

main.cpp中我们输出一行文字,如下:

#include "com_csdn_test_TestJNI.h"  
#include <iostream>  
using namespace std;  

JNIEXPORT void JNICALL Java_com_csdn_test_TestJNI_saveHello(JNIEnv * evn, jobject obj){  
    cout << "hello world" << endl;  
}  

在编译的过程中会发现找不到jni.h和jni_md.h文件,这两个文件可以在JDK的include目录下找到,拷贝到工程中,下面将上面的工程导出动态链接库。

在导出链接库(编译)的时候在MacOS下和在Linux, Windows有所不同, 不是编译成.so或者dll, 而是MacOS自己的jnilib. 并且jni.h的目录也比较特殊, 是/System/Library/Frameworks/JavaVM.framework/Headers/, 这个需要稍微注意一下, 具体的命令如下:

lixiaoqiangdeMac-mini:TestJNI lixiaoqiang$ g++ -dynamiclib -o libhelloworld.jnilib main.cpp -framework JavaVM -I/System/Library/Frameworks/JavaVM.framework/Headers  
lixiaoqiangdeMac-mini:TestJNI lixiaoqiang$ ls  
com_csdn_test_TestJNI.h jni_md.h        main.cpp  
jni.h           libhelloworld.jnilib  
lixiaoqiangdeMac-mini:TestJNI lixiaoqiang$   

可以看到在该目录生成了一个libhelloworld.jnilib文件,我们将该文件拷贝到我们Java工程的bin目录下(windows下是classes目录) 向Java工程中添加加装动态库的代码(下面的static语句块中的System.loadLibrary方法)

package com.csdn.test;  

/** 
* 阳光小强 http://blog.csdn.net/dawanganban 
* @author lixiaoqiang 
* 
*/  
public class TestJNI {  
    
    public native void saveHello();  

    public static void main(String[] args) {  
        TestJNI jni = new TestJNI();  
        jni.saveHello();  
    }  
    
    static{  
        System.loadLibrary("helloworld");  
    }  
}  

最后将该库文件添加到Java程序可以访问的环境变量中就可以运行输出“helloworld”了。


Eclipse集成javah和NDK-Builder

上面我们使用了javah工具来生成了native java文件所对应的C++头文件,但是这样生成比较麻烦,我们这一篇来介绍如何在eclipse中集成javah和NDK-Builder。

eclipse集成javah

eclipse集成javah

选择External Tools Configurations…

选择External Tools Configurations

上面是我配置的,解释如下:

Location:这里配置的是javah所在的路径。 Working Directory:配置项目所在路径 Arguments:就是所需参数了。

注意:在linux或mac os上配置如下:

Working Directory——${project_loc}/bin/classes
Arguments: -d${project_loc}/jni ${java_type_name}

eclipse配置NDK交叉编译

eclipse配置NDK交叉编译

添加到定制工具栏

添加到定制工具栏

选择Organize Favorites…

选择Organize Favorites

选择javah和NDK_Builder然后点击OK,我们可以方便的使用这两个工具了

运行菜单


在上面文章中简单介绍了JNI,这一篇文章来简单看一下jni.h中定义的一些常用方法,来实现通过C++调用Android中的Java代码。

两个参数的介绍

在前面的代码中我们会遇到两个参数,下面对这两个参数做一解释

JNIEnv

JNIEnv是指向可用JNI函数表的接口指针,C代码中JNIEnv是指向JNINativeInterface结构的指针,在C语言中JNIEnv必须作为第一个参数传入每一个JNI函数的调用者,如:

(*env)->NewStringUTF(env, "helloworld");  

在C++中,JNIEnv是C++类的实例,JNI函数以成员函数形式存在,所以在JNI环境中,无序传入该参数,如:

env->NetStringUTF("helloworld");

jobject

jobject是一个指向java对象的引用,如果本地方法是一个静态方法,则是指向类字节码文件的class对象。

JNI数据类型

基本类型映射

从上表中可以看出,Java的数据类型和C++的基本数据类型有一个映射关系,我们在使用JNI的时候可以直接使用Natvie Type来操作Native层的数据,这样就不用记忆复杂的映射关系了,从变量的名字上我们可以看到在Java的基本数据类型前面加一个字母‘j’就是对应的C++的Native类型。

引用类型映射

与基本类型不同的是,引用类型对原生的方法是不透明的(不能直接使用和修改),JNI提供了与这些引用类型密切相关的一组API ,这些API通过JNIEnv接口指针提供给原生函数。

我们在jni.h中可以看到上面类型的定义:

class _jobject {};  
class _jclass : public _jobject {};  
class _jthrowable : public _jobject {};  
class _jstring : public _jobject {};  
class _jarray : public _jobject {};  
class _jbooleanArray : public _jarray {};  
class _jbyteArray : public _jarray {};  
class _jcharArray : public _jarray {};  
class _jshortArray : public _jarray {};  
class _jintArray : public _jarray {};  
class _jlongArray : public _jarray {};  
class _jfloatArray : public _jarray {};  
class _jdoubleArray : public _jarray {};  
class _jobjectArray : public _jarray {};  

typedef _jobject *jobject;  
typedef _jclass *jclass;  
typedef _jthrowable *jthrowable;  
typedef _jstring *jstring;  
typedef _jarray *jarray;  
typedef _jbooleanArray *jbooleanArray;  
typedef _jbyteArray *jbyteArray;  
typedef _jcharArray *jcharArray;  
typedef _jshortArray *jshortArray;  
typedef _jintArray *jintArray;  
typedef _jlongArray *jlongArray;  
typedef _jfloatArray *jfloatArray;  
typedef _jdoubleArray *jdoubleArray;  
typedef _jobjectArray *jobjectArray;  

这些类的设计和Java一样,都继承自一个名为_jobject的父类。

对引用类型操作的例子

package com.example.test;  

import android.app.Activity;  
import android.os.Bundle;  
import android.view.View;  
import android.view.View.OnClickListener;  
import android.widget.Toast;  

import com.example.myfirstjniproj.R;  

/** 
* 阳光小强 http://blog.csdn.net/dawanganban 
* @author lixiaoqiang 
* 
*/  
public class MainActivity extends Activity implements OnClickListener{  

    @Override  
    protected void onCreate(Bundle savedInstanceState) {  
        super.onCreate(savedInstanceState);  
        setContentView(R.layout.activity_main);  
        
        findViewById(R.id.jni_jstring_button).setOnClickListener(this);  
        findViewById(R.id.jni_javaArray_button).setOnClickListener(this);  
    }  

    @Override  
    public void onClick(View view) {  
        switch (view.getId()) {  
        case R.id.jni_jstring_button:  
            showToast(jniStringTest("input string"));  
            break;  
        case R.id.jni_javaArray_button:  
            int[] array = jniArrayTest();  
            showToast("arr[1]=" + array[1] + " : arr[2]=" + array[2]);  
            break;  
        default:  
            break;  
        }  
    }  
    
    private void showToast(String content){  
        Toast.makeText(MainActivity.this, content, Toast.LENGTH_SHORT).show();  
    }  
    
    public native String jniStringTest(String str);  
    
    public native int[] jniArrayTest();  
    
    
    static{  
        System.loadLibrary("jnitest");  
    }  
}  

通过javah生成的头文件

/* DO NOT EDIT THIS FILE - it is machine generated */  
#include <jni.h>  
/* Header for class com_example_test_MainActivity */  

#ifndef _Included_com_example_test_MainActivity  
#define _Included_com_example_test_MainActivity  
#ifdef __cplusplus  
extern "C" {  
#endif  
/* 
* Class:     com_example_test_MainActivity 
* Method:    jniStringTest 
* Signature: (Ljava/lang/String;)Ljava/lang/String; 
*/  
JNIEXPORT jstring JNICALL Java_com_example_test_MainActivity_jniStringTest  
(JNIEnv *, jobject, jstring);  

/* 
* Class:     com_example_test_MainActivity 
* Method:    jniArrayTest 
* Signature: ()[I 
*/  
JNIEXPORT jintArray JNICALL Java_com_example_test_MainActivity_jniArrayTest  
(JNIEnv *, jobject);  

#ifdef __cplusplus  
}  
#endif  
#endif  

c++实现

#include "com_example_test_MainActivity.h"  
#include <stdlib.h>  
using namespace std;  

JNIEXPORT jstring JNICALL Java_com_example_test_MainActivity_jniStringTest  
(JNIEnv * env, jobject obj, jstring str){  
    //将java字符串转成c++字符串  
    jboolean isCopy;  
    const char* cstr = env->GetStringUTFChars(str, &isCopy);  
    //释放原生字符串  
    env->ReleaseStringUTFChars(str, cstr);  
    //创建字符串  
    jstring jstr = env->NewStringUTF("hello world");;  

    return jstr;  
}  

JNIEXPORT jintArray JNICALL Java_com_example_test_MainActivity_jniArrayTest  
(JNIEnv * env, jobject obj){  
    //创建一个10个元素的数组  
    jintArray intarray = env->NewIntArray(10);  
    if(0 != intarray){  
        jint nativeArray[10];  
        //获取原生的数组  
        env->GetIntArrayRegion(intarray, 0, 10, nativeArray);  
        nativeArray[1] = 10;  
        nativeArray[2] = 20;  
        //设置改变  
        env->SetIntArrayRegion(intarray, 0, 10, nativeArray);  
    }  
    return intarray;  
}  

访问域和获取ID

Java有两个域:实例域和静态域,每个实例都有自己的实例域副本,而一个类的所有实例共享一个静态域。

JNIEXPORT jstring JNICALL Java_com_example_test_MainActivity_getInstanceField  
(JNIEnv * env, jobject obj){  
    //通过对象获得类  
    jclass clazz;  
    clazz = env->GetObjectClass(obj);  
    //获得实例域Id  
    jfieldID instanceFieldId;  
    instanceFieldId = env->GetFieldID(clazz, "instanceField", "Ljava/lang/String");  
    //获得实例域  
    jobject instanceField;  
    instanceField = env->GetObjectField(obj, instanceFieldId);  
    jstring jstr = env->NewStringUTF("获取成功");  
    return jstr;  
}  


JNIEXPORT jstring JNICALL Java_com_example_test_MainActivity_getStaticField  
(JNIEnv * env, jobject obj){  
    jclass clazz;  
    clazz = env->GetObjectClass(obj);  
    jfieldID staticFieldId;  
    staticFieldId = env->GetStaticFieldID(clazz, "staticField", "Ljava/lang/String");  
    jobject staticField;  
    staticField = env->GetStaticObjectField(clazz, staticFieldId);  
    jstring jstr = env->NewStringUTF("获取成功");  
    return jstr;  
}  

在上面代码中我们看到了“Ljava/lang/String”字符串,这个是获取ID的类型签名,Java的类型签名映射表如下:

Java的类型签名映射表

调用Java中的方法

与上面的域一样,Java中也有两类方法,实例方法和静态方法,JNI提供了两类方法的函数

public native void getInstanceMethod();  

public native void getStaticMethod();  

public String instanceMethod(){  
    return "instanceMethod";  
}  

public static String staticMethod(){  
    return "staticMethod";  
}  
JNIEXPORT void JNICALL Java_com_example_test_MainActivity_getInstanceMethod  
(JNIEnv * env, jobject obj){  
    jclass clazz;  
    clazz = env->GetObjectClass(obj);  
    jmethodID instanceMethodId;  
    instanceMethodId = env->GetMethodID(clazz, "instanceMethod", "()Ljava/lang/String");  
    jstring instanceMethodResult;  
    instanceMethodResult = env->CallStringMethod(obj, instanceMethodId);  
}  

JNIEXPORT void JNICALL Java_com_example_test_MainActivity_getStaticMethod  
(JNIEnv * env, jobject obj){  
    jclass clazz;  
    clazz = env->GetObjectClass(obj);  
    jmethodID staticMethodId;  
    staticMethodId = env->GetStaticMethodID(clazz, "staticMethod", "()Ljava/lang/String");  
    jstring staticMethodResult;  
    staticMethodResult = env->CallStaticStringMethod(clazz, staticMethodId);  
}