JNI 学习 一

一.JNI原理

JNI的全称是Java Native Interface,通过JNI技术,我们可以方便地在Java程序中调用Native函数,也可以在Native程序中回调Java方法。 对于Android系统来说, JNI是连接Java世界与Native世界的桥梁。它非常基础而又重要,许多系统模块内部都运用了到了它。因此我们每一个从事Android系统开发的程序员都需要熟练掌握JNI。

1.1JNI原理:

Java语言的执行环境是Java虚拟机(JVM),JVM其实是主机环境中的一个进程,每个JVM虚拟机都在本地环境中有一个JavaVM结构体,该结构体在创建Java虚拟机时被返回,在JNI环境中创建JVM的函数为 JNI_CreateJavaVM 。

_JNI_IMPORT_OR_EXPORT_  jint  JNICALL
JNI_CreateJavaVM(JavaVM **pvm, void **penv, void *args);

1.1.1 JavaVM

JavaVM是Java虚拟机在JNI层的代表,JNI全局仅仅有一个,可以跨线程使用,JavaVM中封装的函数指针主要是对JVM操作的接口。
JNI_OnLoad()函数相对应的有JNI_OnUnload()函数,当虚拟机释放的该C库的时候,则会调用JNI_OnUnload()函数来进行善后清除工作。一般动态注册会用到。

struct JNIInvokeInterface_;
struct JavaVM_;

#ifdef __cplusplus
typedef JavaVM_ JavaVM;
#else
typedef const struct JNIInvokeInterface_ *JavaVM;
#endif


struct JavaVM_ {
    const struct JNIInvokeInterface_ *functions;
#ifdef __cplusplus

    jint DestroyJavaVM() { //销毁JAVAVM
        return functions->DestroyJavaVM(this);
    }
    jint AttachCurrentThread(void **penv, void *args) { //获取线程相关的JNIENV
        return functions->AttachCurrentThread(this, penv, args);
    }
    jint DetachCurrentThread() {                //释放线程
        return functions->DetachCurrentThread(this);
    }

    jint GetEnv(void **penv, jint version) { // 获取Env
        return functions->GetEnv(this, penv, version);
    }
    jint AttachCurrentThreadAsDaemon(void **penv, void *args) {
        return functions->AttachCurrentThreadAsDaemon(this, penv, args);
    }
#endif
};


_JNI_IMPORT_OR_EXPORT_ jint JNICALL
JNI_GetDefaultJavaVMInitArgs(void *args);

_JNI_IMPORT_OR_EXPORT_ jint JNICALL
JNI_CreateJavaVM(JavaVM **pvm, void **penv, void *args);

_JNI_IMPORT_OR_EXPORT_ jint JNICALL
JNI_GetCreatedJavaVMs(JavaVM **, jsize, jsize *);

/* Defined by native libraries. */
JNIEXPORT jint JNICALL
JNI_OnLoad(JavaVM *vm, void *reserved); //动态注册 加载JNI库,自动调用

JNIEXPORT void JNICALL
JNI_OnUnload(JavaVM *vm, void *reserved);//释放JNI库,自动调用

1.1.2 JNIEnv

JNIEnv是线程相关的,即在每一个线程中都有一个JNIEnv指针,每个JNIEnv都是线程专有的,其他线程不能使用本线程中的JNIEnv,即线程A不能调用线程B的JNIEnv。所以JNIEnv不能跨线程。
实际上代表了java环境,通过JNIEnv*指针,就可以对java端,进行操作,例如:创建java类中的对象,调用java中方法,获取java对象中的属性,等。
作用:

  • 调用Java 函数:JNIEnv代表了Java执行环境,能够使用JNIEnv调用Java端的代码
  • 操作Java代码:Java对象传入JNI层就是jobject对象,需要使用JNIEnv来操作这个Java对象

获取: 从JavaVM获得,这里面又分为C与C++

  • C 中 JNIInvokeInterface:JNIInvokeInterface是C语言环境中的JavaVM结构体,调用 (AttachCurrentThread)(JavaVM, JNIEnv*, void) 方法,能够获得JNIEnv结构体
  • C++中 _JavaVM:_JavaVM是C++中JavaVM结构体,调用jint AttachCurrentThread(JNIEnv** p_env, void* thr_args) 方法,能够获取JNIEnv结构体;

释放:

  • C 中释放:调用JavaVM结构体JNIInvokeInterface中的(DetachCurrentThread)(JavaVM)方法,能够释放本线程的JNIEnv
  • C++ 中释放:调用JavaVM结构体_JavaVM中的jint DetachCurrentThread(){ return functions->DetachCurrentThread(this); } 方法,就可以释放 本线程的JNIEnv
jni.h中定义:
struct JNINativeInterface_;
struct JNIEnv_;


#ifdef __cplusplus
typedef JNIEnv_ JNIEnv;
#else
typedef const struct JNINativeInterface_ *JNIEnv;
#endif

通用例子
通过JavaVM对象 获取JNIEnv 对象,然后释放该线程

JavaVM *JavaVM;
static JNIEnv * getJNIEnv(bool *multi_thread) {
    JNIEnv *sCallbackEnv;
    if(JavaVM == NULL)
    {
        return NULL;
    }
    if(JavaVM->GetEnv(reinterpret_cast<void**>(&sCallbackEnv), JNI_VERSION_1_6) != JNI_OK) {
        if(JavaVM->AttachCurrentThread(&sCallbackEnv, NULL) < 0) { //获取线程相关
            *multi_thread = false;
            return NULL;
        }
        *multi_thread = true;
    }
    else {
        *multi_thread = false;
    }
    return sCallbackEnv;
}
------------------------------------------------------------------------------------------
    bool multi_thread = true;
    JNIEnv *sCallbackEnv = getJNIEnv(&multi_thread);
    if(multi_thread) {
        JavaVM->DetachCurrentThread(); //释放
    }

注意:

C++void fun(JNIEnv *env)    env->
C中:
void fun(JNIEnv *env)    (*env)->

1.1.3 参数 jobject jclass

java层的代码调用c/c++代码,在JNI函数映射,每个jni的函数,函数参数存在。
jobject jclass 代表的是java层的对象或者类
1.如果native方法不是static的话,这个obj就表示native方法类的实例
2.如果natvie方法是static的话,这个obj就代表这个native方法的类class对象的实例,(static方法不需要类的实例,所以就代表这个类的class对象)

例如:
java层:
private native static void classInitNative() //静态对应JNI层,参数为jclass clazz
private native void initNative(); //非静态对应JNI层,参数 jobject obj

JNI层:
static void classInitNative(JNIEnv* env, jclass clazz)
static bool initNative(JNIEnv* env, jobject obj) 

1.1.3 jfieldId, jmethodID

jfieldId, jmethodID表示的是java层对象的属性和方法
通过jfieldId, jmethodID实现c/c++ 回调java代码

1.1.4 Java和C/C++ 中的基本类型的映射关系:

JNI是接口语言,因而,会有一个中间的转型过程,在这个过程中,有一个非常重要的也是非常关键的类型对接方
式,这个方式便是,数据类型的转变,下表给出了相关的对于的数据格式。

java类型jni类型描述签名
booleanjbooleanunsigned 8 bitsZ
bytejbytesigned 8 bitsB
charjcharunsigned 16 bitsC
shortjshortsigned 16 bitsS
intjintsigned 32 bitsI
longjlongsigned 64 bitsJ
floatjfloatsigned 32bitF
doublejdoublesigned 64 bitsD
Classjclassclass类对象
Stringjstring字符串对象L/java/lang/String
Objectjobject任何java对象
byte[]jbyteArraybyte数组[B
voidV

这个表都是JNI开发中 java 和 JNI之间数据的适配。
注意:

  • 类都是 以L开头, 包名是以“/”分割, “;” 结束
    例如 String str JNI:Ljava/lang/String;

  • 数组是以“]” 开始
    例如long array[] JNI:[J

  • 基本类型之间没有分割
    例如:void fun(int a,double b,byte[] c) JNI: (ID[B)V

1.1.5 java层签名生成方式:

javap -s -p xxx.class
-p: 所有的类和成员函数

2.JNI 动态注册(常用)与静态注册

2.1 静态注册(一般很少用 )

步骤:

  • 编写java类,带有包名com.example.JNI里面 JniTest.java ,在java文件中声明Native方法(即需要调用的本地方法)
  • 在com.example.JNI里面命令行下输入 javac -d . JniTest.java 生成包名下的JniTest.class文件
  • 通过 javah -classpath . -jni com.example.JNI.JniTest(全类名)生成 xxx_JniTest.h 头文件
  • 编写xxx_JniTest.c 源文件,并拷贝xxx_JniTest.h 下的函数,并实现这些函数,且在其中添加jni.h头文件;
  • 编写 cmakelist.txt 文件,编译生成动态/静态链接库

注意:java文件要是带有包名,要在包名外面执行javac 或者 javah
全类名:指的是在包名的外部执行 javah -classpath . -jni com.example.iauto.JniTest
通过javah xx生成c/c++接口,命名规范:java_包名_类名_函数名

JNIExport  jstring JNICALL  Java_com_example_hellojni_MainActivity_stringFromJNI( JNIEnv* env,jobject thiz )
jstring             返回值类型
Java_com_example_hellojni 包名
MainActivity         类名
stringFromJNI        方法名

Java load 和 loadlibrary方法的区别 :
1.相同点:
两个方法都是用来装载dll文件 或者 so ,不论是JNI库文件还是非JNI库文件。
本地方法在被调用时都需要通过这两发方法之一将其加载至内存。

2 不同点
a. System.load(String filename) ,参数为dll文件的绝对路径,可以是任意路径。

System.load("D:\\java\\Test.dll");

b. System.loadLibrary(String libname) 参数为dll文件名,不包含库文件的扩展名。

System. loadLibrary ("Test");

注意:System.loadLibrary(String libname)和java.library.path变量对应的路经相关,此方法加载的文件必须是在java.library.path这一jvm变量所指向的路径中。
可以通过如下方法来获得该变量的值:

System.getProperty("java.library.path")

2.2 动态注册(常用)

举例:
java层:
java层代码非常简单只有两点:
(1)加载JNI库, 通常加载JNI库的操作放在一段static代码中,调用System.loadLibrary方法即可

public class JNITestController {
    static {
        System.loadLibrary("jnitest_jni"); //在java环境变量中寻找
    }
  private static native void InitNative();      //static 对应的方法参数为jclass
  private  native  void fun(int num1,int num2);   //非static 对应方法参数为jobject
}

JNI层:
1.实现 JNI_OnLoad 方法
2.FindClass 方法获取java对象
3.调用RegisterNatives方法,传入java对象,以及JNINative数组,以及注册数目完成。

static void InitNative(JNIEnv* env, jclass clazz) //jclass 对应java为静态方法
{
   env->GetJavaVM(&javaVM);
   std::cout<<"InitNative"<<std::endl;
}

static void fun(JNIEnv* env, jobject object,jint num1,jint num2) //jobject object 非静态方法
{
   std::cout<<"fun:"<<num1 <<" "<< num2 <<std::endl;
}

static JNINativeMethod methods[] = {
    {"InitNative","()V",(void *)InitNative}, //注意 () 之间是没有空格的
    {"fun","(II)V",(void *)fun}
};

jint registerJNI(JNIEnv *env,jclass clazz) 
{  
  return env->RegisterNatives(clazz, methods,
                         sizeof(methods)/sizeof(methods[0]));
}
}
   //步骤:
   //1.检查JNI 版本
   //2.注册
JNIEXPORT jint JNICALL JNI_OnLoad(JavaVM *jvm, void *reserved) 
{
    JNIEnv *env;
    jint result = -1;
    jclass clazz;
    
    if(jvm->GetEnv((void**)&env,JNI_VERSION_1_6)) 
    {
        std::cout<<"JNI version mismatch error"<<std::endl;
        return JNI_ERR;
    }
    clazz = env->FindClass("JNIControl"); //java 中的类
    result = nutshell::registerJNI(env,clazz);
    if(result<0) {
         std::cout<<"JNI registerJNI error"<<std::endl;
         return JNI_ERR;
    }
    return JNI_VERSION_1_6;
}

3 JNI常用方法

一般使用 JNIEnv里面的方法 一定要注意 C: (*env)-> C++:env->

  • 基本类型
    从java层到jni,jni到java 不变,可以直接用。
  • string类型:
    从java到jni,jstring 需要转成const char *,需要释放
    从jni到java,const char * 需要转成jstring,需要释放
  • 对象:
    一般用parcel

3.1 获取JAVAM

通过JEnv获取

JAVAVM m_JavaVM;
env->GetJavaVM(&m_JavaVM);

3.2获取JNIENV版本信息

两种方式:
1.通过javavm获取
jvm->GetEnv((void **)&env, JNI_VERSION_1_6)
2.通过线程获取
jvm->AttachCurrentThread((void **)&env,NULL)
m_JavaVM->DetachCurrentThread()

3.3 获取JNI版本信息

jint GetVersion(JNIEnv *env);

3.4 注册java中的nativie方法

JNI_OnLoad里面获取java层对象class,在进行注册
java层BtService 类中native方法
public static native void NativeInit();
public native void JavaToNativeTest(int taskID);
public native void cleanNative();

jni层
static const JNINativeMethod methods[] = {
    {"NativeInit","()V",(void *)NativeInit},
    {"JavaToNativeTest","(I)V",(void *)JavaToNativeTest},
    {"cleanNative","()V",(void *)cleanNative}
}
jclass Clazz= env->FindClass("com/iauto/service/BtService");
int ret = env->RegisterNatives(Clazz,methods,sizeof(methods)/sizeof(methods[0]));

3.5 获取java中的类

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Android JNI学习路线可以按照以下步骤进行: 1. 了解JNI的基本概念和作用:JNI(Java Native Interface)是Java提供的一种机制,用于实现Java与其他编程语言(如C、C++)之间的交互。它允许在Java代码中调用本地代码(Native Code),并且可以在本地代码中调用Java代码。 2. 学习JNI的基本语法和规则:JNI使用一组特定的函数和数据类型来实现Java与本地代码之间的交互。你需要学习如何声明本地方法、如何在Java代码中调用本地方法、如何在本地代码中调用Java方法等。 3. 学习JNI的数据类型映射:JNI提供了一套数据类型映射规则,用于将Java数据类型映射到本地代码中的数据类型。你需要学习如何处理基本数据类型、对象类型、数组类型等。 4. 学习JNI的异常处理:在JNI中,Java代码和本地代码之间的异常处理是非常重要的。你需要学习如何在本地代码中抛出异常、如何在Java代码中捕获异常等。 5. 学习JNI的线程处理:JNI允许在本地代码中创建和操作线程。你需要学习如何创建和销毁线程、如何在线程之间进行通信等。 6. 学习JNI的性能优化:JNI涉及到Java代码和本地代码之间的频繁切换,因此性能优化是非常重要的。你需要学习如何减少JNI调用的次数、如何避免不必要的数据拷贝等。 7. 学习JNI的调试和测试:在开发JNI程序时,调试和测试是非常重要的。你需要学习如何使用调试器调试本地代码、如何进行单元测试等。 8. 学习JNI的进阶主题:一旦掌握了基本的JNI知识,你可以进一步学习JNI的进阶主题,如JNI与Java虚拟机的交互、JNI与动态链接库的交互、JNI与多线程的交互等。 总结起来,Android JNI学习路线包括了基本概念、基本语法、数据类型映射、异常处理、线程处理、性能优化、调试和测试以及进阶主题等内容。通过系统地学习这些知识,你将能够更好地理解和应用JNI技术。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值