【Android JNI 开发入门指南】

部署运行你感兴趣的模型镜像

Android JNI 开发入门指南

1. 【JNI介绍】

  • What (是什么): JNI (JAVA Native Interface)java本地接口。

    • 通过JNI可以实现Java和本地代码(C/C++)之间相互调用。

    • JNI可以看做是翻译官,实际上就是一套协议。

    • 因此类似于AIDL(安卓接口定义语言),aidl是用于进程间通信的一套协议(接口)。

  • Why (为什么要用):

    • 由于java的跨平台能力,导致了一些弊端,运行在虚拟机上,无法真正直接的操作系统硬件。

    • JNI则可以扩展JAVA的能力,让java也能通过它来操作硬件驱动。(智能家居,物联网…)

    • java是解释型语言,运行效率相对较低,而C/C++的效率要高很多,通过JNI可以把耗时操作放在C/C++中实现可以提高Java程序的运行效率。

    • java代码编译的字节码文件安全性较差, 可以通过jni 把重要的敏感的业务逻辑放到c/c++去实现,由于c/c++反编译比较困难,因此安全性较高。

    • 代码移植,复用已经存在的c代码(opencv,ffmpeg…)

    • C历史悠久,通过JNI可以调用优秀的C开源类库。

  • How (怎么用):

    • 熟练Java
    • C/C++ 能看懂 会调用。
    • JNI开发流程 NDK native develop kit

2. 【NDK(JNI)开发介绍】

  1. [交叉编译]

    • 在一个平台上去编译另一个平台上可以执行的本地代码。

    • 交叉编译的实现原理: 模拟不同平台的特性去编译代码。

    • Android系统运行的 CPU 架构 :ARM,X86,mips

  2. [NDK (native Develop Kit),谷歌提供的: 用作JNI开发的交叉编译工具集。]

    △安装介绍;如果电脑与JDK都是32位的则选择 android-ndk-r9d-windows-x86 的开发工具包。

     					   64为的则选择 android-ndk-r9d-windows-x86_64 的开发工具包。
    

    △使用介绍:解压就能用

    △NDK解压后的:常用目录,文件介绍:

     * docs: 帮助文档
    
     * platforms : 平台版本文件夹,使用时选择项目支持的最小版本号所对应的文件夹,每一个版本号的文件夹中包含了不同cpu架构的资源文件
    
     		[例如]:我的安卓项目设置的最低支持SDK版本为 api-14,则应该选择使用: platforms/android-14下的头文件,与.so文件。
    
     * include : 存储jni开发中常用的 .h头文件
     * lib : 	 google打包好的 提供给开发者使用的:.so文件,.o文件。
     * samples : google官方提供的样例工程 可以参考进行开发
     * android-ndk-r9d\build\tools : linux系统下的批处理文件 在交叉编译时会自动调用
    
     * ndk-build.cmd :交叉编译的命令。(执行它就可以吧c代表编译成安卓应用可以支持的 .so文件)
     * cdt :      eclipse的插件 高亮C代码 C的代码提示。(一般在eclipse中会自带此插件)
    
  3. 【[JNI-Hello World】

    △需求:在Android程序中点击一个Button,实现调用C语言的一个函数,该函数返回一个字符串(Hello World)!

    △核心原理:在java中声明一个native方法,然后在C语言代码中实现这个native方法,然后在java代码中调用这个方法。

    ☆实现步骤: —可以参考:android-ndk-r9d-windows-x86.zip\android-ndk-r9d\samples\hello-jni 这个示例工程。

    • 简要步骤如下:

      1. 创建一个android工程
      2. java代码中写声明native方法
      3. 创建jni目录,编写c代码,方法名字要对应(有一定的命名格式)。
      4. 编写Android.mk文件,如果需要还需Application.mk
      5. NDK编译生成动态链接库
      6. java代码load动态库.调用native代码
    • [详细步骤如下]:

      • [1]:首先写java代码, 声明本地方法 用到native关键字 本地方法不用去实现。

          例:
          	/**
          	 *1. 声明一个java本地方法,利用native关键字,不需要方法体。
          	 */
          	public native String sayHelloFromC();	
        
      • 2:然后在安卓项目根目录下创建jni文件夹, 在jni文件夹下创建 hello.c文件。需要包含进两个头文件:include<stdio.h> 与 include<jni.h>

        △在hello.c中:创建本地函数,本地函数命名规则为: Java_包名_类名_本地方法名

        △按照JNI本地函数命名规则,定义我们自己需要的函数。函数的返回值类型可以参照JNI协议。


        在jni.h头文件中:
        typedef const struct JniNativeInterface* JNIENV ;//因此JNIENV 类型,实际上就是JniNativeInterface这个结构体的指针的别名。

        • 因此JNIEnv 是JniNativeInterface这个结构体的一级指针

        • 参数:JNIENV* env 就是结构体JniNativeInterface这个结构体的二级指针(因为它接收的就是一级指针的地址)

        • JniNativeInterface 这个结构体定义了大量的函数指针,因此就可以通过结构体指针来访问结构体中定义的函数指针所指向的函数了。

        • (env)-> 即可调用结构体中的函数指针。(因为env为二级指针,因此需要加,取出一级指针,然后对一级指针使用 -> )

        • 第二个参数jobject thiz: 调用本地函数的java对象就是这个jobject,在本例中也就是MainActivity.this

        △:注意:我们调用的所有的JNI本地代码函数,都需要带这两个参数。

          例:hello.c 如下:
        
          	#include<stdio.h>
          	#include<jni.h>
        
          	//由于程序的入口是MainActivity,因此无需再此处创建 main()函数
        
          	/**
          	 * 添加一个C语言本地函数:命名规则为: Java_包名_类名_本地方法名 。(本地方法名就是调用这个c函数的java native 方法名)
          	 */
          	jstring Java_com_hzy_jnihelloworld_MainActivity_sayHelloFromC(JNIEnv* env,jobject thiz){
          		//定义一个C语言的字符串
          		char* hello_str = "helloWorld!";
          		//通过:结构体(JniNativeInterface)的二级指针 env 调用结构体中声明的函数指针所对应的函数
          																		(jstring  NewStringUTF(JNIEnv*, const char*);)
          		return (*env)->NewStringUTF(env,hello_str);
          	}
        
      • [3]:在项目的 jni目录下 创建Android.mk (makefile) 告诉编译器.c的源文件在什么地方,要生成的编译对象的名字是什么

         例:Android.mk 如下:
        
         		LOCAL_PATH := $(call my-dir)
        
         	    include $(CLEAR_VARS)
        
         	   	LOCAL_MODULE    := hello   #指定了生成的动态链接库的名字
         	   	LOCAL_SRC_FILES := hello.c #指定了C的源文件叫什么名字
        
         	    include $(BUILD_SHARED_LIBRARY)
        
      • 4:用cmd进入项目的根目录,然后调用命令 ndk-build 编译c代码,编译成功生成的动态链接库.so文件,
        该文件的位置 lib->armeabi->libhello.so 。(右键项目–>refresh即可看到该文件出现。)

        注意:命令使用前需要将 ndk 解压后的目录,添加到系统的path环境变量中。

           如果运行的设备平台为x86,则需新建一个 Application.mk文件,在里面进行配置,实现让ndk编译出多个平台的so文件。--提高兼容性
        
      • [5]:在java代码中加载动态链接库 System.loadlibrary(“动态链接库的名字”); 也就是 jni/Android.mk 中 LOCAL_MODULE所指定的名字。

         //使用前需要加载编译成功后生成的,动态链接库文件
         	static{
         		System.loadLibrary("hello");
         	}
        
      •  例:
         	//调用本地方法
         	String result = sayHelloFromC(); //实际上会调用 .so文件中所定义的对应的本地代码函数。
         	Toast.makeText(getApplicationContext(), result, 0).show();		
        

3. 【jni开发中的常见错误 】

  • △ [java.lang.UnsatisfiedLinkError: Native method not found: 本地方法没有找到 ]

    • 本地函数名写错
    • 忘记加载.so文件 没有调用System.loadlibrary(); //一般在静态代码块中加载
  • △[findLibrary returned null ]

    • System.loadLibrary(“hello”); //注意:加载动态链接库时,name为 :jni/Android.mk 中 LOCAL_MODULE所指定的名字。

    • 平台类型错误:例如:把只支持arm平台的.so文件部署到了 x86cpu的设备上。 —因为默认情况下会生成arm平台的so文件。

      解决方案:

        * 在jni目录下创建 Application.mk 文件,并在里面指定: 
      
        	  APP_ABI := x86 			#则只支持x86平台
        	  APP_ABI := armeabi x86   #则只支持arm平台与x86平台
        	  APP_ABI := all 		  #全部支持
      
          	  APP_PLATFORM := android-14  #此处配置成自己项目所支持的最低的AndroidSDK版本,交叉编译时就不会 Warning!
      
          完成配置后,cmd进入项目根目录,然后ndk-build重新编译。即可!	  
      
  • △[用javah 命令:来生成头文件,解决自己写本地代码函数时,函数名格式写错的问题。]

    • 如果是jdk 1.7以上,则到项目 src目录下运行 javah命令

    • 如果是jdk 1.6,则到项目的bin目录下 classes文件夹下运行 javah命令

    • 写法:javah (native方法声明的java类的全类名)

      例:首先在mainActivity中编写一个native方法:public native String sayHelloFromC(); //native方法的方法体位于本地代码中

        然后到cmd中找到项目的...\src> javah com.hzy.jnihelloworld.MainActivity,
      
        	就会将该类中所有的 native方法,生成对应的本地代码函数到头文件中。
      
        刷新项目即可看到:src/com_hzy_jnihelloworld_MainActivity.h 
      
        	:方法名就生成成功了。
      
        		/*
        		 * Class:     com_hzy_jnihelloworld_MainActivity
        		 * Method:    sayHelloFromC
        		 * Signature: ()Ljava/lang/String;
        		 */
        		JNIEXPORT jstring JNICALL Java_com_hzy_jnihelloworld_MainActivity_sayHelloFromC(JNIEnv *, jobject);
      
  • △:解决CDT插件报错的问题(在.c文件中使用 JNIEnv, jstring这些关键字时,发现报错!)

    右键单击项目选择 properties :

    选中:c/c++ general->paths and symbols->include选项卡下->点击add…->file system
    选择ndk目录下 的platforms文件夹 对应平台下(项目支持的最小版本)usr 目录下 arch-arm -> include
    (apply)确定后 会解决代码提示和报错的问题。

4. 【JNI简便开发流程】

  • 前言:一个标准的JNI程序开发步骤就如上文所示,但是过于繁琐,因此安卓也提供了简便的开发流程。

  • [实现步骤]:

    • [1]:【首先编写java代码, 用native 声明本地方法。】

    • 2:【添加本地支持 右键单击项目->andorid tools->add native surport,然后指定生成的so文件的名字即可。】

        * 如果发现 finish不能点击,则需要给工作空间指定 ndk目录的位置(工具包解压后的存放位置)。
      
        * 进入:window->preferences->左侧选择android->ndk ,然后把ndk解压后的存放目录指定进来即可。
      
          △如果发现自己的eclipse中没有andorid tools->add native surport,这个选项则代表自己的ADT版本问题,
      
          		此时可以将一个ndk插件安装到eclipse中,重启eclipse就会发现有 andorid tools->add native surport这个选项了。
        	
        			例:com.android.ide.eclipse.ndk_23.0.2.1259578.jar,放置到eclipse的plugins目录中即可。
      

      △使用这种方式就无需手动创建 jni文件夹了!,因为完成上面的步骤后,eclipse会自动在项目中创建一个 jni文件夹。

    • [3]:【如果写的是C语言则,先修改一下生成的.cpp文件的扩展名为 xxx.c,且不要忘了相应修改Android.mk文件中LOCAL_SRC_FILES的值。】

        △如果运行的平台不是arm平台,则还需手动添加一个Application.mk文件,进行配置。
      
        	一般配置如下:APP_ABI := armeabi x86   #则只支持arm平台与x86平台
        				  APP_PLATFORM := android-14 #项目支持的android sdk的最低版本号
      
    • 4:【然后使用:javah命令 生成头文件,在生成的头文件中拷贝c的函数名到.c的文件。(避免敲代码导致函数名格式有误)】

    • [5]:编写C函数 如果需要单独编译一下c代码就在c/c++视图中找到小锤子

        如果想直接运行到模拟器上 就不用锤子了,因为部署运行时会先使用ndk编译生成.so文件。
      
    • 6:【java代码中不要忘了: System.loadlibrary(“hello”);

        	//且一般在静态代码块中执行此代码加载.so文件,参数:libName :也就是 jni/Android.mk 中 LOCAL_MODULE所指定的名字。】
      
    • 》》附:在打包程序时,jni,obj这两个目录并不会包含进apk文件中,且能被包含进apk文件中的文件夹,左上角有一个小D图标。

5. 【利用JNI实现java与C之间的数据传递】

  1. 【Java <–> C :int类型,String,数组类型的数据传递】:

    • [1]:首先在Android项目中,创建一个com.hzy.jni包,然后在该包下创建一个 JNI.java 的类文件。(专门用创建本地方法,实现调用C函数)

        JNI.java如下:
      
        	public class JNI {
        		static {
        			System.loadLibrary("Java_C_Both_DataTransmit");
        		}
        		//传递两个数值进去,让C语言执行加法,并将计算结果返回回来
        		public native float add(float x, float y);
      
        		//传递String类型的参数给C,让C语言处理一下返回来。(做一下简单的加密)
        		public native String encryptStr(String s);
      
        		//传递int类型的数组给C ,让C语言处理一下返回一个数组回来。(例:图形处理)
        		public native int[] arrElementsIncrease(int[] intArray); 
        	}
      
    • 2:然后用cmd进入项目的src目录,使用javah命令 结合 JNI.java的全类名,让它自动生成一个头文件。

      就可以获取到JNI类中对应的 native方法在C语言中的函数实现。

    • [3]:然后手动创建jni目录或者:右键项目–>andorid tools->add native surport–>指定生成的.so文件的名字。即看到项目中多了 jni,obj两个目录

      然后在jni目录下找到自动生成的.cpp文件,如果项目不是C++,则改为.c ,然后修改Android.mk文件中,LOCAL_SRC_FILES的值,为xxx.c。

        如果程序运行的平台不是arm则应该在jni/下创建一个Application.mk文件,配置如下:
      
        	APP_ABI := armeabi x86   #则只支持arm平台与x86平台
        	APP_PLATFORM := android-14 #项目支持的android sdk的最低版本号
      
      
        * 编写C源文件如下:
      
        	* 首先函数名可以直接copy到之前利用javah命令,所自动生成的函数名。然后自己编写函数体:
        		```java
        		#include <jni.h>
      
        		/**
        		 * 声明被调函数(被调函数的声明必须在主调函数之前)
        		 * 把一个jstring转换成一个c语言的char* 类型.
        		 */
        		char* _JString2CStr(JNIEnv* env, jstring jstr) {
      
        			 char* rtn = "";
        			 jclass clsstring = (*env)->FindClass(env, "java/lang/String");
        			 jstring strencode = (*env)->NewStringUTF(env,"GB2312");
        			 jmethodID mid = (*env)->GetMethodID(env, clsstring, "getBytes", "(Ljava/lang/String;)[B");
        			 jbyteArray barr = (jbyteArray)(*env)->CallObjectMethod(env, jstr, mid, strencode); // String .getByte("GB2312");
        			 jsize alen = (*env)->GetArrayLength(env, barr);
        			 jbyte* ba = (*env)->GetByteArrayElements(env, barr, JNI_FALSE);
        			 if(alen > 0) {
        				rtn = (char*)malloc(alen+1); // +1是为了存放:'\0'
        				memcpy(rtn, ba, alen);
        				rtn[alen]=0;
        			 }
        			 (*env)->ReleaseByteArrayElements(env, barr, ba,0);
        			 return rtn;
        		}
      
        		/*
        		 * Class:     com_hzy_jni_JNI
        		 * Method:    add
        		 */
        		JNIEXPORT jfloat JNICALL Java_com_hzy_jni_JNI_add(JNIEnv * env, jobject thiz,  jfloat x, jfloat y){
        			return x+y;
        		}
      
      
        		/* 接收一个java的String,然后转换成 C语言的字符串 :利用 char*进行加密处理后就返回给java
        		 * Class:     com_hzy_jni_JNI
        		 * Method:    sayHelloInC
        		 */
        		JNIEXPORT jstring JNICALL Java_com_hzy_jni_JNI_encryptStr(JNIEnv * env, jobject thiz, jstring jstr){
        			//调用工具方法把 java中的string 类型 转换成 C 语言中的 字符串(即:char* 指向的字符数组)
        			char* cstr = _JString2CStr(env,jstr);
        			//调用strlen 获取 cstr 字符串的长度
        			int length = strlen(cstr);
      
        			//做一个简单的加密
        			int i;
        			for(i = 0;i<length;i++){
        				*(cstr+i) += 1;
        			}
        			return (*env)->NewStringUTF(env,cstr);
        		}
      
      
        		/*将传递进来的 jintArray 进行遍历,然后将每一个元素取反,最后返回jintArray 数组。
        		 * 实际上不需要设计返回值了,因为传递进来的java数组,直接被C语言的指针操作了,并没有copy操作,因此直接修改的就是传递进来的数组。
        		 * Class:     com_hzy_jni_JNI
        		 * Method:    arrElementsIncrease
        		 */
        		JNIEXPORT jintArray JNICALL Java_com_hzy_jni_JNI_arrElementsNegation(JNIEnv * env , jobject thisz, jintArray intArr){
        			
        				//获取jarray数组的长度
        				jsize length = (*env)->GetArrayLength(env,intArr);
      
        				jboolean iscopy ; //此参数用 NULL也可以
        				
        				//获取数组元素的指针
        				jint*  jarrPointer = (*env)->GetIntArrayElements(env,intArr,&iscopy);
        				//利用指针位移遍历数组
        				int i ;
        				for(i = 0; i<length ; i++){
        					//将每一个数组元素都取反
        					*(jarrPointer +i) = -(*(jarrPointer +i));
        				}
        				return intArr;
        		}
        		```
      
      • 【注意】:如果使用andorid tools->add native surport,就会发现jni.h文件需要重新指定,否则一些C语言的类型会报错,

          解决方案:见上文:△解决CDT插件报错的问题。
        
      • MainActivity中:
      	//int类型的数据传递
          public void transmitIntValue(View v){
          	int x = 6 ,y=5;
          	float result = jni.add(x,y);
          	Toast.makeText(getApplicationContext(), x+"+"+y+"=="+result, 0).show();
          }
          //String类型的 数据传递
          public void transmitString(View v){
          	String hello_str = "HelloWorld";
          	String encrypResulttStr = jni.encryptStr(hello_str);
          	Toast.makeText(getApplicationContext(), hello_str+"加密后:"+encrypResulttStr, 0).show();
          }
          /*
           * 数组类型的传递
           * 由于:数组属于引用类型,因此传递进去后,C语言操作的就是这个数组本身
           */
          public void transmitIntArray(View v){
          	int [] intArr = {1,-5,77,5};
          	int[] resultArr = jni.arrElementsNegation(intArr);
          	StringBuffer sb = new StringBuffer();
          	for(int i = 0 ; i<resultArr.length ; i++){
          		sb.append(resultArr[i]+"|");
          	}
          	Toast.makeText(getApplicationContext(), "取反结果:"+sb.toString(), 0).show();
          }
          ```
      
      
      
      1. 【在C代码中调用LogCat】

      [1]:首先在 /jni/Android.mk文件中加载本地库文件如下:

        	LOCAL_LDLIBS += -llog  #加载日志库文件
        	
        参考库文件位置,例:...\NDK\android-ndk-r9d\platforms\android-14\arch-x86\usr\lib
      
        	#include <android/log.h>
        	#define LOG_TAG "System.out"
        	#define LOGD(...) __android_log_print(ANDROID_LOG_DEBUG, LOG_TAG, __VA_ARGS__)
        	#define LOGI(...) __android_log_print(ANDROID_LOG_INFO, LOG_TAG, __VA_ARGS__)
      
        	△:如果想输出其他级别的日志,则可以继续使用 宏定义,参照上面两行即可。
      
      
        //define C的宏定义 起别名,  #define LOG_TAG "System.out" 就是给"System.out"起别名为:LOG_TAG 
      
        //#define LOGI(...) __android_log_print(ANDROID_LOG_INFO, LOG_TAG, __VA_ARGS__)
      
        							//给 __android_log_print函数起别名 :写死了前两个参数 第一个参数 优先级 第二个参数TAG
      
        //形参:__VA_ARGS__ 可变参数的固定写法
        
        【注意】:LOGI(...)在调用的时候 用法跟C语言的printf()一样。
      
      1. . 最后使用C语言实现打印日志到LogCat

      JNIEXPORT jstring JNICALL Java_com_hzy_simplejnihelloworld_MainActivity_sayHelloFromC(JNIEnv * env, jobject thiz){

        char* hello_str = "helloWorld!!!";
      
        LOGD("LogCat--hello_str == %s",hello_str);
      
        return (*env)->NewStringUTF(env,hello_str);
      

      }

  2. 【C函数中回调Java的方法】

    [前言]:上文只讲了Java native方法调用C函数,那么如果想让C函数回调Java的方法,就需要使用到反射机制。

    [使用情景]:上文的Java native方法调用C函数,虽然也可以获取到返回值,但是是同步的,因此不适合在C函数做耗时操作。

     所以可以利用C函数回调java,当在C函数中做耗时操作时,就可以反射-》回调java的方法,报告耗时操作的进度信息等等。
    

    [实现原理]:首先在Activity中调用native方法,然后这个方法会调用它在C语言中的实现函数,

     		然后在该C函数中利用反射机制实现调用Java中定义的回调方法。
    

    [在C函数中反射–》回调java方法实现步骤如下:

     例:在本地函数中:
     
     JNIEXPORT void JNICALL Java_com_itheima_callbackjava_JNI_callbackvoidmethod(JNIEnv * env, jobject clazz){
    
     	//[1]:首先获取需要被反射回调的方法所在的类的字节码对象
    
     	//jclass      (*FindClass)(JNIEnv*, const char*);
     	jclass claz = (*env)->FindClass(env,"com/itheima/callbackjava/JNI");
    
     	//[2]:获取需要被反射回调的方法的 methodId
    
     	//最后一个参数为方法签名,可以在 cmd 进入项目的bin/classs/下 java p -s JNI类全类名 ,来获取该类中所有native方法的方法签名
     	//如果是使用 javah命令自动生成c本地函数,则在函数注释上就有这个方法签名。
    
     	//jmethodID   (*GetMethodID)(JNIEnv*, jclass, const char*, const char*);
     	jmethodID methodID =(*env)->GetMethodID(env,claz,"helloFromJava","()V");
    
     	//[3]:通过字节码对象创建一个Object
     	//由于本例中,需要被回到的方法与native方法位于同一个类中,因此可以直接使用本地函数的参数  clazz 。
    
     	//[4]:通过对象调用方法
     	//void        (*CallVoidMethod)(JNIEnv*, jobject, jmethodID, ...);
     	(*env)->CallVoidMethod(env,clazz,methodID);
    
     }	
    

    ]

  • 2.1 :【如果想让C回调Java实现弹出Toast】

    两种实现方式:要么将回调方法与native方法都放到MainActivity中。(本地函数在反射时就获取MainActivity的字节码对象)

      		  要么将这两个方法都放到JNI.java中,然后为JNI类添加一个构造方法用于接收上下文,否则Toast无法弹出。----【推荐】
    

    【详细实现过程:

    		public class JNI {
    			static{
    				System.loadLibrary("callback");
    			}
    			//弹出Toast需要上下文
    			private Context mContext;
    			public JNI(Context context){
    				mContext = context;
    			}
    
    			//native方法,用于沟通java与C
    			public native void callbackvoidmethod();
    			public native void callbackintmethod();
    			public native void callbackStringmethod();
    			public native void callbackShowToast();
    
    			/**
    			 * 定义几个回调方法,这些方法由C函数通过反射实现调用
    			 */
    			//C调用java空方法
    			public void helloFromJava(){
    				System.out.println("hello from java");
    			}
    
    
    			//C调用java中的带两个int参数的方法
    			public int add(int x,int y) {
    				return x+y;
    			}
    			
    			//C调用java中参数为string的方法
    			public void printString(String s){
    				System.out.println(s);
    			}
    			public void showToast(String s){
    				Toast.makeText(mContext, s, 0).show();
    			}
    		}	
    
    • jni/callback.c :
    
    		#include <jni.h>
    			#include <stdlib.h>
    			#include <android/log.h>
    			#define LOG_TAG "System.out"
    			#define LOGD(...) __android_log_print(ANDROID_LOG_DEBUG, LOG_TAG, __VA_ARGS__)
    
    			JNIEXPORT void JNICALL Java_com_itheima_callbackjava_JNI_callbackvoidmethod
    			  (JNIEnv * env, jobject clazz){
    				//C函数 通过反射调用java中的方法
    
    				//[1]:首先获取需要被反射回调的方法所在的类的字节码对象
    
    				//jclass      (*FindClass)(JNIEnv*, const char*);
    				jclass claz = (*env)->FindClass(env,"com/itheima/callbackjava/JNI");
    
    				//[2]:获取需要被反射回调的方法的 methodId
    				//jmethodID   (*GetMethodID)(JNIEnv*, jclass, const char*, const char*);
    				jmethodID methodID =(*env)->GetMethodID(env,claz,"helloFromJava","()V");
    
    				//[3]:通过字节码对象创建一个Object
    				//由于本例中,需要被回到的方法与native方法位于同一个类中,因此可以直接使用本地函数的参数  clazz 。
    
    				//[4]:通过对象调用方法
    				//void        (*CallVoidMethod)(JNIEnv*, jobject, jmethodID, ...);
    				(*env)->CallVoidMethod(env,clazz,methodID);
    
    			}
    
    			JNIEXPORT void JNICALL Java_com_itheima_callbackjava_JNI_callbackintmethod (JNIEnv * env, jobject clazz){
    
    				jclass claz =(*env)->FindClass(env,"com/itheima/callbackjava/JNI");
    
    				jmethodID methodID = (*env)->GetMethodID(env,claz,"add","(II)I");
    				//jint        (*CallIntMethod)(JNIEnv*, jobject, jmethodID, ...);
    				int result =(*env)->CallIntMethod(env,clazz,methodID,3,4);
    				LOGD("result = %d",result);
    			}
    
    			JNIEXPORT void JNICALL Java_com_itheima_callbackjava_JNI_callbackStringmethod
    			  (JNIEnv * env, jobject clazz){
    
    				jclass claz =(*env)->FindClass(env,"com/itheima/callbackjava/JNI");
    				jmethodID methodid =(*env)->GetMethodID(env,claz,"printString","(Ljava/lang/String;)V");
    				jstring result =(*env)->NewStringUTF(env,"hello from c");
    
    				(*env)->CallVoidMethod(env,clazz,methodid,result);
    			}
    
    			JNIEXPORT void JNICALL Java_com_itheima_callbackjava_JNI_callbackShowToast
    			  (JNIEnv * env, jobject clazz){
    
    				jclass claz =(*env)->FindClass(env,"com/itheima/callbackjava/JNI");
    				jmethodID methodid =(*env)->GetMethodID(env,claz,"showToast","(Ljava/lang/String;)V");
    
    				//如果:回调方法与native方法不再同一个类中,则需要获取回调方法所在类的对象。
    				//jobject     (*AllocObject)(JNIEnv*, jclass);
    
    				jstring result =(*env)->NewStringUTF(env,"hello from c");
    				//void        (*CallVoidMethod)(JNIEnv*, jobject, jmethodID, ...);
    				(*env)->CallVoidMethod(env,clazz,methodid,result);
    			}
    
    • Android.mk

      LOCAL_PATH := $(call my-dir)

      include $(CLEAR_VARS)

      LOCAL_MODULE := callback
      LOCAL_SRC_FILES := callback.c

      LOCAL_LDLIBS += -llog
      include $(BUILD_SHARED_LIBRARY)

    • Application.mk

      APP_ABI := armeabi x86 #则只支持arm平台与x86平台
      APP_PLATFORM := android-14 #项目支持的android sdk的最低版本号

    最后在MainActivity调用native方法。

总结:Java调C —需要利用native方法。(调native方法实际上调用的是libs/xxx/xxx.so文件,里面的本地函数)

	C调Java ---需要反射回调。

总结:实际开发中一般会创建一个JNI类,专门用于定义native方法,并且发布项目时JNI这个类也不允许被混淆,

	因为里面的函数名在.c文件中有对应格式的本地函数,且交叉编译时就生成了对应的.so文件。因此不允许对JNI这个类进行代码混淆。

	如果混淆将会导致native方法调用本地c函数失败!!
  • Tips : JNI开发是如果莫名出现一些报错:

    可以先clean project ,或者把项目关闭然后重新打开。

您可能感兴趣的与本文相关的镜像

ACE-Step

ACE-Step

音乐合成
ACE-Step

ACE-Step是由中国团队阶跃星辰(StepFun)与ACE Studio联手打造的开源音乐生成模型。 它拥有3.5B参数量,支持快速高质量生成、强可控性和易于拓展的特点。 最厉害的是,它可以生成多种语言的歌曲,包括但不限于中文、英文、日文等19种语言

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值