Android 之 JNI 开发 详解 - NDK从入门到精通
NDK项目源码地址 :
-- 第一个JNI示例程序下载 : GitHub - https://github.com/han1202012/NDKHelloworld.git
一. JNI介绍
1. JNI引入
JNI概念 : Java本地接口, Java Native Interface, 它是一个 协议, 该协议用来沟通Java代码和外部的本地C/C++代码, 通过该协议 Java代码可以调用外部的本地代码, 外部的C/C++ 代码可以调用Java代码;
C和Java的侧重 :
-- C语言 : C语言中最重要的是 函数 function;
-- Java语言 : Java中最重要的是 JVM, class类, 以及class中的方法;
C与Java如何交流 :
-- JNI规范 : C语言与Java语言交流需要一个适配器, 中间件, 即 JNI, JNI提供了一种规范;
-- C语言中调用Java方法 : 可以让我们在C代码中找到Java代码class中的方法, 并且调用该方法;
-- Java语言中调用C语言方法 : 同时也可以在Java代码中, 将一个C语言的方法映射到Java的某个方法上;
-- JNI桥梁作用 : JNI提供了一个桥梁, 打通了C语言和Java语言之间的障碍;
2. Android中的应用程序框架
正常情况下的Android框架 : 最 顶层是 Android的应用程序代码, 是纯Java代码, 中间有一层的 Framework框架层代码是 C/C++代码, 通过Framework进行系统调用, 调用底层的库 和 linux 内核;
使用JNI时的Android框架 : 绕过Framework提供的调用底层的代码, 直接调用自己写的C代码, 该代码最终会编译成为一个库, 这个库通过JNI提供的一个Stable的ABI 调用linux kernel; ABI是二进制程序接口 application binary interface.
3. JNI作用
JNI作用 :
-- 扩展: JNI扩展了JVM能力, 驱动开发, 例如开发一个wifi驱动, 可以将手机设置为无限路由;
-- 高效 : 本地代码效率高, 游戏渲染, 音频视频处理等方面使用JNI调用本地代码, C语言可以 灵活操作内存;
-- 复用 : 在文件压缩算法 7zip开源代码库, 机器视觉 openCV开放算法库 等方面可以复用C平台上的代码, 不必在开发一套完整的Java体系, 避免重复发明轮子;
-- 特殊 : 产品的核心技术一般也采用JNI开发, 不易破解;
Java语言执行流程 :
-- 编译字节码 : Java编译器编译 .java源文件, 获得.class 字节码文件;
-- 装载类库 : 使用类装载器装载平台上的Java类库, 并进行字节码验证;
-- Java虚拟机 : 将字节码加入到JVM中, Java解释器 和 即时编译器 同时处理字节码文件, 将处理后的结果放入运行时系统;
-- 调用JVM所在平台类库 : JVM处理字节码后, 转换成相应平台的操作, 调用本平台底层类库进行相关处理;
Java一次编译到处执行 : JVM在不同的操作系统都有实现, Java可以一次编译到处运行, 字节码文件一旦编译好了, 可以放在任何平台的虚拟机上运行;
.
二. NDK详解
1. 交叉编译库文件
C代码执行 : C代码被编译成库文件之后, 才能执行, 库文件分为 动态库 和 静态库 两种;
-- 动态库 : unix环境下 .so 后缀的是动态库, windows环境下 .dll 后缀的是动态库; 动态库可以依赖静态库加载一些可执行的C代码;
-- 静态库 : .a 后缀是静态库的扩展名;
库文件来源 : C代码 进行 编译 链接操作之后, 才会生成库文件, 不同类型的CPU 操作系统 生成的库文件是不一样;
-- CPU分类 : arm结构, 嵌入式设备处理器; x86结构, pc 服务器处理器; 不同的CPU指令集不同;
-- 交叉编译 : windows x86编译出来的库文件可以在arm平台运行的代码;
-- 交叉编译工具链 : Google提供的 NDK 就是交叉编译工具链, 可以在linux环境下编译出在arn平台下执行的二进制库文件;
NDK作用 : 是Google提供了交叉编译工具链, 能够在 linux平台编译出在 arm平台下执行的二进制库文件;
NDK版本介绍 : android-ndk-windows 是在windows系统中的cygwin使用的, android-ndk-linux 是在linux下使用的;
2. 部署NDK开发环境
(1) 下载Cygwin安装器
下载地址 : http://cygwin.com/setup-x86.exe , 这是下载器, 可以使用该下载器在线安装, 也可以将cygwin下载到本地之后, 在进行安装;
安装器使用 : Cygwin的下载, 在线安装, 卸载 等操作都有由该安装器进行;
-- 本地文件安装 : 选择安装文件所在的目录, 然后选择所要安装的安装包;
-- 在线安装 : 选择在线安装即可, 然后选择需要的安装包;
-- 卸载 : windows上使用其它软件例如360, 控制面板中是无法卸载Cygwin的, 只能通过安装器来卸载;
(2) 安装Cygin
双击安装器 setup-x86.exe 下一步 :
选择安装方式 :
-- 在线安装 : 直接下载, 然后安装;
-- 下载安装文件 : 将安装文件下载下来, 可以随时安装, 注意安装文件也需要安装器来进行安装;
-- 从本地文件安装 : 即使用下载的安装文件进行安装;
选择Cygwin安装位置 :
选择下载好安装文件位置 : 之前我下了一个完全版的Cygwin, 包括了所有的Cygwin组件, 全部加起来有5.23G, 下载速度很快, 使用网易的镜像, 基本可以全速下载;
选择需要安装Cygwin组件 : 这里我们只需要以下组件 : binutils , gcc , gcc-mingw , gdb , make , 不用下全部的组件;
之后点击下一步等待完成安装即可;
.
安装完之后, 打开bash命令窗口, 可以设置下显示的字体, 使用 make -version 查看是否安装成功 :
(3) Cygwin目录介绍
以下是Cygwin安装目录的情况 : 该安装目录就是所模拟的 linux 的根目录;
对应的linux目录 : 这两个目录进行对比发现, 两个目录是一样的, Cygwin的安装目录就是 linux根目录;
cygdrive目录 : 该目录是Cygwin模拟出来的windows目录结构, 进入该目录后, 会发现windows的盘符目录, 通过该目录可以 访问windows中的文件;
(4) 下载NDK工具
从Google的Android开发者官网上下载该工具, 注意NDK工具分类 : 下载地址 - http://developer.android.com/tools/sdk/ndk/index.html -;
-- windows版本NDK: android-ndk-r9c-windows-x86.zip (32位), android-ndk-r9c-windows-x86_64.zip (64位) 该版本是用在windows上的Cygwin下, 不能直接在windows上直接运行;
-- linux版本NDK : android-ndk-r9c-linux-x86.tar.bz2(32位) , android-ndk-r9c-linux-x86_64.tar.bz2 (64位) , 该版本直接在linux下执行即可;
在这里下载windows版本的NDK, 运行在Cygwin上;
(4) NDK环境介绍
NDK工具的文件结构 :
ndk-build脚本 : NDK build 脚本是 gun-make 的简单封装, gun-make 是编译C语言代码的工具, 该脚本执行的前提是linux环境下必须安装 make 程序;
NDK安装在Cygwin中 : 将NDK压缩文件拷贝到Cygwin的根目录中, 解压 : android-ndk-r9c 目录就是NDK目录;
执行以下NDK目录下的 ndk-build 命令 : ./ndk-build ;
执行结果 :
Android NDK: Could not find application project directory ! Android NDK: Please define the NDK_PROJECT_PATH variable to point to it. /android-ndk-r9c/build/core/build-local.mk:148: *** Android NDK: Aborting 。 停止。
三. 开发第一个NDK程序
1. 开发NDK程序流程
a. 创建Android工程:
首选创建一个Android工程, 在这个工程中进行JNI开发;
b. 声明native方法 :
注意方法名使用 native 修饰, 没有方法体 和 参数, eg : public native String helloFromJNI();
c. 创建C文件 :
在工程根目录下创建 jni 目录, 然后创建一个c语言源文件, 在文件中引入 include <jni.h> , C语言方法声明格式 jstring Java_shuliang.han.ndkhelloworld_MainActivity_helloFromJNI(JNIEnv *env) , jstring 是 Java语言中的String类型, 方法名格式为 : Java_完整包名类名_方法名();
-- JNIEnv参数 : 代表的是Java环境, 通过这个环境可以调用Java里面的方法;
-- jobject参数 : 调用C语言方法的对象, thiz对象表示当前的对象, 即调用JNI方法所在的类;
d. 编写Android.mk文件 :
如何写 查看文档, NDK根目录下有一个 documentation.html 文档, 点击该html文件就可以查看文档, 查看 Android.mk File 文档, 下面是该文档给出的 Android.mk示例 :
LOCAL_PATH := $(call my-dir) include $(CLEAR_VARS) LOCAL_MODULE := hello-jni LOCAL_SRC_FILES := hello-jni.c include $(BUILD_SHARED_LIBRARY)
-- include $(CLEAR_VARS) : 编译工具函数, 通过该函数可以进行一些初始化操作;
-- LOCAL_MODULE : 编译后的 .so 后缀文件叫什么名字;
-- LOCAL_SRC_FILES: 指定编译的源文件名称;
-- include $(BUILD_SHARED_LIBRARY) : 告诉编译器需要生成动态库;
e. NDK编译生成动态库 :
进入 cygdrive 找到windows目录下对应的文件, 编译完成之后, 会 自动生成so文件并放在libs目录下, 之后就可以在Java中调用C语言方法了;
f. Java中加载动态库 :
在Java类中的静态代码块中使用System.LoadLibrary()方法加载编译好的 .so 动态库;
NDK平台版本 : NDK脚本随着 android-sdk 版本不同, 执行的脚本也是不同的, 不同平台会引用不同的头文件, 编译的时候一定注意 sdk 与 ndk 版本要一致;
so文件在内存中位置 : apk文件安装到手机上之后, .so动态库文件存在在 data/安装目录/libs 目录下;
2. 开发实例
(1) 创建Android工程
<uses-sdk android:minSdkVersion="7" android:targetSdkVersion="10" />
(2) 声明native方法
/* * 声明一个native方法 * 这个方法在Java中是没有实现的, 没有方法体 * 该方法需要使用C语言编写 */ public native String helloFromJNI();
(3) 创建C文件
jstring (*NewString)(JNIEnv*, const jchar*, jsize); jsize (*GetStringLength)(JNIEnv*, jstring); const jchar* (*GetStringChars)(JNIEnv*, jstring, jboolean*); void (*ReleaseStringChars)(JNIEnv*, jstring, const jchar*); jstring (*NewStringUTF)(JNIEnv*, const char*); jsize (*GetStringUTFLength)(JNIEnv*, jstring);
#include <jni.h> /* * 方法名称规定 : Java_完整包名类名_方法名() * JNIEnv 指针 * * 参数介绍 : * env : 代表Java环境, 通过这个环境可以调用Java中的方法 * thiz : 代表调用JNI方法的对象, 即MainActivity对象 */ jstring Java_shuliang_han_ndkhelloworld_MainActivity_helloFromJNI(JNIEnv *env, jobject thiz) { /* * 调用 android-ndk-r9c\platforms\android-8\arch-arm\usr\include 中jni.h中的方法 * jni.h 中定义的方法 jstring (*NewStringUTF)(JNIEnv*, const char*); */ return (*env)->NewStringUTF(env, "hello world jni"); }
(4) 编写Android.mk文件
LOCAL_PATH := $(call my-dir) include $(CLEAR_VARS) LOCAL_MODULE := hello LOCAL_SRC_FILES := hello.c include $(BUILD_SHARED_LIBRARY)
(5) 编译NDK动态库
(6) Java中加载动态库
//静态代码块加载C语言库文件 static{ System.loadLibrary("hello"); }
(7) 其它源码
package shuliang.han.ndkhelloworld; import android.app.Activity; import android.os.Bundle; import android.view.View; import android.widget.Toast; public class MainActivity extends Activity { //静态代码块加载C语言库文件 static{ System.loadLibrary("hello"); } /* * 声明一个native方法 * 这个方法在Java中是没有实现的, 没有方法体 * 该方法需要使用C语言编写 */ public native String helloFromJNI(); @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); System.out.println(helloFromJNI()); } public void onClick(View view) { //点击按钮显示从jni调用得到的字符串信息 Toast.makeText(getApplicationContext(), helloFromJNI(), 1).show(); } }
XML布局文件 :
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent" android:paddingBottom="@dimen/activity_vertical_margin" android:paddingLeft="@dimen/activity_horizontal_margin" android:paddingRight="@dimen/activity_horizontal_margin" android:paddingTop="@dimen/activity_vertical_margin" tools:context=".MainActivity" > <Button android:id="@+id/bt" android:layout_width="wrap_content" android:layout_height="wrap_content" android:onClick="onClick" android:text="显示JNI返回的字符串" /> </RelativeLayout>
(8) 将源码上传到GitHub中
touch README.md git init git add README.md git commit -m "first commit" git remote add origin [email protected]:han1202012/NDKHelloworld.git git push -u origin master
打开 Git Bash 命令行窗口 :
-- 添加文件 : git add ./* , 将目录中所有文件添加;
-- 查看状态 : git status ;
-- 提交到远程GitHub仓库 : git push -u origin master ;
3. 项目讲解
(1) Android.mk文件讲解
LOCAL_PATH := $(call my-dir) include $(CLEAR_VARS) LOCAL_MODULE := hello LOCAL_SRC_FILES := hello.c include $(BUILD_SHARED_LIBRARY)
(2) 自动生成方法签名
(3) NDK开发中乱码问题
#include <jni.h> /* * 方法名称规定 : Java_完整包名类名_方法名() * JNIEnv 指针 * * 参数介绍 : * env : 代表Java环境, 通过这个环境可以调用Java中的方法 * thiz : 代表调用JNI方法的对象, 即MainActivity对象 */ jstring Java_shuliang_han_ndkhelloworld_MainActivity_helloFromJNI(JNIEnv *env, jobject thiz) { /* * 调用 android-ndk-r9c\platforms\android-8\arch-arm\usr\include 中jni.h中的方法 * jni.h 中定义的方法 jstring (*NewStringUTF)(JNIEnv*, const char*); */ return (*env)->NewStringUTF(env, "hello world jni 中文"); }
使用NDK重新编译hello.c文件 : 修改了C源码之后, 重新将该c文件编译成so文件;
01-31 14:36:04.803: W/dalvikvm(389): JNI WARNING: illegal continuation byte 0xd0 01-31 14:36:04.803: W/dalvikvm(389): string: 'hello world jni ����' 01-31 14:36:04.803: W/dalvikvm(389): in Lshuliang/han/ndkhelloworld/MainActivity;.helloFromJNI ()Ljava/lang/String; (NewStringUTF) 01-31 14:36:04.834: I/dalvikvm(389): "main" prio=5 tid=1 NATIVE 01-31 14:36:04.834: I/dalvikvm(389): | group="main" sCount=0 dsCount=0 obj=0x4001f1a8 self=0xce48 01-31 14:36:04.834: I/dalvikvm(389): | sysTid=389 nice=0 sched=0/0 cgrp=default handle=-1345006528 01-31 14:36:04.844: I/dalvikvm(389): | schedstat=( 257006717 305462830 51 ) 01-31 14:36:04.844: I/dalvikvm(389): at shuliang.han.ndkhelloworld.MainActivity.helloFromJNI(Native Method) 01-31 14:36:04.844: I/dalvikvm(389): at shuliang.han.ndkhelloworld.MainActivity.onCreate(MainActivity.java:26) 01-31 14:36:04.844: I/dalvikvm(389): at android.app.Instrumentation.callActivityOnCreate(Instrumentation.java:1047) 01-31 14:36:04.853: I/dalvikvm(389): at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:1611) 01-31 14:36:04.853: I/dalvikvm(389): at android.app.ActivityThread.handleLaunchActivity(ActivityThread.java:1663) 01-31 14:36:04.853: I/dalvikvm(389): at android.app.ActivityThread.access$1500(ActivityThread.java:117) 01-31 14:36:04.864: I/dalvikvm(389): at android.app.ActivityThread$H.handleMessage(ActivityThread.java:931) 01-31 14:36:04.864: I/dalvikvm(389): at android.os.Handler.dispatchMessage(Handler.java:99) 01-31 14:36:04.864: I/dalvikvm(389): at android.os.Looper.loop(Looper.java:123) 01-31 14:36:04.864: I/dalvikvm(389): at android.app.ActivityThread.main(ActivityThread.java:3683) 01-31 14:36:04.864: I/dalvikvm(389): at java.lang.reflect.Method.invokeNative(Native Method) 01-31 14:36:04.874: I/dalvikvm(389): at java.lang.reflect.Method.invoke(Method.java:507) 01-31 14:36:04.874: I/dalvikvm(389): at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:839) 01-31 14:36:04.874: I/dalvikvm(389): at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:597) 01-31 14:36:04.874: I/dalvikvm(389): at dalvik.system.NativeStart.main(Native Method) 01-31 14:36:04.884: E/dalvikvm(389): VM aborting
四. Java传递数据给C语言与日志打印
1. JNI数据类型
Java数据类型 | C本地类型 | JNI定义别名 |
int | long | jint/jsize |
long | __int64 | jlong |
byte | signed char | jbyte |
boolean | unsigned char | jboolean |
char | unsigned short | jchar |
short | short | jshort |
float | float | jfloat |
double | doyble | jdouble |
object' |
_jobject | jobject |
jobject (*CallObjectMethodA)(JNIEnv*, jobject, jmethodID, jvalue*); jboolean (*CallBooleanMethod)(JNIEnv*, jobject, jmethodID, ...); jboolean (*CallBooleanMethodV)(JNIEnv*, jobject, jmethodID, va_list); jboolean (*CallBooleanMethodA)(JNIEnv*, jobject, jmethodID, jvalue*); jbyte (*CallByteMethod)(JNIEnv*, jobject, jmethodID, ...); jbyte (*CallByteMethodV)(JNIEnv*, jobject, jmethodID, va_list); jbyte (*CallByteMethodA)(JNIEnv*, jobject, jmethodID, jvalue*); jchar (*CallCharMethod)(JNIEnv*, jobject, jmethodID, ...); jchar (*CallCharMethodV)(JNIEnv*, jobject, jmethodID, va_list); jchar (*CallCharMethodA)(JNIEnv*, jobject, jmethodID, jvalue*); jshort (*CallShortMethod)(JNIEnv*, jobject, jmethodID, ...); jshort (*CallShortMethodV)(JNIEnv*, jobject, jmethodID, va_list); jshort (*CallShortMethodA)(JNIEnv*, jobject, jmethodID, jvalue*); jint (*CallIntMethod)(JNIEnv*, jobject, jmethodID, ...); jint (*CallIntMethodV)(JNIEnv*, jobject, jmethodID, va_list); jint (*CallIntMethodA)(JNIEnv*, jobject, jmethodID, jvalue*); jlong (*CallLongMethod)(JNIEnv*, jobject, jmethodID, ...); jlong (*CallLongMethodV)(JNIEnv*, jobject, jmethodID, va_list); jbooleanArray (*NewBooleanArray)(JNIEnv*, jsize); jbyteArray (*NewByteArray)(JNIEnv*, jsize); jcharArray (*NewCharArray)(JNIEnv*, jsize); jshortArray (*NewShortArray)(JNIEnv*, jsize); jintArray (*NewIntArray)(JNIEnv*, jsize); jlongArray (*NewLongArray)(JNIEnv*, jsize); jfloatArray (*NewFloatArray)(JNIEnv*, jsize); jdoubleArray (*NewDoubleArray)(JNIEnv*, jsize);
2. JNI在Java和C语言之间传递int类型
//将Java中的两个int值 传给C语言, 进行相加后, 返回java语言 shuliang.han.ndkparameterpassing.DataProvider public native int add(int x, int y);
C语言中定义的方法 :
#include <jni.h> //方法签名, Java环境 和 调用native方法的类 必不可少, 后面的参数就是native方法的参数 jint Java_shuliang_han_ndkparameterpassing_DataProvider_add(JNIEnv * env, jobject obj, jint x, jint y) { return x + y; }
使用NDK工具变异该c类库 :
3. NDK中C代码使用LogCat
(1) 引入头文件
#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__)
头文件介绍 : log.h 是关于调用 LogCat日志文件;
/* * Android log priority values, in ascending priority order. 日志等级 */ typedef enum android_LogPriority { ANDROID_LOG_UNKNOWN = 0, ANDROID_LOG_DEFAULT, /* only for SetMinPriority() */ ANDROID_LOG_VERBOSE, ANDROID_LOG_DEBUG, ANDROID_LOG_INFO, ANDROID_LOG_WARN, ANDROID_LOG_ERROR, ANDROID_LOG_FATAL, ANDROID_LOG_SILENT, /* only for SetMinPriority(); must be last */ } android_LogPriority; /* * Send a simple string to the log. 向LogCat中输出日志 参数介绍: 日志优先级 , 日志标签 , 日志内容 */ int __android_log_write(int prio, const char *tag, const char *text);
C语言中输入输出函数占位符介绍 :
占位符 | 数据类型 |
%d | int |
%ld | long int |
%c | char |
%f | float |
&lf | double |
%x | 十六进制 |
%O | 八进制 |
%s | 字符串 |
(2) Android.mk增加liblog.so动态库
LOCAL_PATH := $(call my-dir) include $(CLEAR_VARS) LOCAL_MODULE := DataProvider LOCAL_SRC_FILES := DataProvider.c #增加log函数对应的函数库 liblog.so libthread_db.a LOCAL_LDLIBS += -llog -lthread_db include $(BUILD_SHARED_LIBRARY)
函数库位置 : android-ndk-r9c\platforms\android-9\arch-arm\usr\lib;
(3) 编译执行
//Java中的int对应的是C语言中的long类型, 对应JNI中的jint类型, C语言中 LOGI("JNI_日志 : x = %ld , y = %ld" , x , y);
最终的包含打印日志的完整代码 : 注意, 这里有一处可能错误, 如果是32位机器, int类型占位符使用 %d 即可;
#include <jni.h> #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__) //方法签名, Java环境 和 调用native方法的类 必不可少, 后面的参数就是native方法的参数 jint Java_shuliang_han_ndkparameterpassing_DataProvider_add(JNIEnv * env, jobject obj, jint x, jint y) { //Java中的int对应的是C语言中的long类型, 对应JNI中的jint类型, C语言中 LOGI("JNI_日志 : x = %ld , y = %ld" , x , y); return x + y; }
重新编译C文件 : 执行 /android-ndk-r9c/ndk-build命令;
4. 字符串处理
// java中的jstring, 转化为c的一个字符数组 char* Jstring2CStr(JNIEnv* env, jstring jstr) { char* rtn = NULL; 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); //new char[alen+1]; "\0" memcpy(rtn,ba,alen); rtn[alen]=0; } (*env)->ReleaseByteArrayElements(env,barr,ba,0); //释放内存 return rtn; }
C语言方法 : 注意调用Jstring2CStr方法之后要强转, 否则会出错, Jstring2CStr方法要定义在该方法的前面, C语言中的方法要先声明才能使用;
jstring Java_shuliang_han_ndkparameterpassing_DataProvider_sayHelloInc(JNIEnv *env, jobject obj, jstring str) { char *p = (char*)Jstring2CStr(env, str); //打印Java传递过来的数据 LOGI("Java JNI string parameter is : %s", p); char *append = "append"; //strcat(dest, source) 函数可以将source字符串 添加到dest字符串后面 return (*env)->NewStringUTF(env, strcat(p, append)); }
case R.id.sayHelloInc: Toast.makeText(getApplicationContext(), dataProvider.sayHelloInc("Hello"), Toast.LENGTH_LONG).show(); break;
5. 开发JNI程序流程
- C语言类库接口 : 存在C语言类库, 调用接口为login_server(char* address, char* username, char* password);
- Java定义本地方法 : public native void LoginServer(String address, String user, String pwd);
- C语言JNI代码 : Java_包名_类名_LoginServer(JNIEnv* env, jobject obj, jstring address, jstring user, jstring pwd){...调C接口};
6. 数组参数处理
jbooleanArray (*NewBooleanArray)(JNIEnv*, jsize); jbyteArray (*NewByteArray)(JNIEnv*, jsize); jcharArray (*NewCharArray)(JNIEnv*, jsize); jshortArray (*NewShortArray)(JNIEnv*, jsize); jintArray (*NewIntArray)(JNIEnv*, jsize); jlongArray (*NewLongArray)(JNIEnv*, jsize); jfloatArray (*NewFloatArray)(JNIEnv*, jsize); jdoubleArray (*NewDoubleArray)(JNIEnv*, jsize);
获取数组元素相关方法 :
jboolean* (*GetBooleanArrayElements)(JNIEnv*, jbooleanArray, jboolean*); jbyte* (*GetByteArrayElements)(JNIEnv*, jbyteArray, jboolean*); jchar* (*GetCharArrayElements)(JNIEnv*, jcharArray, jboolean*); jshort* (*GetShortArrayElements)(JNIEnv*, jshortArray, jboolean*); jint* (*GetIntArrayElements)(JNIEnv*, jintArray, jboolean*); jlong* (*GetLongArrayElements)(JNIEnv*, jlongArray, jboolean*); jfloat* (*GetFloatArrayElements)(JNIEnv*, jfloatArray, jboolean*); jdouble* (*GetDoubleArrayElements)(JNIEnv*, jdoubleArray, jboolean*);
jintArray Java_shuliang_han_ndkparameterpassing_DataProvider_intMethod(JNIEnv *env, jobject obj, jintArray arr) { //获取arr大小 int len = (*env)->GetArrayLength(env, arr); //在LogCat中打印出arr的大小 LOGI("the length of array is %d", len); //如果长度为0, 返回arr if(len == 0) return arr; //如果长度大于0, 那么获取数组中的每个元素 jint* p = (*env)->GetIntArrayElements(env, arr, 0); //打印出数组中每个元素的值 int i = 0; for(; i < len; i ++) { LOGI("arr[%d] = %d", i, *(p + i)); } return arr; }
Java语言代码 :
case R.id.intMethod: int[] array = {1, 2, 3, 4, 5}; dataProvider.intMethod(array); break;
执行结果 : 上面的那种LogCat竟然启动失败, 只能将就着用这个了;