原文网址:http://blog.csdn.net/shulianghan/article/details/18964835

NDK项目源码地址 :

-- 第一个JNI示例程序下载 : GitHub - https://github.com/han1202012/NDKHelloworld.git

-- Java传递参数给C语言实例程序 : GitHub - https://github.com/han1202012/NDKParameterPassing.git

--C语言回调Java方法示例程序 : GitHub - https://github.com/han1202012/NDK_Callback.git

--分析Log框架层JNI源码所需的Android底层文件 : CSDN - http://download.csdn.net/detail/han1202012/6905507

.

作者 :万境绝尘 

转载请注明出处 : http://blog.csdn.net/shulianghan/article/details/18964835

.

开发环境介绍 :

-- eclipse : adt-bundle-windows-x86-20130917

-- sdk : 版本 2.3.3

-- ndk : android-ndk-r9c-windows-x86.zip

-- cygwin : 所需组件 binutils , gcc , gcc-mingw , gdb , make;

-- javah : jdk6.0自带工具

-- javap : jdk6.0自带工具

JNI 总结 : 

Java 调用 C 流程 :

-- a. 定义 Native 方法 : 在 shuliang.han.ndkparameterpassing.DataProvider.java 类中定义 Native 方法 public native int add(int x, int y);

-- b. 生成方法签名 : 进入 AndroidProject/bin/classes 目录, 使用 javah shuliang.han.ndkparameterpassing.DataProvider 命令, 便生成了头文件, 该头文件引用了 jni.h, 以及定义好了 对应的 Native 方法, 生成 JNIEXPORT jint JNICALL Java_shuliang_han_ndkparameterpassing_DataProvider_add (JNIEnv *, jobject, jint, jint);

-- c. 编写 Android.mk 文件 :

  1. LOCAL_PATH := $(call my-dir)
  2. include $(CLEAR_VARS)
  3. LOCAL_MODULE    := hello-jni
  4. LOCAL_SRC_FILES := hello-jni.c
  5. include $(BUILD_SHARED_LIBRARY)

-- d. 生成 动态库 so 文件 : 进入 Android.mk 所在目录, 在该目录执行 ndk 下的 ndk-build 命令;

-- e. Java代码加载动态库 : 在 Java 代码中调用该类的类前面, 在类的一开始, 不在方法中, 加入 static{ System.loadLibrary("hello"); } ;

一. 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语言之间的障碍;

JNI中的一些概念 :

-- native : Java语言中修饰本地方法的修饰符, 被该修饰符修饰的方法没有方法体;

-- Native方法 : 在Java语言中被native关键字修饰的方法是Native方法;

-- JNI层 : Java声明Native方法的部分;

-- JNI函数 : JNIEnv提供的函数, 这些函数在jni.h中进行定义;

-- JNI方法 : Native方法对应的JNI层实现的 C/C++方法, 即在jni目录中实现的那些C语言代码;

2. Android中的应用程序框架

正常情况下的Android框架 : 最顶层Android的应用程序代码, 上层的应用层 和 应用框架层 主要是Java代码, 中间有一层的Framework框架层代码是 C/C++代码, 通过Framework进行系统调用, 调用底层的库 和linux 内核;

使用JNI时的Android框架 : 绕过Framework提供的调用底层的代码, 直接调用自己写的C代码, 该代码最终会编译成为一个库, 这个库通过JNI提供的一个Stable的ABI 调用linux kernel;ABI是二进制程序接口 application binary interface.

纽带 : JNI是连接框架层 (Framework - C/C++) 和应用框架层(Application Framework - Java)的纽带;

JNI在Android中作用 : JNI可以调用本地代码库(即C/C++代码), 并通过 Dalvik虚拟机 与应用层 和 应用框架层进行交互, Android中JNI代码主要位于应用层 和 应用框架层;

-- 应用层 : 该层是由JNI开发, 主要使用标准JNI编程模型;

-- 应用框架层 : 使用的是Android中自定义的一套JNI编程模型, 该自定义的JNI编程模型弥补了标准JNI编程模型的不足;

Android中JNI源码位置 : 在应用框架层中, 主要的JNI代码位于 framework/base目录下, 这些模块被编译成共享库之后放在 /system/lib 目录下;

NDK与JNI区别 :

-- NDK: NDK是Google开发的一套开发和编译工具集, 主要用于Android的JNI开发;

-- JNI : JNI是一套编程接口, 用来实现Java代码与本地的C/C++代码进行交互;

JNI编程步骤:

-- 声明native方法 : 在Java代码中声明 native method()方法;

-- 实现JNI的C/C++方法 : 在JNI层实现Java中声明的native方法, 这里使用javah工具生成带方法签名的头文件, 该JNI层的C/C++代码将被编译成动态库;

-- 加载动态库 : 在Java代码中的静态代码块中加载JNI编译后的动态共享库;

.

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 ;

执行结果 :

  1. <span style="font-family: 'Courier New';">Android NDK: Could not find application project directory !
  2. Android NDK: Please define the NDK_PROJECT_PATH variable to point to it.
  3. /android-ndk-r9c/build/core/build-local.mk:148: *** Android NDK: Aborting    。 停止。</span>

三. 开发第一个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示例 :

  1. LOCAL_PATH := $(call my-dir)
  2. include $(CLEAR_VARS)
  3. LOCAL_MODULE    := hello-jni
  4. LOCAL_SRC_FILES := hello-jni.c
  5. include $(BUILD_SHARED_LIBRARY)

-- LOCAL_PATH : 代表mk文件所在的目录;

-- 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工程

 
Android工程版本 : 创建一个Android工程,minSdk 为 7 即 android-2.1, 编译使用的sdk为 10 即 android-2.3.3 ;
  1. <uses-sdk
  2. android:minSdkVersion="7"
  3. android:targetSdkVersion="10" />
NDK编译原则 : 编译NDK动态库是按照最小版本进行编译, 选择编译的平台的时候, 会选择 NDK 7 平台进行编译;
 
      
 

(2) 声明native方法

 
声明native方法, 注意该方法没有方法体 和 参数, 如下 :
 
  1. /*
  2. * 声明一个native方法
  3. * 这个方法在Java中是没有实现的, 没有方法体
  4. * 该方法需要使用C语言编写
  5. */
  6. public native String helloFromJNI();

.

作者 : 万境绝尘 

转载请注明出处 : http://blog.csdn.net/shulianghan/article/details/18964835

.

(3) 创建C文件

 
引入头文件: 首先要包含头文件 jni.h, 该头文件位置定义在 android-ndk-r9c\platforms\android-5\arch-arm\usr\include目录下的 jni.h, 下面是该头文件中定义的一些方法, 包括本项目中使用的 NewString 方法;
  1. jstring     (*NewString)(JNIEnv*, const jchar*, jsize);
  2. jsize       (*GetStringLength)(JNIEnv*, jstring);
  3. const jchar* (*GetStringChars)(JNIEnv*, jstring, jboolean*);
  4. void        (*ReleaseStringChars)(JNIEnv*, jstring, const jchar*);
  5. jstring     (*NewStringUTF)(JNIEnv*, const char*);
  6. jsize       (*GetStringUTFLength)(JNIEnv*, jstring);
 
调用Java类型 : C中调用Java中的String类型为 jstring;
 
C语言方法名规则 : Java_完整包名类名_方法名(JNIEnv *env, jobject thiz), 注意完整的类名包名中包名的点要用 _ 代替;
 
参数介绍 : C语言方法中有两个重要的参数, JNIEnv *env, jobject thiz ;
-- JNIEnv参数 : 该参数代表Java环境, 通过这个环境可以调用Java中的方法;
-- jobject参数 : 该参数代表调用jni方法的类, 在这里就是MainActivity;
 
调用jni.h中的NewStringUTF方法 : 该方法的作用是在C语言中创建一个Java语言中的String类型对象, jni.h中是这样定义的 jstring (*NewStringUTF)(JNIEnv*, const char*), JNIEnv 结构体中包含了 NewStringUTF 函数指针, 通过 JNIEnv 就可以调用这个方法;
 
C语言文件源码 : 
  1. #include <jni.h>
  2. /*
  3. * 方法名称规定 : Java_完整包名类名_方法名()
  4. * JNIEnv 指针
  5. *
  6. * 参数介绍 :
  7. * env : 代表Java环境, 通过这个环境可以调用Java中的方法
  8. * thiz : 代表调用JNI方法的对象, 即MainActivity对象
  9. */
  10. jstring Java_shuliang_han_ndkhelloworld_MainActivity_helloFromJNI(JNIEnv *env, jobject thiz)
  11. {
  12. /*
  13. * 调用 android-ndk-r9c\platforms\android-8\arch-arm\usr\include 中jni.h中的方法
  14. * jni.h 中定义的方法  jstring (*NewStringUTF)(JNIEnv*, const char*);
  15. */
  16. return (*env)->NewStringUTF(env, "hello world jni");
  17. }
 
 

(4) 编写Android.mk文件

 
 
查询NDK文档 : NDK的文档在NDK工具根目录下, 点击 documentation.html 文件, 就可以在浏览器中打开NDK文档;
 
上面的开发流程中详细的介绍了Android.mk 五个参数的详细用处, 这里直接给出源码 : 
  1. LOCAL_PATH := $(call my-dir)
  2. include $(CLEAR_VARS)
  3. LOCAL_MODULE    := hello
  4. LOCAL_SRC_FILES := hello.c
  5. include $(BUILD_SHARED_LIBRARY)

(5) 编译NDK动态库

 
 
进入Cygwin相应目录 : 从Cygwin中的cygdrive 中进入windows的工程jni目录 ;

编译hello.c文件 : 注意Android.mk文件 与 hello.c 文件在同一目录中;
 
编译完成后的情况 : 编译完之后 会成成一个obj文件, 在obj文件中会生成 libhello.so, 系统会自动将该 so后缀文件放在libs目录下;
 
 
 

(6) Java中加载动态库

 
静态代码块中加载 : Java中在静态代码块中加载库文件, 调用 System.loadLibrary("hello") 方法,注意 libs中的库文件名称为 libhello.so,我们加载的时候 将 lib 去掉, 只取hello 作为动态库名称, 这是规定的;
  1. //静态代码块加载C语言库文件
  2. static{
  3. System.loadLibrary("hello");
  4. }
 
 

(7) 其它源码

 
MainActivity源码 : 
  1. package shuliang.han.ndkhelloworld;
  2. import android.app.Activity;
  3. import android.os.Bundle;
  4. import android.view.View;
  5. import android.widget.Toast;
  6. public class MainActivity extends Activity {
  7. //静态代码块加载C语言库文件
  8. static{
  9. System.loadLibrary("hello");
  10. }
  11. /*
  12. * 声明一个native方法
  13. * 这个方法在Java中是没有实现的, 没有方法体
  14. * 该方法需要使用C语言编写
  15. */
  16. public native String helloFromJNI();
  17. @Override
  18. protected void onCreate(Bundle savedInstanceState) {
  19. super.onCreate(savedInstanceState);
  20. setContentView(R.layout.activity_main);
  21. System.out.println(helloFromJNI());
  22. }
  23. public void onClick(View view) {
  24. //点击按钮显示从jni调用得到的字符串信息
  25. Toast.makeText(getApplicationContext(), helloFromJNI(), 1).show();
  26. }
  27. }

XML布局文件 : 

  1. <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
  2. xmlns:tools="http://schemas.android.com/tools"
  3. android:layout_width="match_parent"
  4. android:layout_height="match_parent"
  5. android:paddingBottom="@dimen/activity_vertical_margin"
  6. android:paddingLeft="@dimen/activity_horizontal_margin"
  7. android:paddingRight="@dimen/activity_horizontal_margin"
  8. android:paddingTop="@dimen/activity_vertical_margin"
  9. tools:context=".MainActivity" >
  10. <Button
  11. android:id="@+id/bt"
  12. android:layout_width="wrap_content"
  13. android:layout_height="wrap_content"
  14. android:onClick="onClick"
  15. android:text="显示JNI返回的字符串" />
  16. </RelativeLayout>

(8) 将源码上传到GitHub中

 
 
在上一篇博客 http://blog.csdn.net/shulianghan/article/details/18812279 中对GitHub用法进行了详解;
 
在GitHub上创建工程 : 
 
项目地址 
-- HTTP: https://github.com/han1202012/NDKHelloworld.git 
-- SSH : git@github.com:han1202012/NDKHelloworld.git
 
生成的命令 :

  1. touch README.md
  2. git init
  3. git add README.md
  4. git commit -m "first commit"
  5. git remote add origin git@github.com:han1202012/NDKHelloworld.git
  6. git push -u origin master

打开 Git Bash 命令行窗口 : 

-- 从GitHub上克隆项目到本地 : git clone git@github.com:han1202012/NDKHelloworld.git , 注意克隆的时候直接在仓库根目录即可, 不用再创建项目根目录 ;
-- 添加文件 : git add ./* , 将目录中所有文件添加;

-- 查看状态 : git status ;
-- 提交缓存 : git commit -m '提交';

-- 提交到远程GitHub仓库 : git push -u origin master ;
 
GitHub项目 : 
 
 
 
 

3. 项目讲解

 

(1) Android.mk文件讲解

 
Android.mk文件内容 : 
 
  1. LOCAL_PATH := $(call my-dir)
  2. include $(CLEAR_VARS)
  3. LOCAL_MODULE    := hello
  4. LOCAL_SRC_FILES := hello.c
  5. include $(BUILD_SHARED_LIBRARY)
 
获取当前文件内容 : $(call my-dir) 是编译器中的宏方法, 调用该宏方法, 就会返回前的目录路径
赋值符号 : " := " 是赋值符号, 第一句话 是 返回当前文件所在的当前目录, 并将这个目录路径赋值给 LOCAL_PATH;
初始化编译模块参数 : $(CLEAR_VARS) 作用是将编译模块的参数初始化, LOCAL_MODULE LOCAL_SRC_FILES 也是这样的参数;

指定编译模块 : LOCAL_MODULE    := hello , 指定编译后的 so 文件名称, 编译好之后系统会在该名称前面加上 "lib", 后缀加上 ".so";

指定编译源文件 : LOCAL_SRC_FILES := hello.c 告诉编译系统源文件, 如果有多个文件那么就依次写在后面即可; 
编译成静态库 : include $(BUILD_SHARED_LIBRARY), 作用是高速系统, 编译的结果编译成 .so 后缀的静态库;
 
静态库引入 : NDK的platform中有很多 ".a" 结尾的动态库, 我们编译动态库的时候, 可以将一些静态库引入进来;
 
 

(2) 自动生成方法签名

 
 
使用javah工具 : 在C中实现Java调用的jni方法, 方法的签名很复杂, 需要将完整的包名类名方法名都要使用 "_" 连接起来, 很麻烦, jdk提供的生成签名方法的工具;
 
遗留问题 : 目前查到的方法是 在bin目录下 执行 javah -jni 包名类名 命令, 但是执行不成功, 暂时没找到解决方案;
-- Android中会自动生成 .class文件吗, 没发现啊, PY人!
 
解决问题 : 在jni目录下存在classes目录, 但是这个目录在eclipse中不显示, 这里我们要注意;
 
在Cygwin中使用 javah 命令即可 : 
生成的头文件 : shuliang_han_ndkparameterpassing_DataProvider.h;
  1. /* DO NOT EDIT THIS FILE - it is machine generated */
  2. #include <jni.h>
  3. /* Header for class shuliang_han_ndkparameterpassing_DataProvider */
  4. #ifndef _Included_shuliang_han_ndkparameterpassing_DataProvider
  5. #define _Included_shuliang_han_ndkparameterpassing_DataProvider
  6. #ifdef __cplusplus
  7. extern "C" {
  8. #endif
  9. /*
  10. * Class:     shuliang_han_ndkparameterpassing_DataProvider
  11. * Method:    add
  12. * Signature: (II)I
  13. */
  14. JNIEXPORT jint JNICALL Java_shuliang_han_ndkparameterpassing_DataProvider_add
  15. (JNIEnv *, jobject, jint, jint);
  16. /*
  17. * Class:     shuliang_han_ndkparameterpassing_DataProvider
  18. * Method:    sayHelloInc
  19. * Signature: (Ljava/lang/String;)Ljava/lang/String;
  20. */
  21. JNIEXPORT jstring JNICALL Java_shuliang_han_ndkparameterpassing_DataProvider_sayHelloInc
  22. (JNIEnv *, jobject, jstring);
  23. /*
  24. * Class:     shuliang_han_ndkparameterpassing_DataProvider
  25. * Method:    intMethod
  26. * Signature: ([I)[I
  27. */
  28. JNIEXPORT jintArray JNICALL Java_shuliang_han_ndkparameterpassing_DataProvider_intMethod
  29. (JNIEnv *, jobject, jintArray);
  30. #ifdef __cplusplus
  31. }
  32. #endif
  33. #endif
 
.
 
 

(3) NDK开发中乱码问题

 
解决乱码思路 : C语言编译的时候用的是 ISO-8859-1 码表进行编码, 如果我们使用C语言jni开发, 需要进行转码操作;
-- 将ISO-8859-1转为UTF-8字符: String string = new String(str.getBytes("iso8859-1"), "UTF-8");
 
 
示例 : 
 
添加中文jni调用 : 将jni中的hello.c 中返回的字符串修改为中文, 重新编译 .so 静态库文件;
-- 修改后的hello.c文件如下 : 只改变了返回的字符串, 添加了中文;
  1. #include <jni.h>
  2. /*
  3. * 方法名称规定 : Java_完整包名类名_方法名()
  4. * JNIEnv 指针
  5. *
  6. * 参数介绍 :
  7. * env : 代表Java环境, 通过这个环境可以调用Java中的方法
  8. * thiz : 代表调用JNI方法的对象, 即MainActivity对象
  9. */
  10. jstring Java_shuliang_han_ndkhelloworld_MainActivity_helloFromJNI(JNIEnv *env, jobject thiz)
  11. {
  12. /*
  13. * 调用 android-ndk-r9c\platforms\android-8\arch-arm\usr\include 中jni.h中的方法
  14. * jni.h 中定义的方法  jstring (*NewStringUTF)(JNIEnv*, const char*);
  15. */
  16. return (*env)->NewStringUTF(env, "hello world jni 中文");
  17. }

使用NDK重新编译hello.c文件 : 修改了C源码之后, 重新将该c文件编译成so文件;

-- 编译过程: 打开cygwin, 进入cygdrive/ 下对应windows中源码项目中的jni目录, 执行 /android-ndk-r9c/ndk-build 命令;
 
 
运行Android代码报错 : 因为jni中c文件有中文, 中文不能被识别;
  1. 01-31 14:36:04.803: W/dalvikvm(389): JNI WARNING: illegal continuation byte 0xd0
  2. 01-31 14:36:04.803: W/dalvikvm(389):              string: 'hello world jni ????'
  3. 01-31 14:36:04.803: W/dalvikvm(389):              in Lshuliang/han/ndkhelloworld/MainActivity;.helloFromJNI ()Ljava/lang/String; (NewStringUTF)
  4. 01-31 14:36:04.834: I/dalvikvm(389): "main" prio=5 tid=1 NATIVE
  5. 01-31 14:36:04.834: I/dalvikvm(389):   | group="main" sCount=0 dsCount=0 obj=0x4001f1a8 self=0xce48
  6. 01-31 14:36:04.834: I/dalvikvm(389):   | sysTid=389 nice=0 sched=0/0 cgrp=default handle=-1345006528
  7. 01-31 14:36:04.844: I/dalvikvm(389):   | schedstat=( 257006717 305462830 51 )
  8. 01-31 14:36:04.844: I/dalvikvm(389):   at shuliang.han.ndkhelloworld.MainActivity.helloFromJNI(Native Method)
  9. 01-31 14:36:04.844: I/dalvikvm(389):   at shuliang.han.ndkhelloworld.MainActivity.onCreate(MainActivity.java:26)
  10. 01-31 14:36:04.844: I/dalvikvm(389):   at android.app.Instrumentation.callActivityOnCreate(Instrumentation.java:1047)
  11. 01-31 14:36:04.853: I/dalvikvm(389):   at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:1611)
  12. 01-31 14:36:04.853: I/dalvikvm(389):   at android.app.ActivityThread.handleLaunchActivity(ActivityThread.java:1663)
  13. 01-31 14:36:04.853: I/dalvikvm(389):   at android.app.ActivityThread.access$1500(ActivityThread.java:117)
  14. 01-31 14:36:04.864: I/dalvikvm(389):   at android.app.ActivityThread$H.handleMessage(ActivityThread.java:931)
  15. 01-31 14:36:04.864: I/dalvikvm(389):   at android.os.Handler.dispatchMessage(Handler.java:99)
  16. 01-31 14:36:04.864: I/dalvikvm(389):   at android.os.Looper.loop(Looper.java:123)
  17. 01-31 14:36:04.864: I/dalvikvm(389):   at android.app.ActivityThread.main(ActivityThread.java:3683)
  18. 01-31 14:36:04.864: I/dalvikvm(389):   at java.lang.reflect.Method.invokeNative(Native Method)
  19. 01-31 14:36:04.874: I/dalvikvm(389):   at java.lang.reflect.Method.invoke(Method.java:507)
  20. 01-31 14:36:04.874: I/dalvikvm(389):   at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:839)
  21. 01-31 14:36:04.874: I/dalvikvm(389):   at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:597)
  22. 01-31 14:36:04.874: I/dalvikvm(389):   at dalvik.system.NativeStart.main(Native Method)
  23. 01-31 14:36:04.884: E/dalvikvm(389): VM aborting
.
 
 

4. JNIEnv 详解

 
JNIEnv作用 : JNIEnv 是一个指针,指向了一组JNI函数, 这些函数可以在jni.h中查询到,通过这些函数可以实现 Java层 与 JNI层的交互 , 通过JNIEnv 调用JNI函数 可以访问java虚拟机, 操作java对象;
 
JNI线程相关性 : JNIEnv只在当前的线程有效,JNIEnv不能跨线程传递, 相同的Java线程调用本地方法, 所使用的JNIEnv是相同的, 一个Native方法不能被不同的Java线程调用;
 
JNIEnv结构体系 : JNIEnv指针指向一个线程相关的结构,线程相关结构指向一个指针数组,指针数组中的每个元素最终指向一个JNI函数.
 

(1) JNIEnv的C/C++声明

 
jni.h中声明JNIEnv : C语言中定义的JNIEnv 是 JNINativeInterface* , C++中定义的JNIEnv 是 _JNIEnv;
  1. struct _JNIEnv;
  2. struct _JavaVM;
  3. typedef const struct JNINativeInterface* C_JNIEnv;
  4. #if defined(__cplusplus)    //为了兼容C 和 C++两种代码 使用该 宏加以区分
  5. typedef _JNIEnv JNIEnv;     //C++ 中的JNIEnv类型
  6. typedef _JavaVM JavaVM;
  7. #else
  8. typedef const struct JNINativeInterface* JNIEnv;//C语言中的JNIEnv类型
  9. typedef const struct JNIInvokeInterface* JavaVM;
  10. #endif

(2) C语言中的JNIEnv

 
关于JNIEnv指针调用解析 : C中JNIEnv就是 const struct JNINativeInterface*, JNIEnv * env等价于 JNINativeInterface** env, 因此要得到JNINativeInterface结构体中定义的函数指针, 就必须先获取到 JNINativeInterface的一级指针对象 即 *env , 该一级指针对象就是 JNINativeInterface* env, 然后通过该一级指针对象调用JNI函数 : (*env)->NewStringUTF(env, "hello");
 
在JNINativeInterface结构体中定义了一系列的关于Java操作的相关方法 : 
  1. /*
  2. * Table of interface function pointers.
  3. */
  4. struct JNINativeInterface {
  5. void*       reserved0;
  6. void*       reserved1;
  7. ... ...
  8. jboolean    (*CallStaticBooleanMethodV)(JNIEnv*, jclass, jmethodID,
  9. va_list);
  10. jboolean    (*CallStaticBooleanMethodA)(JNIEnv*, jclass, jmethodID,
  11. jvalue*);
  12. jbyte       (*CallStaticByteMethod)(JNIEnv*, jclass, jmethodID, ...);
  13. jbyte       (*CallStaticByteMethodV)(JNIEnv*, jclass, jmethodID, va_list);
  14. ... ...
  15. void*       (*GetDirectBufferAddress)(JNIEnv*, jobject);
  16. jlong       (*GetDirectBufferCapacity)(JNIEnv*, jobject);
  17. /* added in JNI 1.6 */
  18. jobjectRefType (*GetObjectRefType)(JNIEnv*, jobject);
  19. };

(3) C++中的JNIEnv

 
C++ 中的JNIEnv: C++ 中的JNIEnv 就是 _JNIEnv 结构体, 二者是等同的; 因此在调用 JNI函数的时候, 只需要使用 env->NewStringUTF(env, "hello")方法即可, 不用在进行*运算;
 
.
  1. /*
  2. * C++ object wrapper.
  3. *
  4. * This is usually overlaid on a C struct whose first element is a
  5. * JNINativeInterface*.  We rely somewhat on compiler behavior.
  6. */
  7. struct _JNIEnv {
  8. /* do not rename this; it does not seem to be entirely opaque */
  9. const struct JNINativeInterface* functions;
  10. #if defined(__cplusplus)
  11. jint GetVersion()
  12. { return functions->GetVersion(this); }
  13. jlong GetDirectBufferCapacity(jobject buf)
  14. { return functions->GetDirectBufferCapacity(this, buf); }
  15. /* added in JNI 1.6 */
  16. jobjectRefType GetObjectRefType(jobject obj)
  17. { return functions->GetObjectRefType(this, obj); }
  18. #endif /*__cplusplus*/
  19. };

5. JNI方法命名规则(标准JNI规范)

 
JNI实现的方法 与 Java中Native方法的映射关系 : 使用方法名进行映射, 可以使用 javah 工具进入 bin/classes 目录下执行命令, 即可生成头文件;
 
JNI方法参数介绍
-- 参数① : 第一个参数是JNI接口指针 JNIEnv;
-- 参数② : 如果Native方法是非静态的, 那么第二个参数就是对Java对象的引用, 如果Native方法是静态的, 那么第二个参数就是对Java类的Class对象的引用;
 
JNI方法名规范 : 返回值 + Java前缀 + 全路径类名 + 方法名 + 参数① JNIEnv + 参数② jobject + 其它参数;
-- 注意分隔符 : Java前缀 与 类名 以及类名之间的包名 和 方法名之间 使用 "_" 进行分割;
 
声明 非静态 方法
-- Native方法 : public int hello (String str, int i); 
-- JNI方法: jint Java_shuliang_han_Hello_hello(JNIEnv * env, jobject obj, jstring str, jint i);
 
声明 静态 方法 : 
-- Native方法 : public static int hello (String str, int i); 
--JNI方法 : jint Java_shuliang_han_Hello_hello(JNIEnv * env, jobject clazz, jstring str, jint i);
 
两种规范 : 以上是Java的标准JNI规范, 在Android中还有一套自定义的规范, 该规范是Android应用框架层 和 框架层交互使用的JNI规范, 依靠方法注册 映射 Native方法 和 JNI方法;
 

6. JNI方法签名规则

 
JNI识别Java方法 : JNI依靠函数名 和 方法签名 识别方法, 函数名是不能唯一识别一个方法的, 因为方法可以重载, 类型签名代表了 参数 和 返回值;
-- 签名规则 : (参数1类型签名参数2类型签名参数3类型签名参数N类型签名...)返回值类型签名, 注意参数列表中没有任何间隔;
 
Java类型 与 类型签名对照表 : 注意 boolean 与 long 不是大写首字母, 分别是 Z 与 J,  类是L全限定类名, 数组是[元素类型签名;
-- 类的签名规则 :L + 全限定名 + ;三部分, 全限定类名以 / 分割;
Java类型 类型签名
boolean Z
byte B
char C
short S
int I
long J
float F
double D
L全限定类名
数组 [元素类型签名
 
eg. long function(int n, String str, int[] arr);
该方法的签名 :(ILjava/lang/String;[I)J
.
 
.
 

四. Java调用JNI法与日志打印

 
 

1. JNI数据类型

 
 
Java数据类型 C数据类型 JNI数据类型对比 : 32位 与 64位机器可能会有出入;
 
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
数据类型表示方法 : int数组类型 jintArray , boolean数组 jbooleanArray ...
 
头文件定义类型 : 这些基本的数据类型在jni.h 中都有相应的定义 : 
  1. jobject     (*CallObjectMethodA)(JNIEnv*, jobject, jmethodID, jvalue*);
  2. jboolean    (*CallBooleanMethod)(JNIEnv*, jobject, jmethodID, ...);
  3. jboolean    (*CallBooleanMethodV)(JNIEnv*, jobject, jmethodID, va_list);
  4. jboolean    (*CallBooleanMethodA)(JNIEnv*, jobject, jmethodID, jvalue*);
  5. jbyte       (*CallByteMethod)(JNIEnv*, jobject, jmethodID, ...);
  6. jbyte       (*CallByteMethodV)(JNIEnv*, jobject, jmethodID, va_list);
  7. jbyte       (*CallByteMethodA)(JNIEnv*, jobject, jmethodID, jvalue*);
  8. jchar       (*CallCharMethod)(JNIEnv*, jobject, jmethodID, ...);
  9. jchar       (*CallCharMethodV)(JNIEnv*, jobject, jmethodID, va_list);
  10. jchar       (*CallCharMethodA)(JNIEnv*, jobject, jmethodID, jvalue*);
  11. jshort      (*CallShortMethod)(JNIEnv*, jobject, jmethodID, ...);
  12. jshort      (*CallShortMethodV)(JNIEnv*, jobject, jmethodID, va_list);
  13. jshort      (*CallShortMethodA)(JNIEnv*, jobject, jmethodID, jvalue*);
  14. jint        (*CallIntMethod)(JNIEnv*, jobject, jmethodID, ...);
  15. jint        (*CallIntMethodV)(JNIEnv*, jobject, jmethodID, va_list);
  16. jint        (*CallIntMethodA)(JNIEnv*, jobject, jmethodID, jvalue*);
  17. jlong       (*CallLongMethod)(JNIEnv*, jobject, jmethodID, ...);
  18. jlong       (*CallLongMethodV)(JNIEnv*, jobject, jmethodID, va_list);
  19. jbooleanArray (*NewBooleanArray)(JNIEnv*, jsize);
  20. jbyteArray    (*NewByteArray)(JNIEnv*, jsize);
  21. jcharArray    (*NewCharArray)(JNIEnv*, jsize);
  22. jshortArray   (*NewShortArray)(JNIEnv*, jsize);
  23. jintArray     (*NewIntArray)(JNIEnv*, jsize);
  24. jlongArray    (*NewLongArray)(JNIEnv*, jsize);
  25. jfloatArray   (*NewFloatArray)(JNIEnv*, jsize);
  26. jdoubleArray  (*NewDoubleArray)(JNIEnv*, jsize);

2. JNI在Java和C语言之间传递int类型

 
 
Java中定义的方法 : 
  1. //将Java中的两个int值 传给C语言, 进行相加后, 返回java语言 shuliang.han.ndkparameterpassing.DataProvider
  2. public native int add(int x, int y);

C语言中定义的方法 : 

  1. #include <jni.h>
  2. //方法签名, Java环境 和 调用native方法的类 必不可少, 后面的参数就是native方法的参数
  3. jint Java_shuliang_han_ndkparameterpassing_DataProvider_add(JNIEnv * env, jobject obj, jint x, jint y)
  4. {
  5. return x + y;
  6. }

使用NDK工具变异该c类库 : 

在cygwin中进入cygdrive, 然后进入windows中相应的目录, 执行 /android-ndk-r9c/ndk-build 命令, 即可完成编译;
 
 

3. NDK中C代码使用LogCat

 
 

(1) 引入头文件

 
NDK中断点调试 : 断点调试在NDK中实现极其困难, 因此在这里我们一般都是打印日志;
 
引入头文件 : 在C代码中引入下面的头文件;
  1. #include <android/log.h>
  2. #define LOG_TAG "System.out"
  3. #define LOGD(...) __android_log_print(ANDROID_LOG_DEBUG, LOG_TAG, __VA_ARGS__)
  4. #define LOGI(...) __android_log_print(ANDROID_LOG_INFO, LOG_TAG, __VA_ARGS__)

头文件介绍 : log.h 是关于调用 LogCat日志文件;

-- log.h头文件路径 : android-ndk-r9c\platforms\android-9\arch-arm\usr\include\android\log.h;
-- 主要方法 :  __android_log_write, 下面有该方法的解析, 传入参数 日志等级 日志标签 日志内容;
-- 宏定义 : __android_log_write 方法太麻烦, 这里做出一个映射, LOGD(...) 输出debug级别的日志, LOGI(...) 输出Info级别的日志;
--LogCat日志级别 : verbose < debug < info < warn < error < assert;
 
使用到的log.h文件内容解析 : __android_log_write 方法中的日志等级参数就使用 枚举中的内容 
  1. /*
  2. * Android log priority values, in ascending priority order. 日志等级
  3. */
  4. typedef enum android_LogPriority {
  5. ANDROID_LOG_UNKNOWN = 0,
  6. ANDROID_LOG_DEFAULT,    /* only for SetMinPriority() */
  7. ANDROID_LOG_VERBOSE,
  8. ANDROID_LOG_DEBUG,
  9. ANDROID_LOG_INFO,
  10. ANDROID_LOG_WARN,
  11. ANDROID_LOG_ERROR,
  12. ANDROID_LOG_FATAL,
  13. ANDROID_LOG_SILENT,     /* only for SetMinPriority(); must be last */
  14. } android_LogPriority;
  15. /*
  16. * Send a simple string to the log. 向LogCat中输出日志
  17. 参数介绍: 日志优先级 , 日志标签 , 日志内容
  18. */
  19. 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动态库

 
在该make配置文件中, 增加一行 : LOCAL_LDLIBS += -llog , 该语句添加在 LOCAL_SRC_FILES 语句下面一行;
 
完整的Android.mk文件 : 
  1. LOCAL_PATH := $(call my-dir)
  2. include $(CLEAR_VARS)
  3. LOCAL_MODULE    := DataProvider
  4. LOCAL_SRC_FILES := DataProvider.c
  5. #增加log函数对应的函数库 liblog.so  libthread_db.a
  6. LOCAL_LDLIBS += -llog -lthread_db
  7. include $(BUILD_SHARED_LIBRARY)
函数库位置 : android-ndk-r9c\platforms\android-9\arch-arm\usr\lib;
函数库截图 : 从该目录下的 liglog.so可以看出, 存在该库;
引入函数库方法 : 使用 LOCAL_LDLIBS += -l函数库名, 注意函数库名不带lib前缀 和.so 后缀, 同时可以添加多个库, 使用 -l库1 -l库2 -库3 ;
 
 

(3) 编译执行

 
根据(1) 中的占位符, 编写打印日志代码
  1. //Java中的int对应的是C语言中的long类型, 对应JNI中的jint类型, C语言中
  2. LOGI("JNI_日志 : x = %ld , y = %ld" , x , y);

最终的包含打印日志的完整代码 : 注意, 这里有一处可能错误, 如果是32位机器, int类型占位符使用 %d 即可;

  1. #include <jni.h>
  2. #include <android/log.h>
  3. #define LOG_TAG "System.out"
  4. #define LOGD(...) __android_log_print(ANDROID_LOG_DEBUG, LOG_TAG, __VA_ARGS__)
  5. #define LOGI(...) __android_log_print(ANDROID_LOG_INFO, LOG_TAG, __VA_ARGS__)
  6. //方法签名, Java环境 和 调用native方法的类 必不可少, 后面的参数就是native方法的参数
  7. jint Java_shuliang_han_ndkparameterpassing_DataProvider_add(JNIEnv * env, jobject obj, jint x, jint y)
  8. {
  9. //Java中的int对应的是C语言中的long类型, 对应JNI中的jint类型, C语言中
  10. LOGI("JNI_日志 : x = %ld , y = %ld" , x , y);
  11. return x + y;
  12. }

重新编译C文件 : 执行 /android-ndk-r9c/ndk-build命令;

-- 第一次编译 : 出现警告, long int占位符行不通, 注意区分机器位长, 64位 与 32位不同, 这样编译出现的结果就不会打印日志;
-- 第二次编译 : 将占位符改为 %d ;
 
执行按钮之后打印的日志 : 虽然有乱码, 不过显示出来了;
 
 
 

4. 字符串处理

.
 
Java中的String转为C语言中的char字符串 : 下面的工具方法可以在C程序中解决这个问题;
  1. // java中的jstring, 转化为c的一个字符数组
  2. char* Jstring2CStr(JNIEnv* env, jstring jstr) {
  3. <span style="white-space:pre">  </span>//声明了一个字符串变量 rtn
  4. <span style="white-space:pre">  </span>char* rtn = NULL;
  5. <span style="white-space:pre">  </span>//找到Java中的String的Class对象
  6. <span style="white-space:pre">  </span>jclass clsstring = (*env)->FindClass(env, "java/lang/String");
  7. <span style="white-space:pre">  </span>//创建一个Java中的字符串 "GB2312"
  8. <span style="white-space:pre">  </span>jstring strencode = (*env)->NewStringUTF(env, "GB2312");
  9. <span style="white-space:pre">  </span>/*
  10. <span style="white-space:pre">  </span> * 获取String中定义的方法 getBytes(), 该方法的参数是 String类型的, 返回值是 byte[]数组
  11. <span style="white-space:pre">  </span> * "(Ljava/lang/String;)[B" 方法前面解析 :
  12. <span style="white-space:pre">  </span> * -- Ljava/lang/String; 表示参数是String字符串
  13. <span style="white-space:pre">  </span> * -- [B : 中括号表示这是一个数组, B代表byte类型, 返回值是一个byte数组
  14. <span style="white-space:pre">  </span> */
  15. <span style="white-space:pre">  </span>jmethodID mid = (*env)->GetMethodID(env, clsstring, "getBytes",
  16. <span style="white-space:pre">          </span>"(Ljava/lang/String;)[B");
  17. <span style="white-space:pre">  </span>//调用Java中的getBytes方法, 传入参数介绍 参数②表示调用该方法的对象, 参数③表示方法id , 参数④表示方法参数
  18. <span style="white-space:pre">  </span>jbyteArray barr = (jbyteArray)(*env)->CallObjectMethod(env, jstr, mid,
  19. <span style="white-space:pre">          </span>strencode); // String .getByte("GB2312");
  20. <span style="white-space:pre">  </span>//获取数组的长度
  21. <span style="white-space:pre">  </span>jsize alen = (*env)->GetArrayLength(env, barr);
  22. <span style="white-space:pre">  </span>//获取数组中的所有的元素 , 存放在 jbyte*数组中
  23. <span style="white-space:pre">  </span>jbyte* ba = (*env)->GetByteArrayElements(env, barr, JNI_FALSE);
  24. <span style="white-space:pre">  </span>//将Java数组中所有元素拷贝到C的char*数组中, 注意C语言数组结尾要加一个 '\0'
  25. <span style="white-space:pre">  </span>if (alen > 0) {
  26. <span style="white-space:pre">      </span>rtn = (char*) malloc(alen + 1); //new   char[alen+1]; "\0"
  27. <span style="white-space:pre">      </span>memcpy(rtn, ba, alen);
  28. <span style="white-space:pre">      </span>rtn[alen] = 0;
  29. <span style="white-space:pre">  </span>}
  30. <span style="white-space:pre">  </span>(*env)->ReleaseByteArrayElements(env, barr, ba, 0); //释放内存
  31. <span style="white-space:pre">  </span>return rtn;
  32. }
 
Jstring2CStr方法讲解 : 
a. 获取Java中String类型的class对象 : 参数 : 上下文环境 env, String类完整路径 ;
  1. jclass clsstring = (*env)->FindClass(env, "java/lang/String");

b.创建Java字符串 : 使用 NewStringUTF 方法;

  1. jstring strencode = (*env)->NewStringUTF(env, "GB2312");

c.获取String中的getBytes()方法 : 参数介绍 ① env 上下文环境 ② 完整的类路径 ③ 方法名 ④ 方法签名, 方法签名 Ljava/lang/String; 代表参数是String字符串, [B  中括号表示这是一个数组, B代表byte类型, 返回值是一个byte数组;

  1. jmethodID mid = (*env)->GetMethodID(env, clsstring, "getBytes",
  2. "(Ljava/lang/String;)[B");

d. 获取数组的长度 : 

  1. jsize alen = (*env)->GetArrayLength(env, barr);

e. 获取数组元素 : 获取数组中的所有的元素 , 存放在 jbyte*数组中;

  1. jbyte* ba = (*env)->GetByteArrayElements(env, barr, JNI_FALSE);

f.数组拷贝: 将Java数组中所有元素拷贝到C的char*数组中, 注意C语言数组结尾要加一个 '\0';

  1. if (alen > 0) {
  2. rtn = (char*) malloc(alen + 1); //new   char[alen+1]; "\0"
  3. memcpy(rtn, ba, alen);
  4. rtn[alen] = 0;
  5. }

g.释放内存 : 

  1. (*env)->ReleaseByteArrayElements(env, barr, ba, 0); //释放内存
 

.

作者 : 万境绝尘 

转载请注明出处 : http://blog.csdn.net/shulianghan/article/details/18964835

.

C语言方法 : 注意调用Jstring2CStr方法之后要强转, 否则会出错, Jstring2CStr方法要定义在该方法的前面, C语言中的方法要先声明才能使用;

  1. jstring Java_shuliang_han_ndkparameterpassing_DataProvider_sayHelloInc(JNIEnv *env, jobject obj, jstring str)
  2. {
  3. char *p = (char*)Jstring2CStr(env, str);
  4. //打印Java传递过来的数据
  5. LOGI("Java JNI string parameter is : %s", p);
  6. char *append = "append";
  7. //strcat(dest, source) 函数可以将source字符串 添加到dest字符串后面
  8. return (*env)->NewStringUTF(env, strcat(p, append));
  9. }

-- 如果没有强转会出现下面的错误 : char *p = Jstring2CStr(env, str);

-- 将Jstring2CStr方法定义在主方法下面会出现下面错误 : 

 
Java源码 : 
  1. case R.id.sayHelloInc:
  2. Toast.makeText(getApplicationContext(), dataProvider.sayHelloInc("Hello"), Toast.LENGTH_LONG).show();
  3. break;

编译之后运行结果 : 

 
 
 

5. 开发JNI程序流程

 
a. C语言类库接口 : 存在C语言类库, 调用接口为login_server(char* address, char* username, char* password);
b. Java定义本地方法 : public native void LoginServer(String address, String user, String pwd);
c. C语言JNI代码 : Java_包名_类名_LoginServer(JNIEnv* env, jobject obj, jstring address, jstring user, jstring pwd){...调C接口};

注意跨语言字符串转换: JNI方法中, 要将Java的String字符串转为C中的char*字符串;

首先验证C码农提供的代码是否可用 : 验证该api是否可用, 在一个 int main() 函数中进行测试, 根据该测试代码查看方法执行相关的情况;

 

6. 数组参数处理

 
模块讲解 : 在该模块中, Java语言传递一个int数组参数给C语言, C语言将这一组参数读取出来, 并且输出到Android的LogCat中, 这里涉及到了两个重要的JNI方法, 一个数获取数组长度方法, 一个是获取数组中每个元素的方法;

获取数组长度方法 : jni中定义 - jsize (*GetArrayLength)(JNIEnv*, jarray);
创建数组相关方法 : 

  1. jbooleanArray (*NewBooleanArray)(JNIEnv*, jsize);
  2. jbyteArray    (*NewByteArray)(JNIEnv*, jsize);
  3. jcharArray    (*NewCharArray)(JNIEnv*, jsize);
  4. jshortArray   (*NewShortArray)(JNIEnv*, jsize);
  5. jintArray     (*NewIntArray)(JNIEnv*, jsize);
  6. jlongArray    (*NewLongArray)(JNIEnv*, jsize);
  7. jfloatArray   (*NewFloatArray)(JNIEnv*, jsize);
  8. jdoubleArray  (*NewDoubleArray)(JNIEnv*, jsize);

获取数组元素相关方法 : 

  1. jboolean*   (*GetBooleanArrayElements)(JNIEnv*, jbooleanArray, jboolean*);
  2. jbyte*      (*GetByteArrayElements)(JNIEnv*, jbyteArray, jboolean*);
  3. jchar*      (*GetCharArrayElements)(JNIEnv*, jcharArray, jboolean*);
  4. jshort*     (*GetShortArrayElements)(JNIEnv*, jshortArray, jboolean*);
  5. jint*       (*GetIntArrayElements)(JNIEnv*, jintArray, jboolean*);
  6. jlong*      (*GetLongArrayElements)(JNIEnv*, jlongArray, jboolean*);
  7. jfloat*     (*GetFloatArrayElements)(JNIEnv*, jfloatArray, jboolean*);
  8. jdouble*    (*GetDoubleArrayElements)(JNIEnv*, jdoubleArray, jboolean*);

C语言代码 : 

  1. jintArray Java_shuliang_han_ndkparameterpassing_DataProvider_intMethod(JNIEnv *env, jobject obj, jintArray arr)
  2. {
  3. //获取arr大小
  4. int len = (*env)->GetArrayLength(env, arr);
  5. //在LogCat中打印出arr的大小
  6. LOGI("the length of array is %d", len);
  7. //如果长度为0, 返回arr
  8. if(len == 0)
  9. return arr;
  10. //如果长度大于0, 那么获取数组中的每个元素
  11. jint* p = (*env)->GetIntArrayElements(env, arr, 0);
  12. //打印出数组中每个元素的值
  13. int i = 0;
  14. for(; i < len; i ++)
  15. {
  16. LOGI("arr[%d] = %d", i, *(p + i));
  17. }
  18. return arr;
  19. }
Java代码 : 
  1. case R.id.intMethod:
  2. int[] array = {1, 2, 3, 4, 5};
  3. dataProvider.intMethod(array);
  4. break;

执行结果 : 上面的那种LogCat竟然启动失败, 只能将就着用这个了;

 

7. 本程序源码

 
XML布局文件 : 
  1. <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
  2. xmlns:tools="http://schemas.android.com/tools"
  3. android:orientation="vertical"
  4. android:layout_width="match_parent"
  5. android:layout_height="match_parent" >
  6. <Button
  7. android:id="@+id/add"
  8. android:layout_width="wrap_content"
  9. android:layout_height="wrap_content"
  10. android:text="调用 add 本地 方法"
  11. android:onClick="onClick"/>
  12. <Button
  13. android:id="@+id/sayHelloInc"
  14. android:layout_width="wrap_content"
  15. android:layout_height="wrap_content"
  16. android:text="调用 sayHelloInc 本地 方法"
  17. android:onClick="onClick"/>
  18. <Button
  19. android:id="@+id/intMethod"
  20. android:layout_width="wrap_content"
  21. android:layout_height="wrap_content"
  22. android:text="调用 intMethod 本地 方法"
  23. android:onClick="onClick"/>
  24. </LinearLayout>

Java源码 : 

-- MainActivity源码 : 
  1. package shuliang.han.ndkparameterpassing;
  2. import android.app.Activity;
  3. import android.os.Bundle;
  4. import android.view.View;
  5. import android.widget.Toast;
  6. public class MainActivity extends Activity {
  7. static{
  8. System.loadLibrary("DataProvider");
  9. }
  10. DataProvider dataProvider;
  11. @Override
  12. public void onCreate(Bundle savedInstanceState) {
  13. super.onCreate(savedInstanceState);
  14. setContentView(R.layout.activity_main);
  15. dataProvider = new DataProvider();
  16. }
  17. public void onClick(View view) {
  18. int id = view.getId();
  19. switch (id) {
  20. case R.id.add:
  21. int result = dataProvider.add(1, 2);
  22. Toast.makeText(getApplicationContext(), "the add result : " + result, Toast.LENGTH_LONG).show();
  23. break;
  24. case R.id.sayHelloInc:
  25. Toast.makeText(getApplicationContext(), dataProvider.sayHelloInc("Hello"), Toast.LENGTH_LONG).show();
  26. break;
  27. case R.id.intMethod:
  28. int[] array = {1, 2, 3, 4, 5};
  29. dataProvider.intMethod(array);
  30. break;
  31. default:
  32. break;
  33. }
  34. }
  35. }

--DataProvider源码 : 

  1. package shuliang.han.ndkparameterpassing;
  2. public class DataProvider {
  3. //将Java中的两个int值 传给C语言, 进行相加后, 返回java语言 shuliang.han.ndkparameterpassing.DataProvider
  4. public native int add(int x, int y);
  5. //将Java字符串传递给C语言, C语言处理字符串之后, 将处理结果返回给java
  6. public native String sayHelloInc(String s);
  7. //将java中的int数组传递给C语言, C语言为每个元素加10, 返回给Java
  8. public native int[] intMethod(int[] nums);
  9. }

JNI相关源码 : 

-- Android.mk源码 : 
  1. LOCAL_PATH := $(call my-dir)
  2. include $(CLEAR_VARS)
  3. LOCAL_MODULE    := DataProvider
  4. LOCAL_SRC_FILES := DataProvider.c
  5. #增加log函数对应的log库
  6. LOCAL_LDLIBS += -llog
  7. include $(BUILD_SHARED_LIBRARY)

--DataProvider.c 主程序源码 : 

  1. #include <jni.h>
  2. #include <string.h>
  3. #include <android/log.h>
  4. #define LOG_TAG "System.out"
  5. #define LOGD(...) __android_log_print(ANDROID_LOG_DEBUG, LOG_TAG, __VA_ARGS__)
  6. #define LOGI(...) __android_log_print(ANDROID_LOG_INFO, LOG_TAG, __VA_ARGS__)
  7. // java中的jstring, 转化为c的一个字符数组
  8. char* Jstring2CStr(JNIEnv* env, jstring jstr) {
  9. <span style="white-space:pre">  </span>//声明了一个字符串变量 rtn
  10. <span style="white-space:pre">  </span>char* rtn = NULL;
  11. <span style="white-space:pre">  </span>//找到Java中的String的Class对象
  12. <span style="white-space:pre">  </span>jclass clsstring = (*env)->FindClass(env, "java/lang/String");
  13. <span style="white-space:pre">  </span>//创建一个Java中的字符串 "GB2312"
  14. <span style="white-space:pre">  </span>jstring strencode = (*env)->NewStringUTF(env, "GB2312");
  15. <span style="white-space:pre">  </span>/*
  16. <span style="white-space:pre">  </span> * 获取String中定义的方法 getBytes(), 该方法的参数是 String类型的, 返回值是 byte[]数组
  17. <span style="white-space:pre">  </span> * "(Ljava/lang/String;)[B" 方法前面解析 :
  18. <span style="white-space:pre">  </span> * -- Ljava/lang/String; 表示参数是String字符串
  19. <span style="white-space:pre">  </span> * -- [B : 中括号表示这是一个数组, B代表byte类型, 返回值是一个byte数组
  20. <span style="white-space:pre">  </span> */
  21. <span style="white-space:pre">  </span>jmethodID mid = (*env)->GetMethodID(env, clsstring, "getBytes",
  22. <span style="white-space:pre">          </span>"(Ljava/lang/String;)[B");
  23. <span style="white-space:pre">  </span>//调用Java中的getBytes方法, 传入参数介绍 参数②表示调用该方法的对象, 参数③表示方法id , 参数④表示方法参数
  24. <span style="white-space:pre">  </span>jbyteArray barr = (jbyteArray)(*env)->CallObjectMethod(env, jstr, mid,
  25. <span style="white-space:pre">          </span>strencode); // String .getByte("GB2312");
  26. <span style="white-space:pre">  </span>//获取数组的长度
  27. <span style="white-space:pre">  </span>jsize alen = (*env)->GetArrayLength(env, barr);
  28. <span style="white-space:pre">  </span>//获取数组中的所有的元素 , 存放在 jbyte*数组中
  29. <span style="white-space:pre">  </span>jbyte* ba = (*env)->GetByteArrayElements(env, barr, JNI_FALSE);
  30. <span style="white-space:pre">  </span>//将Java数组中所有元素拷贝到C的char*数组中, 注意C语言数组结尾要加一个 '\0'
  31. <span style="white-space:pre">  </span>if (alen > 0) {
  32. <span style="white-space:pre">      </span>rtn = (char*) malloc(alen + 1); //new   char[alen+1]; "\0"
  33. <span style="white-space:pre">      </span>memcpy(rtn, ba, alen);
  34. <span style="white-space:pre">      </span>rtn[alen] = 0;
  35. <span style="white-space:pre">  </span>}
  36. <span style="white-space:pre">  </span>(*env)->ReleaseByteArrayElements(env, barr, ba, 0); //释放内存
  37. <span style="white-space:pre">  </span>return rtn;
  38. }
  39. //方法签名, Java环境 和 调用native方法的类 必不可少, 后面的参数就是native方法的参数
  40. jint Java_shuliang_han_ndkparameterpassing_DataProvider_add(JNIEnv * env, jobject obj, jint x, jint y)
  41. {
  42. //Java中的int对应的是C语言中的long类型, 对应JNI中的jint类型, C语言中
  43. LOGI("JNI_log : x = %d , y = %d" , x , y);
  44. return x + y;
  45. }
  46. jstring Java_shuliang_han_ndkparameterpassing_DataProvider_sayHelloInc(JNIEnv *env, jobject obj, jstring str)
  47. {
  48. char *p = (char*)Jstring2CStr(env, str);
  49. //打印Java传递过来的数据
  50. LOGI("Java JNI string parameter is : %s", p);
  51. char *append = "append";
  52. //strcat(dest, source) 函数可以将source字符串 添加到dest字符串后面
  53. return (*env)->NewStringUTF(env, strcat(p, append));
  54. }
  55. jintArray Java_shuliang_han_ndkparameterpassing_DataProvider_intMethod(JNIEnv *env, jobject obj, jintArray arr)
  56. {
  57. //获取arr大小
  58. int len = (*env)->GetArrayLength(env, arr);
  59. //在LogCat中打印出arr的大小
  60. LOGI("the length of array is %d", len);
  61. //如果长度为0, 返回arr
  62. if(len == 0)
  63. return arr;
  64. //如果长度大于0, 那么获取数组中的每个元素
  65. jint* p = (*env)->GetIntArrayElements(env, arr, 0);
  66. //打印出数组中每个元素的值
  67. int i = 0;
  68. for(; i < len; i ++)
  69. {
  70. LOGI("arr[%d] = %d", i, *(p + i));
  71. }
  72. return arr;
  73. }
.
 
 
 

8. 上传代码到GitHub

创建新项目 : han1202012/NDKParameterPassing ;
-- SSH地址 : git@github.com:han1202012/NDKParameterPassing.git ;
-- HTTP地址 : https://github.com/han1202012/NDKParameterPassing.git ;

 
 

五. C语言代码回调Java方法

.
 
C语言回调Java方法场景 : 
-- 复用方法 : 使用Java对象, 复用Java中的方法;
-- 激活Java : C程序后台运行, 该后台程序一直运行, 某个时间出发后需要启动Java服务, 激活Android中的某个界面, 例如使用Intent启动一个Activity;
 
 

1. C代码回调Java方法的流程

 

(1) 找到java对应的Class

 
创建一个char*数组, 然后使用jni.h中提供的FindClass方法获取jclass返回值;
  1. //DataProvider完整类名 shulaing.han.ndk_callback.DataProvider
  2. char* classname = "shulaing/han/ndk_callback/DataProvider";
  3. jclass dpclazz = (*env)->FindClass(env, classname);

(2) 找到要调用的方法的methodID

 
使用jni.h中提供的GetMethodID方法, 获取jmethodID, 传入参数 ①JNIEnv指针 ②Class对象 ③ 方法名 ④方法签名, 在这里方法名和方法签名确定一个方法, 方法签名就是方法的返回值 与 参数的唯一标示;

  1. //参数介绍 : 第二个参数是Class对象, 第三个参数是方法名,第四个参数是方法的签名, 获取到调用的method
  2. jmethodID methodID = (*env)->GetMethodID(env, dpclazz, "Add", "(II)I");
 
找到静态方法 : 如果方法是静态的, 就使用GetStaticMethod方法获取 
  1. jmethodID GetStaticMethodID(jclass clazz, const char* name, const char* sig)
  2. { return functions->GetStaticMethodID(this, clazz, name, sig); }

(3) 在C语言中调用相应方法

 
普通方法 : CallTypeMethod , 其中的Type随着返回值类型的不同而改变;
参数介绍 : ① JNIEnv指针 ②调用该native方法的对象 ③方法的methodID ④⑤... 后面是可变参数, 这些参数是
  1. jmethodID   (*GetMethodID)(JNIEnv*, jclass, const char*, const char*);
  2. jobject     (*CallObjectMethod)(JNIEnv*, jobject, jmethodID, ...);
  3. jobject     (*CallObjectMethodV)(JNIEnv*, jobject, jmethodID, va_list);
  4. jobject     (*CallObjectMethodA)(JNIEnv*, jobject, jmethodID, jvalue*);
  5. jboolean    (*CallBooleanMethod)(JNIEnv*, jobject, jmethodID, ...);
  6. jboolean    (*CallBooleanMethodV)(JNIEnv*, jobject, jmethodID, va_list);
  7. jboolean    (*CallBooleanMethodA)(JNIEnv*, jobject, jmethodID, jvalue*);
  8. jbyte       (*CallByteMethod)(JNIEnv*, jobject, jmethodID, ...);
  9. jbyte       (*CallByteMethodV)(JNIEnv*, jobject, jmethodID, va_list);
  10. jbyte       (*CallByteMethodA)(JNIEnv*, jobject, jmethodID, jvalue*);
  11. jchar       (*CallCharMethod)(JNIEnv*, jobject, jmethodID, ...);
  12. jchar       (*CallCharMethodV)(JNIEnv*, jobject, jmethodID, va_list);
  13. jchar       (*CallCharMethodA)(JNIEnv*, jobject, jmethodID, jvalue*);
  14. jshort      (*CallShortMethod)(JNIEnv*, jobject, jmethodID, ...);
  15. jshort      (*CallShortMethodV)(JNIEnv*, jobject, jmethodID, va_list);
  16. jshort      (*CallShortMethodA)(JNIEnv*, jobject, jmethodID, jvalue*);
  17. jint        (*CallIntMethod)(JNIEnv*, jobject, jmethodID, ...);
  18. jint        (*CallIntMethodV)(JNIEnv*, jobject, jmethodID, va_list);
  19. jint        (*CallIntMethodA)(JNIEnv*, jobject, jmethodID, jvalue*);
  20. jlong       (*CallLongMethod)(JNIEnv*, jobject, jmethodID, ...);
  21. jlong       (*CallLongMethodV)(JNIEnv*, jobject, jmethodID, va_list);
  22. jlong       (*CallLongMethodA)(JNIEnv*, jobject, jmethodID, jvalue*);
  23. jfloat      (*CallFloatMethod)(JNIEnv*, jobject, jmethodID, ...) __NDK_FPABI__;
  24. jfloat      (*CallFloatMethodV)(JNIEnv*, jobject, jmethodID, va_list) __NDK_FPABI__;
  25. jfloat      (*CallFloatMethodA)(JNIEnv*, jobject, jmethodID, jvalue*) __NDK_FPABI__;
  26. jdouble     (*CallDoubleMethod)(JNIEnv*, jobject, jmethodID, ...) __NDK_FPABI__;
  27. jdouble     (*CallDoubleMethodV)(JNIEnv*, jobject, jmethodID, va_list) __NDK_FPABI__;
  28. jdouble     (*CallDoubleMethodA)(JNIEnv*, jobject, jmethodID, jvalue*) __NDK_FPABI__;
  29. void        (*CallVoidMethod)(JNIEnv*, jobject, jmethodID, ...);
  30. void        (*CallVoidMethodV)(JNIEnv*, jobject, jmethodID, va_list);
  31. void        (*CallVoidMethodA)(JNIEnv*, jobject, jmethodID, jvalue*);

静态方法 : CallStaticTypeMethod, 其中的Type随着返回值类型不同而改变;

 
 
 
.
 

2. 一些基本代码编写

 
Java代码 : 定义一个callCcode本地方法, 以及三个Java方法, 在jni中使用本地方法调用Java中的方法;
  1. package shulaing.han.ndk_callback;
  2. public class DataProvider {
  3. public native void callCcode();
  4. //C调用java中空方法 shulaing.han.ndk_callback.DataProvider
  5. public void helloFromJava(){
  6. System.out.println("hello from java");
  7. }
  8. //C调用java中的带两个int参数的方法
  9. public int Add(int x,int y){
  10. return x + y;
  11. }
  12. //C调用java中参数为string的方法
  13. public void printString(String s){
  14. System.out.println(s);
  15. }
  16. }

生成头文件 : 进入 bin/classed目录, 使用 javah shulaing.han.ndk_callback.DataProvider 命令, 可以在bin/classes下生成头文件;

 
头文件内容 : 文件名 : shulaing_han_ndk_callback_DataProvider.h ;
  1. /* DO NOT EDIT THIS FILE - it is machine generated */
  2. #include <jni.h>
  3. /* Header for class shulaing_han_ndk_callback_DataProvider */
  4. #ifndef _Included_shulaing_han_ndk_callback_DataProvider
  5. #define _Included_shulaing_han_ndk_callback_DataProvider
  6. #ifdef __cplusplus
  7. extern "C" {
  8. #endif
  9. /*
  10. * Class:     shulaing_han_ndk_callback_DataProvider
  11. * Method:    callCcode
  12. * Signature: ()V
  13. */
  14. JNIEXPORT void JNICALL Java_shulaing_han_ndk_1callback_DataProvider_callCcode
  15. (JNIEnv *, jobject);
  16. #ifdef __cplusplus
  17. }
  18. #endif
  19. #endif

编写Android.mk文件 : 注意将LogCat日志输出系统动态库加入;

  1. LOCAL_PATH := $(call my-dir)
  2. include $(CLEAR_VARS)
  3. LOCAL_MODULE    := jni
  4. LOCAL_SRC_FILES := jni.c
  5. #增加log函数对应的log库
  6. LOCAL_LDLIBS += -llog
  7. include $(BUILD_SHARED_LIBRARY)

编写jni的C代码 : 注意加入LogCat相关导入的包;

  1. #include "shulaing_han_ndk_callback_DataProvider.h"
  2. #include <string.h>
  3. #include <android/log.h>
  4. #define LOG_TAG "System.out"
  5. #define LOGD(...) __android_log_print(ANDROID_LOG_DEBUG, LOG_TAG, __VA_ARGS__)
  6. #define LOGI(...) __android_log_print(ANDROID_LOG_INFO, LOG_TAG, __VA_ARGS__)

3. C中回调Java的void返回值方法

 
使用JNIEnv指针获取Class对象 : 在jni.h文件中找到 - jclass (*FindClass)(JNIEnv*, const char*);
-- 参数介绍 : 第二个参数是类的路径字符串, 如 "/shuliang/han/ndk_callback/DataProvider" ;
 
获取Java类中定义的method方法 : 在jni.h中找到方法 - jmethodID (*GetMethodID)(JNIEnv*, jclass, const char*, const char*);
-- 参数介绍 : 第二个参数是 Java类的Class对象, 第三个参数是方法名, 第四个参数是Java方法的签名;
 
方法签名生成工具 : javap , 使用javap -s 命令即可生成方法签名;
 
进入bin/classed目录下 : 执行 javap -s shulaing.han.ndk_callback.DataProvider 命令, 即可显示出每个方法的签名;
  1. $ javap -s shulaing.han.ndk_callback.DataProvider
  2. Compiled from "DataProvider.java"
  3. public class shulaing.han.ndk_callback.DataProvider extends java.lang.Object{
  4. public shulaing.han.ndk_callback.DataProvider();
  5. Signature: ()V
  6. public native void callCcode();
  7. Signature: ()V
  8. public void helloFromJava();
  9. Signature: ()V
  10. public int Add(int, int);
  11. Signature: (II)I
  12. public void printString(java.lang.String);
  13. Signature: (Ljava/lang/String;)V
  14. }

截图 : 

 
 
方法签名介绍 : 
-- 返回值null, 参数null : void helloFromJava() 方法的签名是 "()V", 括号里什么都没有代表参数为null, V代表返回值是void;
-- 返回值int, 参数两个int : int Add(int x,int y) 方法的签名是 "(II)I", 括号中II表示两个int类型参数, 右边括号外的I代表返回值是int类型;
-- 返回值null, 参数String : void printString(String s) 方法签名是 "(Ljava/lang/String;)V", 括号中的Ljava/lang/String; 表示参数是String类型, V表示返回值是void;
 
jni.h中定义的回调Java方法的相关函数 : 
  1. jmethodID   (*GetMethodID)(JNIEnv*, jclass, const char*, const char*);
  2. jobject     (*CallObjectMethod)(JNIEnv*, jobject, jmethodID, ...);
  3. jobject     (*CallObjectMethodV)(JNIEnv*, jobject, jmethodID, va_list);
  4. jobject     (*CallObjectMethodA)(JNIEnv*, jobject, jmethodID, jvalue*);
  5. jboolean    (*CallBooleanMethod)(JNIEnv*, jobject, jmethodID, ...);
  6. jboolean    (*CallBooleanMethodV)(JNIEnv*, jobject, jmethodID, va_list);
  7. jboolean    (*CallBooleanMethodA)(JNIEnv*, jobject, jmethodID, jvalue*);
  8. jbyte       (*CallByteMethod)(JNIEnv*, jobject, jmethodID, ...);
  9. jbyte       (*CallByteMethodV)(JNIEnv*, jobject, jmethodID, va_list);
  10. jbyte       (*CallByteMethodA)(JNIEnv*, jobject, jmethodID, jvalue*);
  11. jchar       (*CallCharMethod)(JNIEnv*, jobject, jmethodID, ...);
  12. jchar       (*CallCharMethodV)(JNIEnv*, jobject, jmethodID, va_list);
  13. jchar       (*CallCharMethodA)(JNIEnv*, jobject, jmethodID, jvalue*);
  14. jshort      (*CallShortMethod)(JNIEnv*, jobject, jmethodID, ...);
  15. jshort      (*CallShortMethodV)(JNIEnv*, jobject, jmethodID, va_list);
  16. jshort      (*CallShortMethodA)(JNIEnv*, jobject, jmethodID, jvalue*);
  17. jint        (*CallIntMethod)(JNIEnv*, jobject, jmethodID, ...);
  18. jint        (*CallIntMethodV)(JNIEnv*, jobject, jmethodID, va_list);
  19. jint        (*CallIntMethodA)(JNIEnv*, jobject, jmethodID, jvalue*);
  20. jlong       (*CallLongMethod)(JNIEnv*, jobject, jmethodID, ...);
  21. jlong       (*CallLongMethodV)(JNIEnv*, jobject, jmethodID, va_list);
  22. jlong       (*CallLongMethodA)(JNIEnv*, jobject, jmethodID, jvalue*);
  23. jfloat      (*CallFloatMethod)(JNIEnv*, jobject, jmethodID, ...) __NDK_FPABI__;
  24. jfloat      (*CallFloatMethodV)(JNIEnv*, jobject, jmethodID, va_list) __NDK_FPABI__;
  25. jfloat      (*CallFloatMethodA)(JNIEnv*, jobject, jmethodID, jvalue*) __NDK_FPABI__;
  26. jdouble     (*CallDoubleMethod)(JNIEnv*, jobject, jmethodID, ...) __NDK_FPABI__;
  27. jdouble     (*CallDoubleMethodV)(JNIEnv*, jobject, jmethodID, va_list) __NDK_FPABI__;
  28. jdouble     (*CallDoubleMethodA)(JNIEnv*, jobject, jmethodID, jvalue*) __NDK_FPABI__;
  29. void        (*CallVoidMethod)(JNIEnv*, jobject, jmethodID, ...);
  30. void        (*CallVoidMethodV)(JNIEnv*, jobject, jmethodID, va_list);
  31. void        (*CallVoidMethodA)(JNIEnv*, jobject, jmethodID, jvalue*);
 

C语言代码 : 

  1. #include "shulaing_han_ndk_callback_DataProvider.h"
  2. #include <string.h>
  3. #include <android/log.h>
  4. #define LOG_TAG "System.out"
  5. #define LOGD(...) __android_log_print(ANDROID_LOG_DEBUG, LOG_TAG, __VA_ARGS__)
  6. #define LOGI(...) __android_log_print(ANDROID_LOG_INFO, LOG_TAG, __VA_ARGS__)
  7. JNIEXPORT void JNICALL Java_shulaing_han_ndk_1callback_DataProvider_callCcode
  8. (JNIEnv * env, jobject obj)
  9. {
  10. //调用DataProvider对象中的helloFromJava()方法
  11. //获取到某个对象, 获取对象中的方法, 调用获取到的方法
  12. LOGI("in code");
  13. //DataProvider完整类名 shulaing.han.ndk_callback.DataProvider
  14. char* classname = "shulaing/han/ndk_callback/DataProvider";
  15. jclass dpclazz = (*env)->FindClass(env, classname);
  16. if(dpclazz == 0)
  17. LOGI("class not find !!!");
  18. else
  19. LOGI("class find !!!");
  20. //参数介绍 : 第二个参数是Class对象, 第三个参数是方法名,第四个参数是方法的签名, 获取到调用的method
  21. jmethodID methodID = (*env)->GetMethodID(env, dpclazz, "helloFromJava", "()V");
  22. if(methodID == 0)
  23. LOGI("method not find !!!");
  24. else
  25. LOGI("method find !!!");
  26. /*
  27. * 调用方法 void (*CallVoidMethod)(JNIEnv*, jobject, jmethodID, ...);
  28. * 参数介绍 : 后面的 ... 是可变参数, 如果该返回值void的方法有参数, 就将参数按照次序排列
  29. */
  30. LOGI("before call method");
  31. (*env)->CallVoidMethod(env, obj, methodID);
  32. LOGI("after call method");
  33. }

Java代码 : 

--XML布局文件代码 : 
  1. <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
  2. xmlns:tools="http://schemas.android.com/tools"
  3. android:layout_width="match_parent"
  4. android:layout_height="match_parent"
  5. android:orientation="vertical"
  6. android:paddingBottom="@dimen/activity_vertical_margin"
  7. android:paddingLeft="@dimen/activity_horizontal_margin"
  8. android:paddingRight="@dimen/activity_horizontal_margin"
  9. android:paddingTop="@dimen/activity_vertical_margin"
  10. tools:context=".MainActivity" >
  11. <Button
  12. android:id="@+id/call_void_method"
  13. android:layout_width="wrap_content"
  14. android:layout_height="wrap_content"
  15. android:onClick="onClick"
  16. android:text="C语言回调Java中的空方法" />
  17. </LinearLayout>

--MainActivity代码 : 

  1. package shulaing.han.ndk_callback;
  2. import android.app.Activity;
  3. import android.os.Bundle;
  4. import android.view.View;
  5. public class MainActivity extends Activity {
  6. static{
  7. System.loadLibrary("jni");
  8. }
  9. DataProvider dp;
  10. @Override
  11. protected void onCreate(Bundle savedInstanceState) {
  12. super.onCreate(savedInstanceState);
  13. setContentView(R.layout.activity_main);
  14. dp = new DataProvider();
  15. }
  16. public void onClick(View view) {
  17. int id = view.getId();
  18. switch (id) {
  19. case R.id.call_void_method:
  20. dp.callCcode();
  21. break;
  22. default:
  23. break;
  24. }
  25. }
  26. }
 

执行结果 : 

.

 
 

4. C代码回调Java中带String参数的方法

 
在DataProvider中添加两个native方法 : 
  1. public native void callCcode();
  2. public native void callCcode1();
  3. public native void callCcode2();
进入bin/classes目录, 使用 javah -jni shulaing.han.ndk_callback.DataProvider 命令生成头文件 : 
  1. /* DO NOT EDIT THIS FILE - it is machine generated */
  2. #include <jni.h>
  3. /* Header for class shulaing_han_ndk_callback_DataProvider */
  4. #ifndef _Included_shulaing_han_ndk_callback_DataProvider
  5. #define _Included_shulaing_han_ndk_callback_DataProvider
  6. #ifdef __cplusplus
  7. extern "C" {
  8. #endif
  9. /*
  10. * Class:     shulaing_han_ndk_callback_DataProvider
  11. * Method:    callCcode
  12. * Signature: ()V
  13. */
  14. JNIEXPORT void JNICALL Java_shulaing_han_ndk_1callback_DataProvider_callCcode
  15. (JNIEnv *, jobject);
  16. /*
  17. * Class:     shulaing_han_ndk_callback_DataProvider
  18. * Method:    callCcode1
  19. * Signature: ()V
  20. */
  21. JNIEXPORT void JNICALL Java_shulaing_han_ndk_1callback_DataProvider_callCcode1
  22. (JNIEnv *, jobject);
  23. /*
  24. * Class:     shulaing_han_ndk_callback_DataProvider
  25. * Method:    callCcode2
  26. * Signature: ()V
  27. */
  28. JNIEXPORT void JNICALL Java_shulaing_han_ndk_1callback_DataProvider_callCcode2
  29. (JNIEnv *, jobject);
  30. #ifdef __cplusplus
  31. }
  32. #endif
  33. #endif
 
jni C语言代码 : 这里只需要修改两处, 方法名, 获取方法id中的参数, 调用方法中最后加上一个Java参数;
  1. JNIEXPORT void JNICALL Java_shulaing_han_ndk_1callback_DataProvider_callCcode1
  2. (JNIEnv *env, jobject obj)
  3. {
  4. //调用DataProvider对象中的helloFromJava()方法
  5. //获取到某个对象, 获取对象中的方法, 调用获取到的方法
  6. LOGI("in code");
  7. //DataProvider完整类名 shulaing.han.ndk_callback.DataProvider
  8. char* classname = "shulaing/han/ndk_callback/DataProvider";
  9. jclass dpclazz = (*env)->FindClass(env, classname);
  10. if(dpclazz == 0)
  11. LOGI("class not find !!!");
  12. else
  13. LOGI("class find !!!");
  14. //参数介绍 : 第二个参数是Class对象, 第三个参数是方法名,第四个参数是方法的签名, 获取到调用的method
  15. jmethodID methodID = (*env)->GetMethodID(env, dpclazz, "printString", "(Ljava/lang/String;)V");
  16. if(methodID == 0)
  17. LOGI("method not find !!!");
  18. else
  19. LOGI("method find !!!");
  20. /*
  21. * 调用方法 void (*CallVoidMethod)(JNIEnv*, jobject, jmethodID, ...);
  22. * 参数介绍 : 后面的 ... 是可变参数, 如果该返回值void的方法有参数, 就将参数按照次序排列
  23. */
  24. LOGI("before call method");
  25. (*env)->CallVoidMethod(env, obj, methodID, (*env)->NewStringUTF(env, "printString method callback success!!"));
  26. LOGI("after call method");
  27. }

执行后的结果 : 

 

5. C代码中回调带两个int类型的参数的方法

 
按照上面的流程, 不同之处就是jni中获取方法 和 方法id , 调用方法的jni函数不同 : 
  1. JNIEXPORT void JNICALL Java_shulaing_han_ndk_1callback_DataProvider_callCcode2
  2. (JNIEnv *env, jobject obj)
  3. {
  4. //调用DataProvider对象中的helloFromJava()方法
  5. //获取到某个对象, 获取对象中的方法, 调用获取到的方法
  6. LOGI("in code");
  7. //DataProvider完整类名 shulaing.han.ndk_callback.DataProvider
  8. char* classname = "shulaing/han/ndk_callback/DataProvider";
  9. jclass dpclazz = (*env)->FindClass(env, classname);
  10. if(dpclazz == 0)
  11. LOGI("class not find !!!");
  12. else
  13. LOGI("class find !!!");
  14. //参数介绍 : 第二个参数是Class对象, 第三个参数是方法名,第四个参数是方法的签名, 获取到调用的method
  15. jmethodID methodID = (*env)->GetMethodID(env, dpclazz, "Add", "(II)I");
  16. if(methodID == 0)
  17. LOGI("method not find !!!");
  18. else
  19. LOGI("method find !!!");
  20. /*
  21. * 调用方法 void (*CallVoidMethod)(JNIEnv*, jobject, jmethodID, ...);
  22. * 参数介绍 : 后面的 ... 是可变参数, 如果该返回值void的方法有参数, 就将参数按照次序排列
  23. */
  24. LOGI("before call method");
  25. (*env)->CallIntMethod(env, obj, methodID, 3, 5);
  26. LOGI("after call method");
  27. }

Java代码 : 

  1. case R.id.call_int_parameter_method:
  2. dp.callCcode2();
  3. break;
执行结果 : 

.

作者 : 万境绝尘 

转载请注明出处 : http://blog.csdn.net/shulianghan/article/details/18964835

.

6. 完整源码

 
Java源码 : 
-- DataProvider源码 : 
  1. package shulaing.han.ndk_callback;
  2. public class DataProvider {
  3. public native void callCcode();
  4. public native void callCcode1();
  5. public native void callCcode2();
  6. //C调用java中空方法 shulaing.han.ndk_callback.DataProvider
  7. public void helloFromJava(){
  8. System.out.println("hello from java");
  9. }
  10. //C调用java中的带两个int参数的方法
  11. public int Add(int x,int y){
  12. System.out.println("the add result is : " + (x + y));
  13. return x + y;
  14. }
  15. //C调用java中参数为string的方法
  16. public void printString(String s){
  17. System.out.println("in java code :" + s);
  18. }
  19. }
-- MainActivity源码 : 
  1. package shulaing.han.ndk_callback;
  2. import android.app.Activity;
  3. import android.os.Bundle;
  4. import android.view.View;
  5. public class MainActivity extends Activity {
  6. static{
  7. System.loadLibrary("jni");
  8. }
  9. DataProvider dp;
  10. @Override
  11. protected void onCreate(Bundle savedInstanceState) {
  12. super.onCreate(savedInstanceState);
  13. setContentView(R.layout.activity_main);
  14. dp = new DataProvider();
  15. }
  16. public void onClick(View view) {
  17. int id = view.getId();
  18. switch (id) {
  19. case R.id.call_void_method:
  20. dp.callCcode();
  21. break;
  22. case R.id.call_string_parameter_method:
  23. dp.callCcode1();
  24. break;
  25. case R.id.call_int_parameter_method:
  26. dp.callCcode2();
  27. break;
  28. default:
  29. break;
  30. }
  31. }
  32. }
 
XML布局文件源码 : 
  1. <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
  2. xmlns:tools="http://schemas.android.com/tools"
  3. android:layout_width="match_parent"
  4. android:layout_height="match_parent"
  5. android:orientation="vertical"
  6. android:paddingBottom="@dimen/activity_vertical_margin"
  7. android:paddingLeft="@dimen/activity_horizontal_margin"
  8. android:paddingRight="@dimen/activity_horizontal_margin"
  9. android:paddingTop="@dimen/activity_vertical_margin"
  10. tools:context=".MainActivity" >
  11. <Button
  12. android:id="@+id/call_void_method"
  13. android:layout_width="wrap_content"
  14. android:layout_height="wrap_content"
  15. android:onClick="onClick"
  16. android:text="C语言回调Java中的空方法" />
  17. <Button
  18. android:id="@+id/call_string_parameter_method"
  19. android:layout_width="wrap_content"
  20. android:layout_height="wrap_content"
  21. android:onClick="onClick"
  22. android:text="C语言回调Java中的String参数方法" />
  23. <Button
  24. android:id="@+id/call_int_parameter_method"
  25. android:layout_width="wrap_content"
  26. android:layout_height="wrap_content"
  27. android:onClick="onClick"
  28. android:text="C语言回调Java中的int参数方法" />
  29. </LinearLayout>
 
 
jni源码 : 
-- 头文件源码 : 
  1. /* DO NOT EDIT THIS FILE - it is machine generated */
  2. #include <jni.h>
  3. /* Header for class shulaing_han_ndk_callback_DataProvider */
  4. #ifndef _Included_shulaing_han_ndk_callback_DataProvider
  5. #define _Included_shulaing_han_ndk_callback_DataProvider
  6. #ifdef __cplusplus
  7. extern "C" {
  8. #endif
  9. /*
  10. * Class:     shulaing_han_ndk_callback_DataProvider
  11. * Method:    callCcode
  12. * Signature: ()V
  13. */
  14. JNIEXPORT void JNICALL Java_shulaing_han_ndk_1callback_DataProvider_callCcode
  15. (JNIEnv *, jobject);
  16. /*
  17. * Class:     shulaing_han_ndk_callback_DataProvider
  18. * Method:    callCcode1
  19. * Signature: ()V
  20. */
  21. JNIEXPORT void JNICALL Java_shulaing_han_ndk_1callback_DataProvider_callCcode1
  22. (JNIEnv *, jobject);
  23. /*
  24. * Class:     shulaing_han_ndk_callback_DataProvider
  25. * Method:    callCcode2
  26. * Signature: ()V
  27. */
  28. JNIEXPORT void JNICALL Java_shulaing_han_ndk_1callback_DataProvider_callCcode2
  29. (JNIEnv *, jobject);
  30. #ifdef __cplusplus
  31. }
  32. #endif
  33. #endif
-- Android.mk源码 :
  1. LOCAL_PATH := $(call my-dir)
  2. include $(CLEAR_VARS)
  3. LOCAL_MODULE    := jni
  4. LOCAL_SRC_FILES := jni.c
  5. #增加log函数对应的log库
  6. LOCAL_LDLIBS += -llog
  7. include $(BUILD_SHARED_LIBRARY)
-- jni主程序源码 :  
  1. #include "shulaing_han_ndk_callback_DataProvider.h"
  2. #include "first.h"
  3. #include <string.h>
  4. #include <android/log.h>
  5. #define LOG_TAG "System.out"
  6. #define LOGD(...) __android_log_print(ANDROID_LOG_DEBUG, LOG_TAG, __VA_ARGS__)
  7. #define LOGI(...) __android_log_print(ANDROID_LOG_INFO, LOG_TAG, __VA_ARGS__)
  8. JNIEXPORT void JNICALL Java_shulaing_han_ndk_1callback_DataProvider_callCcode
  9. (JNIEnv * env, jobject obj)
  10. {
  11. //调用DataProvider对象中的helloFromJava()方法
  12. //获取到某个对象, 获取对象中的方法, 调用获取到的方法
  13. LOGI("in code");
  14. //DataProvider完整类名 shulaing.han.ndk_callback.DataProvider
  15. char* classname = "shulaing/han/ndk_callback/DataProvider";
  16. jclass dpclazz = (*env)->FindClass(env, classname);
  17. if(dpclazz == 0)
  18. LOGI("class not find !!!");
  19. else
  20. LOGI("class find !!!");
  21. //参数介绍 : 第二个参数是Class对象, 第三个参数是方法名,第四个参数是方法的签名, 获取到调用的method
  22. jmethodID methodID = (*env)->GetMethodID(env, dpclazz, "helloFromJava", "()V");
  23. if(methodID == 0)
  24. LOGI("method not find !!!");
  25. else
  26. LOGI("method find !!!");
  27. /*
  28. * 调用方法 void (*CallVoidMethod)(JNIEnv*, jobject, jmethodID, ...);
  29. * 参数介绍 : 后面的 ... 是可变参数, 如果该返回值void的方法有参数, 就将参数按照次序排列
  30. */
  31. LOGI("before call method");
  32. (*env)->CallVoidMethod(env, obj, methodID);
  33. LOGI("after call method");
  34. }
  35. JNIEXPORT void JNICALL Java_shulaing_han_ndk_1callback_DataProvider_callCcode1
  36. (JNIEnv *env, jobject obj)
  37. {
  38. //调用DataProvider对象中的helloFromJava()方法
  39. //获取到某个对象, 获取对象中的方法, 调用获取到的方法
  40. LOGI("in code");
  41. //DataProvider完整类名 shulaing.han.ndk_callback.DataProvider
  42. char* classname = "shulaing/han/ndk_callback/DataProvider";
  43. jclass dpclazz = (*env)->FindClass(env, classname);
  44. if(dpclazz == 0)
  45. LOGI("class not find !!!");
  46. else
  47. LOGI("class find !!!");
  48. //参数介绍 : 第二个参数是Class对象, 第三个参数是方法名,第四个参数是方法的签名, 获取到调用的method
  49. jmethodID methodID = (*env)->GetMethodID(env, dpclazz, "printString", "(Ljava/lang/String;)V");
  50. if(methodID == 0)
  51. LOGI("method not find !!!");
  52. else
  53. LOGI("method find !!!");
  54. /*
  55. * 调用方法 void (*CallVoidMethod)(JNIEnv*, jobject, jmethodID, ...);
  56. * 参数介绍 : 后面的 ... 是可变参数, 如果该返回值void的方法有参数, 就将参数按照次序排列
  57. */
  58. LOGI("before call method");
  59. (*env)->CallVoidMethod(env, obj, methodID, (*env)->NewStringUTF(env, "printString method callback success!!"));
  60. LOGI("after call method");
  61. }
  62. /*
  63. * 实际开发的情况
  64. * C代码工程师给我们 first.h first.c , 我们只需要将first.h引入, 然后就可以使用其中的方法了
  65. */
  66. JNIEXPORT void JNICALL Java_shulaing_han_ndk_1callback_DataProvider_callCcode2
  67. (JNIEnv *env, jobject obj)
  68. {
  69. //调用DataProvider对象中的helloFromJava()方法
  70. //获取到某个对象, 获取对象中的方法, 调用获取到的方法
  71. LOGI("in code");
  72. //DataProvider完整类名 shulaing.han.ndk_callback.DataProvider
  73. char* classname = "shulaing/han/ndk_callback/DataProvider";
  74. jclass dpclazz = (*env)->FindClass(env, classname);
  75. if(dpclazz == 0)
  76. LOGI("class not find !!!");
  77. else
  78. LOGI("class find !!!");
  79. //参数介绍 : 第二个参数是Class对象, 第三个参数是方法名,第四个参数是方法的签名, 获取到调用的method
  80. jmethodID methodID = (*env)->GetMethodID(env, dpclazz, "Add", "(II)I");
  81. if(methodID == 0)
  82. LOGI("method not find !!!");
  83. else
  84. LOGI("method find !!!");
  85. /*
  86. * 调用方法 void (*CallVoidMethod)(JNIEnv*, jobject, jmethodID, ...);
  87. * 参数介绍 : 后面的 ... 是可变参数, 如果该返回值void的方法有参数, 就将参数按照次序排列
  88. */
  89. LOGI("before call method");
  90. (*env)->CallIntMethod(env, obj, methodID, 3, 5);
  91. LOGI("after call method");
  92. }

7. 将程序上传到GitHub中

GitHub地址 : 
-- SSH : git@github.com:han1202012/NDK_Callback.git
-- HTTP : https://github.com/han1202012/NDK_Callback.git
.
 
 
.
 

六. 实际开发中的环境

 
这里举一个简单的小例子 : 
-- 在实际开发中, C工程师会给我们c文件如下 : first.h first.c, 一个C主程序, 一个头文件, 我们只需要将这个头文件引入到jni中的C代码中即可, 在我们自定义生成的签名函数中调用 first.h中的方法;
 
first.h源码 : 
  1. #ifndef FIRST_H
  2. #define FIRST_H
  3. extern int first(int  x, int  y);
  4. #endif /* FIRST_H */

first.c源码 : 

  1. #include "first.h"
  2. int  first(int  x, int  y)
  3. {
  4. return x + y;
  5. }


在签名函数中, 直接调用 first()方法即可
;

 
.
 

七 分析Log日志系统框架的JNI代码

 
 
在这里分析日志输出函数 : Log.i(TAG, "log"), 分析该日志系统的JNI层源码结构;
 
这里使用下载的Android2.3.3源码进行分析 : 在 http://blog.csdn.net/shulianghan/article/details/17350401 中介绍了如何使用repo 和 git 下载Android源码 和 kernel 源码;
 
LogJNI调用层次 : android.util.Log.java 中的接口 是通过JNI调用 本地库 并最终调用内核驱动程序 Logger 将日志信息写到 内核空间中.
 
分析的源码文件 : "\" 代表Android源代码的本目录;
-- Java代码 : \frameworks\base\core\java\android\util\Log.java
-- JNI层实现代码 : \frameworks\base\core\jni\android_util_Log.cpp
下面的是Android自定义的JNI规范相关的源码 : 
-- JNI规范头文件 : \dalvik\libnativehelper\include\nativehelper\jni.h
-- JNI帮助文件 : ① \dalvik\libnativehelper\include\nativehelper\JNIHelp.h  ② \dalvik\libnativehelper\JNIHelp.c
-- JNI运行时文件 :  \frameworks\base\core\jni\AndroidRuntime.cpp
 
这里将上面几个文件上传到CSDN资源中, 便于查看 : http://download.csdn.net/detail/han1202012/6905507 ;
 

1. 分析Log.java源码

 
Log.java分析 : 在Log.java文件中,定义了 isLoggable 和 println_native 两个Native方法, 在Java方法中, 只需要事先声明native方法, 不用实现方法体, 可以直接调用;
Log.java在Android源码中的位置 : \frameworks\base\core\java\android\util\Log.java
 
Log.java内容 : 
  1. package android.util;
  2. import com.android.internal.os.RuntimeInit;
  3. import java.io.PrintWriter;
  4. import java.io.StringWriter;
  5. public final class Log {
  6. ... ...
  7. //打印日志
  8. public static int d(String tag, String msg) {
  9. return println_native(LOG_ID_MAIN, DEBUG, tag, msg);
  10. }
  11. //打印日志和异常
  12. public static int d(String tag, String msg, Throwable tr) {
  13. return println_native(LOG_ID_MAIN, DEBUG, tag, msg + '\n' + getStackTraceString(tr));
  14. }
  15. //打印日志
  16. public static int i(String tag, String msg) {
  17. return println_native(LOG_ID_MAIN, INFO, tag, msg);
  18. }
  19. ... ...
  20. //声明native方法
  21. public static native boolean isLoggable(String tag, int level);
  22. ... ...
  23. /** @hide */ public static final int LOG_ID_MAIN = 0;
  24. /** @hide */ public static final int LOG_ID_RADIO = 1;
  25. /** @hide */ public static final int LOG_ID_EVENTS = 2;
  26. /** @hide */ public static final int LOG_ID_SYSTEM = 3;
  27. //声明native方法
  28. /** @hide */ public static native int println_native(int bufID,
  29. int priority, String tag, String msg);
  30. }

2. 分析Log系统JNI层源码

 
JNI层方法: JNI层方法根据一定规则与Java层声明的Native方法进行映射, 然后可以通过JNIEnv指针提供的JNI函数对Java层进行操作;
Log系统的JNI层文件是 : android_util_Log.cpp, 该文件路径 :\frameworks\base\core\jni\android_util_Log.cpp 代码如下 :
  1. #define LOG_NAMESPACE "log.tag."
  2. #define LOG_TAG "Log_println"
  3. #include <assert.h>
  4. #include <cutils/properties.h>
  5. #include <utils/Log.h>
  6. #include <utils/String8.h>
  7. #include "jni.h"
  8. #include "utils/misc.h"
  9. #include "android_runtime/AndroidRuntime.h"
  10. ... ...
  11. namespace android {
  12. struct levels_t {
  13. jint verbose;
  14. jint debug;
  15. jint info;
  16. jint warn;
  17. jint error;
  18. jint assert;
  19. };
  20. static levels_t levels;
  21. static int toLevel(const char* value)
  22. {
  23. switch (value[0]) {
  24. case 'V': return levels.verbose;
  25. case 'D': return levels.debug;
  26. case 'I': return levels.info;
  27. case 'W': return levels.warn;
  28. case 'E': return levels.error;
  29. case 'A': return levels.assert;
  30. case 'S': return -1; // SUPPRESS
  31. }
  32. return levels.info;
  33. }
  34. /*
  35. 实现Java层声明的 isLoggable 方法, 注意方法名不符合标准JNI规范
  36. 标准的JNI规范方法名应该是 Java_包名_类名_方法名
  37. 其中传入了JNIEnv 和 jobject 参数, JNIEnv参数是Java运行环境, 可以与JVM进行交互
  38. jobject参数是包含Native方法的Java类对象
  39. 该方法中可以通过JNIEnv调用本地库进行函数处理, 最后返回给Java层函数
  40. */
  41. static jboolean android_util_Log_isLoggable(JNIEnv* env, jobject clazz, jstring tag, jint level)
  42. {
  43. #ifndef HAVE_ANDROID_OS
  44. return false;
  45. #else /* HAVE_ANDROID_OS */
  46. int len;
  47. char key[PROPERTY_KEY_MAX];
  48. char buf[PROPERTY_VALUE_MAX];
  49. if (tag == NULL) {
  50. return false;
  51. }
  52. jboolean result = false;
  53. //调用了JNI函数
  54. const char* chars = env->GetStringUTFChars(tag, NULL);
  55. if ((strlen(chars)+sizeof(LOG_NAMESPACE)) > PROPERTY_KEY_MAX) {
  56. jclass clazz = env->FindClass("java/lang/IllegalArgumentException");
  57. char buf2[200];
  58. snprintf(buf2, sizeof(buf2), "Log tag \"%s\" exceeds limit of %d characters\n",
  59. chars, PROPERTY_KEY_MAX - sizeof(LOG_NAMESPACE));
  60. // release the chars!
  61. env->ReleaseStringUTFChars(tag, chars);
  62. env->ThrowNew(clazz, buf2);
  63. return false;
  64. } else {
  65. strncpy(key, LOG_NAMESPACE, sizeof(LOG_NAMESPACE)-1);
  66. strcpy(key + sizeof(LOG_NAMESPACE) - 1, chars);
  67. }
  68. env->ReleaseStringUTFChars(tag, chars);
  69. len = property_get(key, buf, "");
  70. int logLevel = toLevel(buf);
  71. return (logLevel >= 0 && level >= logLevel) ? true : false;
  72. #endif /* HAVE_ANDROID_OS */
  73. }
  74. /*
  75. * In class android.util.Log:
  76. *  public static native int println_native(int buffer, int priority, String tag, String msg)
  77. */
  78. static jint android_util_Log_println_native(JNIEnv* env, jobject clazz,
  79. jint bufID, jint priority, jstring tagObj, jstring msgObj)
  80. {
  81. const char* tag = NULL;
  82. const char* msg = NULL;
  83. if (msgObj == NULL) {
  84. jclass npeClazz;
  85. npeClazz = env->FindClass("java/lang/NullPointerException");
  86. assert(npeClazz != NULL);
  87. env->ThrowNew(npeClazz, "println needs a message");
  88. return -1;
  89. }
  90. if (bufID < 0 || bufID >= LOG_ID_MAX) {
  91. jclass npeClazz;
  92. npeClazz = env->FindClass("java/lang/NullPointerException");
  93. assert(npeClazz != NULL);
  94. env->ThrowNew(npeClazz, "bad bufID");
  95. return -1;
  96. }
  97. if (tagObj != NULL)
  98. tag = env->GetStringUTFChars(tagObj, NULL);  //调用JNI函数
  99. msg = env->GetStringUTFChars(msgObj, NULL);
  100. int res = __android_log_buf_write(bufID, (android_LogPriority)priority, tag, msg);
  101. if (tag != NULL)
  102. env->ReleaseStringUTFChars(tagObj, tag); //调用JNI函数释放资源
  103. env->ReleaseStringUTFChars(msgObj, msg); //调用JNI函数释放资源
  104. return res;
  105. }
  106. /*
  107. * JNI registration. JNI方法注册
  108. */
  109. static JNINativeMethod gMethods[] = {
  110. /* name, signature, funcPtr */
  111. { "isLoggable",      "(Ljava/lang/String;I)Z", (void*) android_util_Log_isLoggable },
  112. { "println_native",  "(IILjava/lang/String;Ljava/lang/String;)I", (void*) android_util_Log_println_native },
  113. };
  114. int register_android_util_Log(JNIEnv* env)
  115. {
  116. jclass clazz = env->FindClass("android/util/Log");
  117. if (clazz == NULL) {
  118. LOGE("Can't find android/util/Log");
  119. return -1;
  120. }
  121. levels.verbose = env->GetStaticIntField(clazz, env->GetStaticFieldID(clazz, "VERBOSE", "I"));
  122. levels.debug = env->GetStaticIntField(clazz, env->GetStaticFieldID(clazz, "DEBUG", "I"));
  123. levels.info = env->GetStaticIntField(clazz, env->GetStaticFieldID(clazz, "INFO", "I"));
  124. levels.warn = env->GetStaticIntField(clazz, env->GetStaticFieldID(clazz, "WARN", "I"));
  125. levels.error = env->GetStaticIntField(clazz, env->GetStaticFieldID(clazz, "ERROR", "I"));
  126. levels.assert = env->GetStaticIntField(clazz, env->GetStaticFieldID(clazz, "ASSERT", "I"));
  127. return AndroidRuntime::registerNativeMethods(env, "android/util/Log", gMethods, NELEM(gMethods));
  128. }
  129. }; // namespace android

3. 声明JNI 与 Native 方法的映射关系

 
标准JNI规范 : 在标准的JNI规范中, Java中的Native方法 与 JNI层方法 是通过方法名的对应关系进行映射的, 我们通过 javah 工具生成JNI层头文件, 头文件中定义了规范的JNI层方法名, 这个方法名就与Java Native方法对应;
 
Android自定义规范 : 在 \dalvik\libnativehelper\include\nativehelper\jni.h 中定义了这样的映射关系 : 
  1. typedef struct {
  2. const char* name;       //Java层Native函数方法名
  3. const char* signature;  //Java层Native函数的签名
  4. void*       fnPtr;      //JNI层实现的方法
  5. } JNINativeMethod;

.

JNINativeMethod类型数据 : 在android_util_Log.cpp 中定义了一个该类型的数组 :
  1. /*
  2. * JNI registration.
  3. */
  4. static JNINativeMethod gMethods[] = {
  5. /* name, signature, funcPtr */
  6. { "isLoggable",      "(Ljava/lang/String;I)Z", (void*) android_util_Log_isLoggable },
  7. { "println_native",  "(IILjava/lang/String;Ljava/lang/String;)I", (void*) android_util_Log_println_native },
  8. };

JNINativeMethod结构体作用 : JNINativeMethod是一个结构体类型, 声明了Native方法 与 JNI方法 的映射关系;

-- 解析上面的数组中的元素 : 
 --- Native方法 : "isLoggable" 是Java中声明的Native方法; 
 --- 方法签名 : "(Ljava/lang/String;I)Z" 表示该方法的签名, 参数是String类型 和 int类型, Z 表示 boolean类型;
 --- JNI方法 : (void*) android_util_Log_isLoggable 表示JNI层实现的方法指针;
 

4. 注册JNI方法到虚拟机中

映射关系体现到虚拟机中 :  在android_util_Log.cpp 中存在这样的方法 : 
  1. int register_android_util_Log(JNIEnv* env)
  2. {
  3. jclass clazz = env->FindClass("android/util/Log");
  4. if (clazz == NULL) {
  5. LOGE("Can't find android/util/Log");
  6. return -1;
  7. }
  8. levels.verbose = env->GetStaticIntField(clazz, env->GetStaticFieldID(clazz, "VERBOSE", "I"));
  9. levels.debug = env->GetStaticIntField(clazz, env->GetStaticFieldID(clazz, "DEBUG", "I"));
  10. levels.info = env->GetStaticIntField(clazz, env->GetStaticFieldID(clazz, "INFO", "I"));
  11. levels.warn = env->GetStaticIntField(clazz, env->GetStaticFieldID(clazz, "WARN", "I"));
  12. levels.error = env->GetStaticIntField(clazz, env->GetStaticFieldID(clazz, "ERROR", "I"));
  13. levels.assert = env->GetStaticIntField(clazz, env->GetStaticFieldID(clazz, "ASSERT", "I"));
  14. return AndroidRuntime::registerNativeMethods(env, "android/util/Log", gMethods, NELEM(gMethods));
  15. }
  16. }; // namespace android

核心方法 : 该函数调用了 AndroidRuntime::registerNativeMethods(env, "android/util/Log", gMethods, NELEM(gMethods)) 方法注册JNI方法;

 
register_android_util_Log调用时机 : 该函数是在Android系统启动的时候, 通过AndroidRuntime.cpp中的register_jni_proocs方法执行, 执行该方法的时候会将 Native方法 与 JNI方法 的函数映射关系注册给 Dalvik 虚拟机;
 
 

5. 解析registerNativeMethod函数

 
该函数定义在AndroidRuntime.cpp中 : 该文件的路径在 \frameworks\base\core\jni\AndroidRuntime.cpp ;
  1. /*
  2. * Register native methods using JNI.
  3. */
  4. /*static*/ int AndroidRuntime::registerNativeMethods(JNIEnv* env,
  5. const char* className, const JNINativeMethod* gMethods, int numMethods)
  6. {
  7. return jniRegisterNativeMethods(env, className, gMethods, numMethods);
  8. }

registerNativeMethods 方法只是对 jniRegisterNativeMethods 方法的封装, 在JNIHelp.h中找到该方法的声明

  1. /*
  2. * Register one or more native methods with a particular class.
  3. */
  4. int jniRegisterNativeMethods(C_JNIEnv* env, const char* className,
  5. const JNINativeMethod* gMethods, int numMethods);
 

在JNIHelp.c 中找到该方法的实现 : 最终方法中调用了 JNIEnv 的RegisterNatives 函数, 将gMethods中存放的JNINativeMethod结构体(存放Native方法 与 JNI方法关联信息) 传递到java虚拟机;

  1. /*
  2. * Register native JNI-callable methods.
  3. *
  4. * "className" looks like "java/lang/String".
  5. */
  6. int jniRegisterNativeMethods(JNIEnv* env, const char* className,
  7. const JNINativeMethod* gMethods, int numMethods)
  8. {
  9. jclass clazz;
  10. LOGV("Registering %s natives\n", className);
  11. clazz = (*env)->FindClass(env, className);
  12. if (clazz == NULL) {
  13. LOGE("Native registration unable to find class '%s'\n", className);
  14. return -1;
  15. }
  16. int result = 0;
  17. if ((*env)->RegisterNatives(env, clazz, gMethods, numMethods) < 0) {
  18. LOGE("RegisterNatives failed for '%s'\n", className);
  19. result = -1;
  20. }
  21. (*env)->DeleteLocalRef(env, clazz);
  22. return result;
  23. }

6. JNI的规范

 
Android中JNI存在两种规范 : 一种是标准的JNI规范, 多在应用层使用; 另一种是Android中自定义的规范, 多使用在应用框架层;
-- JNI标准规范: 遵守JNI标准规函数命名方式, JNI中方法命名为 Java_包名_类名_方法名 , 可以使用javah生成签名头文件, 靠这种方式实现 Native方法 与 JNI方法之间的映射关系, 即应用直接与框架层进行交互, 这种规范常用与应用开发;
-- 函数注册规范 : 这是Android自定义的一种规范, 应用框架层采用该规范, 即应用框架层 与 框架层 进行交互, 底层源码开发多使用该规范;
 

.

作者 : 万境绝尘 

转载请注明出处 : http://blog.csdn.net/shulianghan/article/details/18964835

【转】 Android 开发 之 JNI入门 - NDK从入门到精通的更多相关文章

  1. 【Android 应用开发】Android 开发 之 JNI入门 - NDK从入门到精通

    NDK项目源码地址 : -- 第一个JNI示例程序下载 : GitHub - https://github.com/han1202012/NDKHelloworld.git -- Java传递参数给C ...

  2. Android 开发 之 JNI入门 - NDK从入门到精通

    NDK项目源码地址 : -- 第一个JNI示例程序下载 : GitHub - https://github.com/han1202012/NDKHelloworld.git -- Java传递参数给C ...

  3. Android Studio使用JNI和NDK进行开发

    想要学习一下在Android Studio中进行JNI的开发,文章挺多的,但是几乎没有一个完整的说明的,中间总是有一两步漏掉.分享技术就应该完整的让读者学会,藏着掖着不是君子所为.对于那些故意含糊过去 ...

  4. Android开发学习之路--NDK、JNI之初体验

    好久没有更新博客了,最近一直在看一个仿微信项目,然后看源码并自己实现下,相信经过这个项目可以让自己了解一个项目中的代码以及种种需要注意的事项.不知不觉中博客已经快要40w访问量,而且排名也即将突破30 ...

  5. Android Studio通过JNI调用NDK程序

    NDK开发,其实是为了项目需要调用底层的一些C/C++的一些东西:另外就是为了效率更加高些,安全性更高. 如果你在Eclipse+ADT下开发过NDK就能体会到要么是配置NDK还要下载Cygwin,配 ...

  6. 《Android开发艺术探索》读书笔记 (13) 第13章 综合技术、第14章 JNI和NDK编程、第15章 Android性能优化

    第13章 综合技术 13.1 使用CrashHandler来获取应用的Crash信息 (1)应用发生Crash在所难免,但是如何采集crash信息以供后续开发处理这类问题呢?利用Thread类的set ...

  7. Android开发学习之路--Android Studio cmake编译ffmpeg

      最新的android studio2.2引入了cmake可以很好地实现ndk的编写.这里使用最新的方式,对于以前的android下的ndk编译什么的可以参考之前的文章:Android开发学习之路– ...

  8. Android 开发一定要看的15个实战项目

    前言: 虽说网上有太多的Android课程,但是大多都是视频,有Android在线开发环境的几乎没有,但是对于学习Android的人来说拥有在线的Android开发环境是非常好的,可以随时动手操作学习 ...

  9. Android开发环境——Eclipse ADT相关内容汇总

     Android开发环境将分为SDK相关内容.Eclipse ADT相关内容.模拟器AVD相关内容.调试器DDMS相关内容.日志LogCat相关内容.连接驱动ADB相关内容.内存泄露检测工具MAT相关 ...

随机推荐

  1. nginx低版本不支持pathinfo模式,thinkphp针对此问题的解决办法

    将一个thinkphp项目从apache环境移到nginx1.2上,怎奈,nginx这个版本默认不支持pathinfo模式 首先,编辑nginx的虚拟主机配置文件 location ~ .*.(php ...

  2. ViewData,ViewBag和TempData

      ViewData ViewBag TempData 类型 字典 Dynamic TempDataDictionary 出生时间 MVC1 MVC3   框架版本 .net3.5 .net4.0   ...

  3. String声明为NULL和""的区别

    代码虐我千百遍,我待代码如初恋. String 声明为 NULL 则声明了一个变量不指向任何一块地址,则 length()会出现错误. 声明为"",则是一个长度为0的字符串.

  4. 超简单的卸载ORACLE 11g

    本机环境 win10 64位 找到安装目录下的 F:\app\Shuai\product\11.2.0\dbhome_1 按键盘d找到deinstall文件夹进入 管理员运行deinstall.bat ...

  5. 2326: [HNOI2011]数学作业 - BZOJ

    首先是DP,分段DP(按位数讨论) 然后每一段构造出它对应的矩阵,用矩阵快速幂加速 type matrix=..,..]of int64; var n,m:int64; a,b,c,d:matrix; ...

  6. Javascript和ECMAScript二三事

    来自<javascript高级程序设计 第三版:作者Nicholas C. Zakas>的学习笔记(一) Javascript是一种专为与网页交互而设计的脚本语言,由下列三个不同部分组成: ...

  7. 无法为请求的 Configuration 对象创建配置文件 错误原因

    Configuration config = WebConfigurationManager.OpenWebConfiguration("~"); 无法为请求的 Configura ...

  8. jquery捕捉文本域输入事件

    <input type='text' /> change事件是在文本域光标失去焦点时才会触发,要监听正在输入内容事件用键盘事件监听如果想要捕捉文本域输入事件,可以使用$("inp ...

  9. php采集远程文章简单类

    <?php /** * 采集类 * @author Milkcy * @copyright (C) 2012-2015 TCCMS.COM * @lastmodify 2012-07-10 14 ...

  10. POJ3096Surprising Strings(map)

    题意:输入很多字符串,以星号结束.判断每个字符串是不是“Surprising Strings”,判断方法是:以“ZGBG”为例,“0-pairs”是ZG,GB,BG,这三个子串不相同,所以是“0-un ...