JNI学习小结

Java中类型与C/C++中对应关系
 
Java中的类的对应
 
Sign签名, 用来识别对应各个方法。
JDK下的javap.exe能输出签名。用法javap -s -p 完整类名
 
 

  下面是几个例子程序:

1、C++本地方法中获取Java中的变量以及调用Java中的方法

  Java代码:

复制代码
 1 package com.test;
 2 
 3 import java.util.Date;
 4 
 5 public class TestNative {
 6     public native void sayHello();
 7     private int a = 10;
 8     public int function(int x,Date date,int[] y){
 9         System.out.println("function");
10         return 0;
11     }
12     public double max(double a,double b){
13         return a>b?a:b;
14     }
15     /**
16      * @param args
17      */
18     public static void main(String[] args) {
19         System.loadLibrary("NativeCode");
20         TestNative tNative = new TestNative();
21         tNative.sayHello();
22     }
23 
24 }
复制代码

  C++本地代码:

  com_test_TestNative.h代码省略了

复制代码
 1 #include<iostream.h>
 2 #include"com_test_TestNative.h"
 3 
 4 JNIEXPORT void JNICALL Java_com_test_TestNative_sayHello(JNIEnv *env, jobject obj)
 5 {
 6     //因为sayHello不是静态函数,所以传进来的obj就是调用这个函数的对象,否则就是传入native方法所在的类
 7     jclass hello_clazz = env->GetObjectClass(obj); //得到的就是native方法所在的类
 8 //    jfieldID fieldId_a = env->GetFieldID(hello_clazz,"a","I");
 9 //    jmethodID methodId_fun = env->GetMethodID(hello_clazz,"function","(ILjava/util/Date;[I)I");
10 //    env->CallIntMethod(obj,methodId_fun,0L,NULL,NULL); //方法调用
11 //    cout<<"hello world!"<<endl;
12 //    cout<<"successful"<<endl;
13 /*
14     jfieldID field_a = env->GetFieldID(hello_clazz,"a","I");  //得到字段a的ID
15     jint a = env->GetIntField(obj,field_a);   //得到字段a的值
16     cout<<a<<endl;
17     env->SetIntField(obj,field_a,100L);
18 */
19     jmethodID methodId_max = env->GetMethodID(hello_clazz,"max","(DD)D");
20     jvalue *values = new jvalue[2];  //jvalue是用来向java函数中传参数的
21     values[0].d = 3.14;
22     values[1].d = 3.22;
23     //jdouble max = env->CallDoubleMethod(obj,methodId_max,3.18,3.15); //第一种方法调用
24     jdouble max = env->CallDoubleMethodA(obj,methodId_max,values); //第二种方法调用
25     delete [] values;
26     cout<<max<<endl;
27 
28 }
复制代码


2、C++本地方法中对Java中的字符串操作(实现用户输入一个字符串,在C++中对其反转)

  Java代码:

复制代码
 1 package com.test;
 2 
 3 import java.io.BufferedReader;
 4 import java.io.IOException;
 5 import java.io.InputStreamReader;
 6 
 7 public class TestStr {
 8     public native void cppCode();
 9     private String message;
10     public static void main(String[] args) throws IOException {
11         System.loadLibrary("CPP");
12         BufferedReader reader = new BufferedReader(new InputStreamReader(System.in));
13         String str = reader.readLine();
14         TestStr obj = new TestStr();
15         obj.message = str;
16         obj.cppCode();
17         System.out.println("java :"+obj.message);
18     }
19 
20 }
复制代码

  c++本地方法:

  com_test_TestStr.h省略了

复制代码
 1 #include "com_test_TestStr.h"
 2 #include<windows.h>
 3 #include<string>
 4 #include<algorithm>
 5 using namespace std;
 6 JNIEXPORT void JNICALL Java_com_test_TestStr_cppCode(JNIEnv *env, jobject obj)
 7 {
 8     jfieldID fid_msg = env->GetFieldID(env->GetObjectClass(obj),"message","Ljava/lang/String;");
 9     jstring j_msg = (jstring)env->GetObjectField(obj,fid_msg);
10     jsize len = env->GetStringLength(j_msg);   //得到字符串的长度
11     jchar* jstr = new jchar[len+1];    //申请空间
12     jstr[len] = L'\0';
13     env->GetStringRegion(j_msg,0,len,jstr);
14     //env->ReleaseStringChars(j_msg,jstr);
15     //MessageBoxW(NULL,(const wchar_t*)jstr,L"Title",MB_OK);
16     wstring wstr((const wchar_t*)jstr);
17     delete[] jstr;  //释放空间
18     std::reverse(wstr.begin(),wstr.end());  //字符串反转
19     jstring j_new_str = env->NewString((const jchar*)wstr.c_str(),(jint)wstr.size());   //创建一个新的字符串
20     env->SetObjectField(obj,fid_msg,j_new_str);  //设置字符串给java对象
21 
22 }
复制代码

 

3、C++本地方法中获取Java中的数组(实现用C++本地方法将Java中的数组排序)

  Java代码:

复制代码
 1 package com.test;
 2 
 3 public class TestArray {
 4 
 5     public int[] array = {5,7,3,4,1,9,2,8,6,0};
 6     
 7     public native void callCppFun();
 8     
 9     public static void main(String[] args) {
10         System.loadLibrary("TestArray");
11         TestArray obj = new TestArray();
12         obj.callCppFun();
13         for(int a:obj.array){
14             System.out.println(a);
15         }
16     }
17 
18 }
复制代码

  C++本地代码:

  com_test_TestArray.h省略了

复制代码
 1 #include"com_test_TestArray.h"
 2 #include<iostream>
 3 #include<algorithm>
 4 using namespace std;
 5 JNIEXPORT void JNICALL Java_com_test_TestArray_callCppFun (JNIEnv *env, jobject obj)
 6 {
 7     jfieldID fid_array = env->GetFieldID(env->GetObjectClass(obj),"array","[I"); //获取数组的id
 8     jintArray jint_array = (jintArray)env->GetObjectField(obj,fid_array);   
 9 
10     jint* int_arr = env->GetIntArrayElements(jint_array,NULL); //转化为jint数组
11     jsize len = env->GetArrayLength(jint_array);
12     std::sort(int_arr,int_arr+len);  //对数组进行排序
13     env->ReleaseIntArrayElements(jint_array,int_arr,0); //0:释放C++数组,并且更新到Java
14 /*    for(jsize i=0;i<len;i++){
15         cout<<int_arr[i]<<endl;
16     }
17 */
18  //   env->ReleaseIntArrayElements(jint_array,int_arr,JNI_ABORT);//JNI_ABORT:释放C++数组,但是不更新到Java  
19 }
 
 
 
 
  
  
使用Android Sutdio创建一个新的工程后,接下来记录创建NDK工程的基本步骤。

本文将达到:
1. 创建NDK工程
2. 在JNI中输出Log语句
3. 指定编译的so库的abi版本
4. 解决在创建NDK工程中的问题

Step: 1. 添加native接口
注意写好native接口和System.loadLibrary()即可了,并无特别之处。
P.S:onCreate()中对R.id.txt执行setText(),所以这里需要对xml布局文件按正常的开发步骤进行修改即可。

直接给出代码如下:
[java]  view plain copy 在CODE上查看代码片 派生到我的代码片
  1. public class MainActivity extends Activity{  
  2.     static {  
  3.         System.loadLibrary("JniTest");  
  4.     }  
  5.      
  6.     public native String getStringFromNative();  
  7.     @Override  
  8.     protected void onCreate(Bundle savedInstanceState) {  
  9.         super.onCreate(savedInstanceState);  
  10.         setContentView(R.layout.activity_main);  
  11.         TextView txtView = (TextView) findViewById(R.id.txt);  
  12.         txtView.setText(getStringFromNative());  
  13.     }  
  14. }  

Step: 2.执行Build->Make Project

这一步骤执行一下,验证工程中并无其它错误,并对工程进行了编译,生成了.class文件.
.class文件的生成路径是在 app_path/build/intermediates/classes/debug下的.如下图:

Step: 3.javah生成c头文件

点击"View->Tool Windows->Terminal",即在Studio中进行终端命令行工具.执行如下命令生成c语言头文件。
这里需要注意的是要进入 <Project>\app\src\main的目录下执行javah命令,为的是生成的 .h 文件同样是在<Project>\app\src\main路径下,可以在Studio的工程结构中直接看到。

操作命令:
javah -d jni -classpath <SDK_android.jar>;<APP_classes> lab.sodino.jnitest.MainActivity
具体操作图如下:

[java]  view plain copy 在CODE上查看代码片 派生到我的代码片
  1. javah -d jni -classpath c:\Users\sodinochen\AppData\Local\Android\sdk\platforms  
  2. \android-16\android.jar;..\..\build\intermediates\classes\debug lab.sodino.jnitest.MainActivity  


对于"主版本51比50新,此编译器支持最新的主版本"则是由于电脑上安装了两个版本的jdk引起的,而当前使用的是旧的jdk。
把旧的jdk删除,并执行java version命令后显示当前jdk为最新的1.7时,则不会再有此提示了。如下图:

最后的生成结果:

Step: 4.编辑c文件

在main.c文件中实现头文件中的方法,具体功能为直接return回一个String,并且使用android_log打印出相关日志。
代码如下:
[cpp]  view plain copy 在CODE上查看代码片 派生到我的代码片
  1. /* DO NOT EDIT THIS FILE - it is machine generated */  
  2. #include <jni.h>  
  3. #include <android/log.h>  
  4.   
  5. #ifndef LOG_TAG  
  6. #define LOG_TAG "ANDROID_LAB"  
  7. #define LOGE(...) __android_log_print(ANDROID_LOG_ERROR, LOG_TAG, __VA_ARGS__)  
  8. #endif  
  9.   
  10. /* Header for class lab_sodino_jnitest_MainActivity */  
  11.   
  12. #ifndef _Included_lab_sodino_jnitest_MainActivity  
  13. #define _Included_lab_sodino_jnitest_MainActivity  
  14. #ifdef __cplusplus  
  15. extern "C" {  
  16. #endif  
  17. /* 
  18.  * Class: lab_sodino_jnitest_MainActivity 
  19.  * Method: getStringFromNative 
  20.  * Signature: ()Ljava/lang/String; 
  21.  */  
  22. JNIEXPORT jstring JNICALL Java_lab_sodino_jnitest_MainActivity_getStringFromNative  
  23.   (JNIEnv * env, jobject jObj){  
  24.       LOGE("log string from ndk.");  
  25.       return (*env)->NewStringUTF(env,"Hello From JNI!");  
  26.   }  
  27.   
  28. #ifdef __cplusplus  
  29. }  
  30. #endif  
  31. #endif  
到这里后,我们再执行一个"Build->Make Project",发现"Messages Gradle Build"会给出提示如下:
[java]  view plain copy 在CODE上查看代码片 派生到我的代码片
  1. Error:Execution failed for task ':app:compileDebugNdk'.   
  2. > NDK not configured.   
  3. Download the NDK from http://developer.android.com/tools/sdk/ndk/.Then add ndk.dir=path/to/ndk in local.properties.   
  4. (On Windows, make sure you escape backslashes, e.g. C:\\ndk rather than C:\ndk)  
这里提示了NDK未配置,并且需要在工程中的local.properties文件中配置NDK路径。好了,提示很清楚了,那我们就进入下一步吧。

Step: 5.配置NDK
这一步包括两个动作:
1.指明ndk路径
2. 修改build.gradle配置
    工程中共有两个build.gradle配置文件,我们要修改的是在<Project>\app\build.gradle这个文件。为其在defaultConfig分支中增加上
[java]  view plain copy 在CODE上查看代码片 派生到我的代码片
  1. ndk {  
  2.     moduleName "JniTest"  
  3.     ldLibs "log""z""m"  
  4.     abiFilters "armeabi""armeabi-v7a""x86"  
  5. }  
    以上配置代码指定的so库名称为JniTest,链接时使用到的库,对应android.mk文件中的LOCAL_LDLIBS,及最终输出指定三种abi体系结构下的so库。
添加后如下图:
这时,再执行"Build->Rebuild Project",就可以编译出so文件了。
但在Window平台上会出现一个问题:
[java]  view plain copy 在CODE上查看代码片 派生到我的代码片
  1. Error:Execution failed for task ':app:compileDebugNdk'.  
  2. > com.android.ide.common.internal.LoggedErrorException: Failed to run command:  
  3.  D:\Mission\adt-bundle-windows\ndk-r10b\ndk-build.cmd NDK_PROJECT_PATH=null APP_BUILD_SCRIPT=C:\Users\sodinochen\AndroidstudioProjects\JniTest2\app\build\intermediates\ndk\debug\Android.mk APP_PLATFORM=android-21 NDK_OUT=C:\Users\sodinochen\AndroidstudioProjects\JniTest2\app\build\intermediates\ndk\debug\obj NDK_LIBS_OUT=C:\Users\sodinochen\AndroidstudioProjects\JniTest2\app\build\intermediates\ndk\debug\lib APP_ABI=armeabi,armeabi-v7a,x86  
  4. Error Code:  
  5.  2  
  6. Output:  
  7.  make.exe: *** No rule to make target `C:\Users\sodinochen\AndroidstudioProjects\JniTest2\app\build\intermediates\ndk\debug\obj/local/armeabi/objs/JniTest/C_\Users\sodinochen\AndroidstudioProjects\JniTest2\app\src\main\jni', needed by `C:\Users\sodinochen\AndroidstudioProjects\JniTest2\app\build\intermediates\ndk\debug\obj/local/armeabi/objs/JniTest/C_\Users\sodinochen\AndroidstudioProjects\JniTest2\app\src\main\jni\main.o'. Stop.  
出现这个错误很莫名其妙..几番折腾下,找到一个视频出来了大概原因及解决方式:
出处见Youtube视频 02:50分开始: https://www.youtube.com/watch?v=okLKfxfbz40#t=362
在Windows下NDK一个bug,当仅仅编译一个文件时出现会出现此问题,解决方法就是 再往jni文件夹加入一个空util.c文件即可。如下图:
编译出来的库文件被Studio输出到了下图的路径中

Step: 6.安装运行

界面:

查看Log打印:

 
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值