Android 开发 之 JNI入门 - NDK从入门到精通
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 文件 :
LOCAL_PATH := $(call my-dir) include $(CLEAR_VARS) LOCAL_MODULE := hello-jni
LOCAL_SRC_FILES := hello-jni.c 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 ;
执行结果 :
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)
-- 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工程
<uses-sdk
android:minSdkVersion="7"
android:targetSdkVersion="10" />
(2) 声明native方法
/*
* 声明一个native方法
* 这个方法在Java中是没有实现的, 没有方法体
* 该方法需要使用C语言编写
*/
public native String helloFromJNI();
.
作者 : 万境绝尘
转载请注明出处 : http://blog.csdn.net/shulianghan/article/details/18964835
.
(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 git@github.com:han1202012/NDKHelloworld.git
git push -u origin master
打开 Git Bash 命令行窗口 :
-- 查看状态 : 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)
指定编译模块 : LOCAL_MODULE := hello , 指定编译后的 so 文件名称, 编译好之后系统会在该名称前面加上 "lib", 后缀加上 ".so";
(2) 自动生成方法签名
/* DO NOT EDIT THIS FILE - it is machine generated */
#include <jni.h>
/* Header for class shuliang_han_ndkparameterpassing_DataProvider */ #ifndef _Included_shuliang_han_ndkparameterpassing_DataProvider
#define _Included_shuliang_han_ndkparameterpassing_DataProvider
#ifdef __cplusplus
extern "C" {
#endif
/*
* Class: shuliang_han_ndkparameterpassing_DataProvider
* Method: add
* Signature: (II)I
*/
JNIEXPORT jint JNICALL Java_shuliang_han_ndkparameterpassing_DataProvider_add
(JNIEnv *, jobject, jint, jint); /*
* Class: shuliang_han_ndkparameterpassing_DataProvider
* Method: sayHelloInc
* Signature: (Ljava/lang/String;)Ljava/lang/String;
*/
JNIEXPORT jstring JNICALL Java_shuliang_han_ndkparameterpassing_DataProvider_sayHelloInc
(JNIEnv *, jobject, jstring); /*
* Class: shuliang_han_ndkparameterpassing_DataProvider
* Method: intMethod
* Signature: ([I)[I
*/
JNIEXPORT jintArray JNICALL Java_shuliang_han_ndkparameterpassing_DataProvider_intMethod
(JNIEnv *, jobject, jintArray); #ifdef __cplusplus
}
#endif
#endif
(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
4. JNIEnv 详解
(1) JNIEnv的C/C++声明
struct _JNIEnv;
struct _JavaVM;
typedef const struct JNINativeInterface* C_JNIEnv; #if defined(__cplusplus) //为了兼容C 和 C++两种代码 使用该 宏加以区分
typedef _JNIEnv JNIEnv; //C++ 中的JNIEnv类型
typedef _JavaVM JavaVM;
#else
typedef const struct JNINativeInterface* JNIEnv;//C语言中的JNIEnv类型
typedef const struct JNIInvokeInterface* JavaVM;
#endif
(2) C语言中的JNIEnv
/*
* Table of interface function pointers.
*/
struct JNINativeInterface {
void* reserved0;
void* reserved1; ... ... jboolean (*CallStaticBooleanMethodV)(JNIEnv*, jclass, jmethodID,
va_list);
jboolean (*CallStaticBooleanMethodA)(JNIEnv*, jclass, jmethodID,
jvalue*);
jbyte (*CallStaticByteMethod)(JNIEnv*, jclass, jmethodID, ...);
jbyte (*CallStaticByteMethodV)(JNIEnv*, jclass, jmethodID, va_list); ... ... void* (*GetDirectBufferAddress)(JNIEnv*, jobject);
jlong (*GetDirectBufferCapacity)(JNIEnv*, jobject); /* added in JNI 1.6 */
jobjectRefType (*GetObjectRefType)(JNIEnv*, jobject);
};
(3) C++中的JNIEnv
/*
* C++ object wrapper.
*
* This is usually overlaid on a C struct whose first element is a
* JNINativeInterface*. We rely somewhat on compiler behavior.
*/
struct _JNIEnv {
/* do not rename this; it does not seem to be entirely opaque */
const struct JNINativeInterface* functions; #if defined(__cplusplus) jint GetVersion()
{ return functions->GetVersion(this); } jlong GetDirectBufferCapacity(jobject buf)
{ return functions->GetDirectBufferCapacity(this, buf); } /* added in JNI 1.6 */
jobjectRefType GetObjectRefType(jobject obj)
{ return functions->GetObjectRefType(this, obj); }
#endif /*__cplusplus*/
};
5. JNI方法命名规则(标准JNI规范)
6. JNI方法签名规则
Java类型 | 类型签名 |
boolean | Z |
byte | B |
char | C |
short | S |
int | I |
long | J |
float | F |
double | D |
类 | L全限定类名 |
数组 | [元素类型签名 |
四. Java调用JNI法与日志打印
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)
(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) {
//声明了一个字符串变量 rtn
char* rtn = NULL;
//找到Java中的String的Class对象
jclass clsstring = (*env)->FindClass(env, "java/lang/String");
//创建一个Java中的字符串 "GB2312"
jstring strencode = (*env)->NewStringUTF(env, "GB2312");
/*
* 获取String中定义的方法 getBytes(), 该方法的参数是 String类型的, 返回值是 byte[]数组
* "(Ljava/lang/String;)[B" 方法前面解析 :
* -- Ljava/lang/String; 表示参数是String字符串
* -- [B : 中括号表示这是一个数组, B代表byte类型, 返回值是一个byte数组
*/
jmethodID mid = (*env)->GetMethodID(env, clsstring, "getBytes",
"(Ljava/lang/String;)[B");
//调用Java中的getBytes方法, 传入参数介绍 参数②表示调用该方法的对象, 参数③表示方法id , 参数④表示方法参数
jbyteArray barr = (jbyteArray)(*env)->CallObjectMethod(env, jstr, mid,
strencode); // String .getByte("GB2312");
//获取数组的长度
jsize alen = (*env)->GetArrayLength(env, barr);
//获取数组中的所有的元素 , 存放在 jbyte*数组中
jbyte* ba = (*env)->GetByteArrayElements(env, barr, JNI_FALSE);
//将Java数组中所有元素拷贝到C的char*数组中, 注意C语言数组结尾要加一个 '\0'
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;
}
jclass clsstring = (*env)->FindClass(env, "java/lang/String");
b.创建Java字符串 : 使用 NewStringUTF 方法;
jstring strencode = (*env)->NewStringUTF(env, "GB2312");
c.获取String中的getBytes()方法 : 参数介绍 ① env 上下文环境 ② 完整的类路径 ③ 方法名 ④ 方法签名, 方法签名 Ljava/lang/String; 代表参数是String字符串, [B 中括号表示这是一个数组, B代表byte类型, 返回值是一个byte数组;
jmethodID mid = (*env)->GetMethodID(env, clsstring, "getBytes",
"(Ljava/lang/String;)[B");
d. 获取数组的长度 :
jsize alen = (*env)->GetArrayLength(env, barr);
e. 获取数组元素 : 获取数组中的所有的元素 , 存放在 jbyte*数组中;
jbyte* ba = (*env)->GetByteArrayElements(env, barr, JNI_FALSE);
f.数组拷贝: 将Java数组中所有元素拷贝到C的char*数组中, 注意C语言数组结尾要加一个 '\0';
if (alen > 0) {
rtn = (char*) malloc(alen + 1); //new char[alen+1]; "\0"
memcpy(rtn, ba, alen);
rtn[alen] = 0;
}
g.释放内存 :
(*env)->ReleaseByteArrayElements(env, barr, ba, 0); //释放内存
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));
}
-- 如果没有强转会出现下面的错误 : char *p = Jstring2CStr(env, str);
-- 将Jstring2CStr方法定义在主方法下面会出现下面错误 :
case R.id.sayHelloInc:
Toast.makeText(getApplicationContext(), dataProvider.sayHelloInc("Hello"), Toast.LENGTH_LONG).show();
break;
编译之后运行结果 :
5. 开发JNI程序流程
b. Java定义本地方法 : public native void LoginServer(String address, String user, String pwd);
注意跨语言字符串转换: JNI方法中, 要将Java的String字符串转为C中的char*字符串;
首先验证C码农提供的代码是否可用 : 验证该api是否可用, 在一个 int main() 函数中进行测试, 根据该测试代码查看方法执行相关的情况;
6. 数组参数处理
获取数组长度方法 : jni中定义 - jsize (*GetArrayLength)(JNIEnv*, jarray);
创建数组相关方法 :
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*);
C语言代码 :
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; }
case R.id.intMethod:
int[] array = {1, 2, 3, 4, 5};
dataProvider.intMethod(array);
break;
执行结果 : 上面的那种LogCat竟然启动失败, 只能将就着用这个了;
7. 本程序源码
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent" > <Button
android:id="@+id/add"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="调用 add 本地 方法"
android:onClick="onClick"/> <Button
android:id="@+id/sayHelloInc"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="调用 sayHelloInc 本地 方法"
android:onClick="onClick"/> <Button
android:id="@+id/intMethod"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="调用 intMethod 本地 方法"
android:onClick="onClick"/> </LinearLayout>
Java源码 :
package shuliang.han.ndkparameterpassing; import android.app.Activity;
import android.os.Bundle;
import android.view.View;
import android.widget.Toast; public class MainActivity extends Activity { static{
System.loadLibrary("DataProvider");
} DataProvider dataProvider;
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
dataProvider = new DataProvider();
} public void onClick(View view) { int id = view.getId(); switch (id) {
case R.id.add:
int result = dataProvider.add(1, 2);
Toast.makeText(getApplicationContext(), "the add result : " + result, Toast.LENGTH_LONG).show();
break; case R.id.sayHelloInc:
Toast.makeText(getApplicationContext(), dataProvider.sayHelloInc("Hello"), Toast.LENGTH_LONG).show();
break; case R.id.intMethod:
int[] array = {1, 2, 3, 4, 5};
dataProvider.intMethod(array);
break; default:
break;
}
} }
--DataProvider源码 :
package shuliang.han.ndkparameterpassing; public class DataProvider { //将Java中的两个int值 传给C语言, 进行相加后, 返回java语言 shuliang.han.ndkparameterpassing.DataProvider
public native int add(int x, int y); //将Java字符串传递给C语言, C语言处理字符串之后, 将处理结果返回给java
public native String sayHelloInc(String s); //将java中的int数组传递给C语言, C语言为每个元素加10, 返回给Java
public native int[] intMethod(int[] nums); }
JNI相关源码 :
LOCAL_PATH := $(call my-dir) include $(CLEAR_VARS) LOCAL_MODULE := DataProvider
LOCAL_SRC_FILES := DataProvider.c
#增加log函数对应的log库
LOCAL_LDLIBS += -llog include $(BUILD_SHARED_LIBRARY)
--DataProvider.c 主程序源码 :
#include <jni.h>
#include <string.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中的jstring, 转化为c的一个字符数组
char* Jstring2CStr(JNIEnv* env, jstring jstr) {
//声明了一个字符串变量 rtn
char* rtn = NULL;
//找到Java中的String的Class对象
jclass clsstring = (*env)->FindClass(env, "java/lang/String");
//创建一个Java中的字符串 "GB2312"
jstring strencode = (*env)->NewStringUTF(env, "GB2312");
/*
* 获取String中定义的方法 getBytes(), 该方法的参数是 String类型的, 返回值是 byte[]数组
* "(Ljava/lang/String;)[B" 方法前面解析 :
* -- Ljava/lang/String; 表示参数是String字符串
* -- [B : 中括号表示这是一个数组, B代表byte类型, 返回值是一个byte数组
*/
jmethodID mid = (*env)->GetMethodID(env, clsstring, "getBytes",
"(Ljava/lang/String;)[B");
//调用Java中的getBytes方法, 传入参数介绍 参数②表示调用该方法的对象, 参数③表示方法id , 参数④表示方法参数
jbyteArray barr = (jbyteArray)(*env)->CallObjectMethod(env, jstr, mid,
strencode); // String .getByte("GB2312");
//获取数组的长度
jsize alen = (*env)->GetArrayLength(env, barr);
//获取数组中的所有的元素 , 存放在 jbyte*数组中
jbyte* ba = (*env)->GetByteArrayElements(env, barr, JNI_FALSE);
//将Java数组中所有元素拷贝到C的char*数组中, 注意C语言数组结尾要加一个 '\0'
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;
} //方法签名, 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_log : x = %d , y = %d" , x , y);
return x + y;
} 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));
} 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; }
8. 上传代码到GitHub
创建新项目 : han1202012/NDKParameterPassing ;
-- SSH地址 : git@github.com:han1202012/NDKParameterPassing.git ;
-- HTTP地址 : https://github.com/han1202012/NDKParameterPassing.git ;
五. C语言代码回调Java方法
1. C代码回调Java方法的流程
(1) 找到java对应的Class
//DataProvider完整类名 shulaing.han.ndk_callback.DataProvider
char* classname = "shulaing/han/ndk_callback/DataProvider"; jclass dpclazz = (*env)->FindClass(env, classname);
(2) 找到要调用的方法的methodID
//参数介绍 : 第二个参数是Class对象, 第三个参数是方法名,第四个参数是方法的签名, 获取到调用的method
jmethodID methodID = (*env)->GetMethodID(env, dpclazz, "Add", "(II)I");
jmethodID GetStaticMethodID(jclass clazz, const char* name, const char* sig)
{ return functions->GetStaticMethodID(this, clazz, name, sig); }
(3) 在C语言中调用相应方法
jmethodID (*GetMethodID)(JNIEnv*, jclass, const char*, const char*); jobject (*CallObjectMethod)(JNIEnv*, jobject, jmethodID, ...);
jobject (*CallObjectMethodV)(JNIEnv*, jobject, jmethodID, va_list);
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);
jlong (*CallLongMethodA)(JNIEnv*, jobject, jmethodID, jvalue*);
jfloat (*CallFloatMethod)(JNIEnv*, jobject, jmethodID, ...) __NDK_FPABI__;
jfloat (*CallFloatMethodV)(JNIEnv*, jobject, jmethodID, va_list) __NDK_FPABI__;
jfloat (*CallFloatMethodA)(JNIEnv*, jobject, jmethodID, jvalue*) __NDK_FPABI__;
jdouble (*CallDoubleMethod)(JNIEnv*, jobject, jmethodID, ...) __NDK_FPABI__;
jdouble (*CallDoubleMethodV)(JNIEnv*, jobject, jmethodID, va_list) __NDK_FPABI__;
jdouble (*CallDoubleMethodA)(JNIEnv*, jobject, jmethodID, jvalue*) __NDK_FPABI__;
void (*CallVoidMethod)(JNIEnv*, jobject, jmethodID, ...);
void (*CallVoidMethodV)(JNIEnv*, jobject, jmethodID, va_list);
void (*CallVoidMethodA)(JNIEnv*, jobject, jmethodID, jvalue*);
静态方法 : CallStaticTypeMethod, 其中的Type随着返回值类型不同而改变;
2. 一些基本代码编写
package shulaing.han.ndk_callback; public class DataProvider { public native void callCcode(); //C调用java中空方法 shulaing.han.ndk_callback.DataProvider public void helloFromJava(){
System.out.println("hello from java");
} //C调用java中的带两个int参数的方法
public int Add(int x,int y){
return x + y;
} //C调用java中参数为string的方法
public void printString(String s){
System.out.println(s);
} }
生成头文件 : 进入 bin/classed目录, 使用 javah shulaing.han.ndk_callback.DataProvider 命令, 可以在bin/classes下生成头文件;
/* DO NOT EDIT THIS FILE - it is machine generated */
#include <jni.h>
/* Header for class shulaing_han_ndk_callback_DataProvider */ #ifndef _Included_shulaing_han_ndk_callback_DataProvider
#define _Included_shulaing_han_ndk_callback_DataProvider
#ifdef __cplusplus
extern "C" {
#endif
/*
* Class: shulaing_han_ndk_callback_DataProvider
* Method: callCcode
* Signature: ()V
*/
JNIEXPORT void JNICALL Java_shulaing_han_ndk_1callback_DataProvider_callCcode
(JNIEnv *, jobject); #ifdef __cplusplus
}
#endif
#endif
编写Android.mk文件 : 注意将LogCat日志输出系统动态库加入;
LOCAL_PATH := $(call my-dir) include $(CLEAR_VARS) LOCAL_MODULE := jni
LOCAL_SRC_FILES := jni.c
#增加log函数对应的log库
LOCAL_LDLIBS += -llog include $(BUILD_SHARED_LIBRARY)
编写jni的C代码 : 注意加入LogCat相关导入的包;
#include "shulaing_han_ndk_callback_DataProvider.h"
#include <string.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__)
3. C中回调Java的void返回值方法
$ javap -s shulaing.han.ndk_callback.DataProvider
Compiled from "DataProvider.java"
public class shulaing.han.ndk_callback.DataProvider extends java.lang.Object{
public shulaing.han.ndk_callback.DataProvider();
Signature: ()V
public native void callCcode();
Signature: ()V
public void helloFromJava();
Signature: ()V
public int Add(int, int);
Signature: (II)I
public void printString(java.lang.String);
Signature: (Ljava/lang/String;)V
}
截图 :
jmethodID (*GetMethodID)(JNIEnv*, jclass, const char*, const char*); jobject (*CallObjectMethod)(JNIEnv*, jobject, jmethodID, ...);
jobject (*CallObjectMethodV)(JNIEnv*, jobject, jmethodID, va_list);
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);
jlong (*CallLongMethodA)(JNIEnv*, jobject, jmethodID, jvalue*);
jfloat (*CallFloatMethod)(JNIEnv*, jobject, jmethodID, ...) __NDK_FPABI__;
jfloat (*CallFloatMethodV)(JNIEnv*, jobject, jmethodID, va_list) __NDK_FPABI__;
jfloat (*CallFloatMethodA)(JNIEnv*, jobject, jmethodID, jvalue*) __NDK_FPABI__;
jdouble (*CallDoubleMethod)(JNIEnv*, jobject, jmethodID, ...) __NDK_FPABI__;
jdouble (*CallDoubleMethodV)(JNIEnv*, jobject, jmethodID, va_list) __NDK_FPABI__;
jdouble (*CallDoubleMethodA)(JNIEnv*, jobject, jmethodID, jvalue*) __NDK_FPABI__;
void (*CallVoidMethod)(JNIEnv*, jobject, jmethodID, ...);
void (*CallVoidMethodV)(JNIEnv*, jobject, jmethodID, va_list);
void (*CallVoidMethodA)(JNIEnv*, jobject, jmethodID, jvalue*);
C语言代码 :
#include "shulaing_han_ndk_callback_DataProvider.h"
#include <string.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__) JNIEXPORT void JNICALL Java_shulaing_han_ndk_1callback_DataProvider_callCcode
(JNIEnv * env, jobject obj)
{
//调用DataProvider对象中的helloFromJava()方法
//获取到某个对象, 获取对象中的方法, 调用获取到的方法
LOGI("in code");
//DataProvider完整类名 shulaing.han.ndk_callback.DataProvider
char* classname = "shulaing/han/ndk_callback/DataProvider"; jclass dpclazz = (*env)->FindClass(env, classname);
if(dpclazz == 0)
LOGI("class not find !!!");
else
LOGI("class find !!!"); //参数介绍 : 第二个参数是Class对象, 第三个参数是方法名,第四个参数是方法的签名, 获取到调用的method
jmethodID methodID = (*env)->GetMethodID(env, dpclazz, "helloFromJava", "()V");
if(methodID == 0)
LOGI("method not find !!!");
else
LOGI("method find !!!"); /*
* 调用方法 void (*CallVoidMethod)(JNIEnv*, jobject, jmethodID, ...);
* 参数介绍 : 后面的 ... 是可变参数, 如果该返回值void的方法有参数, 就将参数按照次序排列
*/
LOGI("before call method");
(*env)->CallVoidMethod(env, obj, methodID);
LOGI("after call method"); }
Java代码 :
<LinearLayout 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:orientation="vertical"
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/call_void_method"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:onClick="onClick"
android:text="C语言回调Java中的空方法" /> </LinearLayout>
--MainActivity代码 :
package shulaing.han.ndk_callback; import android.app.Activity;
import android.os.Bundle;
import android.view.View; public class MainActivity extends Activity { static{
System.loadLibrary("jni");
}
DataProvider dp; @Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
dp = new DataProvider();
} public void onClick(View view) {
int id = view.getId();
switch (id) {
case R.id.call_void_method:
dp.callCcode();
break; default:
break;
}
} }
执行结果 :
.
4. C代码回调Java中带String参数的方法
public native void callCcode();
public native void callCcode1();
public native void callCcode2();
/* DO NOT EDIT THIS FILE - it is machine generated */
#include <jni.h>
/* Header for class shulaing_han_ndk_callback_DataProvider */ #ifndef _Included_shulaing_han_ndk_callback_DataProvider
#define _Included_shulaing_han_ndk_callback_DataProvider
#ifdef __cplusplus
extern "C" {
#endif
/*
* Class: shulaing_han_ndk_callback_DataProvider
* Method: callCcode
* Signature: ()V
*/
JNIEXPORT void JNICALL Java_shulaing_han_ndk_1callback_DataProvider_callCcode
(JNIEnv *, jobject); /*
* Class: shulaing_han_ndk_callback_DataProvider
* Method: callCcode1
* Signature: ()V
*/
JNIEXPORT void JNICALL Java_shulaing_han_ndk_1callback_DataProvider_callCcode1
(JNIEnv *, jobject); /*
* Class: shulaing_han_ndk_callback_DataProvider
* Method: callCcode2
* Signature: ()V
*/
JNIEXPORT void JNICALL Java_shulaing_han_ndk_1callback_DataProvider_callCcode2
(JNIEnv *, jobject); #ifdef __cplusplus
}
#endif
#endif
JNIEXPORT void JNICALL Java_shulaing_han_ndk_1callback_DataProvider_callCcode1
(JNIEnv *env, jobject obj)
{
//调用DataProvider对象中的helloFromJava()方法
//获取到某个对象, 获取对象中的方法, 调用获取到的方法
LOGI("in code");
//DataProvider完整类名 shulaing.han.ndk_callback.DataProvider
char* classname = "shulaing/han/ndk_callback/DataProvider"; jclass dpclazz = (*env)->FindClass(env, classname);
if(dpclazz == 0)
LOGI("class not find !!!");
else
LOGI("class find !!!"); //参数介绍 : 第二个参数是Class对象, 第三个参数是方法名,第四个参数是方法的签名, 获取到调用的method
jmethodID methodID = (*env)->GetMethodID(env, dpclazz, "printString", "(Ljava/lang/String;)V");
if(methodID == 0)
LOGI("method not find !!!");
else
LOGI("method find !!!"); /*
* 调用方法 void (*CallVoidMethod)(JNIEnv*, jobject, jmethodID, ...);
* 参数介绍 : 后面的 ... 是可变参数, 如果该返回值void的方法有参数, 就将参数按照次序排列
*/
LOGI("before call method");
(*env)->CallVoidMethod(env, obj, methodID, (*env)->NewStringUTF(env, "printString method callback success!!"));
LOGI("after call method");
}
执行后的结果 :
5. C代码中回调带两个int类型的参数的方法
JNIEXPORT void JNICALL Java_shulaing_han_ndk_1callback_DataProvider_callCcode2
(JNIEnv *env, jobject obj)
{
//调用DataProvider对象中的helloFromJava()方法
//获取到某个对象, 获取对象中的方法, 调用获取到的方法
LOGI("in code");
//DataProvider完整类名 shulaing.han.ndk_callback.DataProvider
char* classname = "shulaing/han/ndk_callback/DataProvider"; jclass dpclazz = (*env)->FindClass(env, classname);
if(dpclazz == 0)
LOGI("class not find !!!");
else
LOGI("class find !!!"); //参数介绍 : 第二个参数是Class对象, 第三个参数是方法名,第四个参数是方法的签名, 获取到调用的method
jmethodID methodID = (*env)->GetMethodID(env, dpclazz, "Add", "(II)I");
if(methodID == 0)
LOGI("method not find !!!");
else
LOGI("method find !!!"); /*
* 调用方法 void (*CallVoidMethod)(JNIEnv*, jobject, jmethodID, ...);
* 参数介绍 : 后面的 ... 是可变参数, 如果该返回值void的方法有参数, 就将参数按照次序排列
*/
LOGI("before call method");
(*env)->CallIntMethod(env, obj, methodID, 3, 5);
LOGI("after call method"); }
Java代码 :
case R.id.call_int_parameter_method:
dp.callCcode2();
break;
6. 完整源码
package shulaing.han.ndk_callback; public class DataProvider { public native void callCcode();
public native void callCcode1();
public native void callCcode2(); //C调用java中空方法 shulaing.han.ndk_callback.DataProvider public void helloFromJava(){
System.out.println("hello from java");
} //C调用java中的带两个int参数的方法
public int Add(int x,int y){
System.out.println("the add result is : " + (x + y));
return x + y;
} //C调用java中参数为string的方法
public void printString(String s){
System.out.println("in java code :" + s);
} }
package shulaing.han.ndk_callback; import android.app.Activity;
import android.os.Bundle;
import android.view.View; public class MainActivity extends Activity { static{
System.loadLibrary("jni");
}
DataProvider dp; @Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
dp = new DataProvider();
} public void onClick(View view) {
int id = view.getId();
switch (id) {
case R.id.call_void_method:
dp.callCcode();
break; case R.id.call_string_parameter_method:
dp.callCcode1();
break; case R.id.call_int_parameter_method:
dp.callCcode2();
break; default:
break;
}
} }
<LinearLayout 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:orientation="vertical"
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/call_void_method"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:onClick="onClick"
android:text="C语言回调Java中的空方法" /> <Button
android:id="@+id/call_string_parameter_method"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:onClick="onClick"
android:text="C语言回调Java中的String参数方法" /> <Button
android:id="@+id/call_int_parameter_method"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:onClick="onClick"
android:text="C语言回调Java中的int参数方法" /> </LinearLayout>
/* DO NOT EDIT THIS FILE - it is machine generated */
#include <jni.h>
/* Header for class shulaing_han_ndk_callback_DataProvider */ #ifndef _Included_shulaing_han_ndk_callback_DataProvider
#define _Included_shulaing_han_ndk_callback_DataProvider
#ifdef __cplusplus
extern "C" {
#endif
/*
* Class: shulaing_han_ndk_callback_DataProvider
* Method: callCcode
* Signature: ()V
*/
JNIEXPORT void JNICALL Java_shulaing_han_ndk_1callback_DataProvider_callCcode
(JNIEnv *, jobject); /*
* Class: shulaing_han_ndk_callback_DataProvider
* Method: callCcode1
* Signature: ()V
*/
JNIEXPORT void JNICALL Java_shulaing_han_ndk_1callback_DataProvider_callCcode1
(JNIEnv *, jobject); /*
* Class: shulaing_han_ndk_callback_DataProvider
* Method: callCcode2
* Signature: ()V
*/
JNIEXPORT void JNICALL Java_shulaing_han_ndk_1callback_DataProvider_callCcode2
(JNIEnv *, jobject); #ifdef __cplusplus
}
#endif
#endif
LOCAL_PATH := $(call my-dir) include $(CLEAR_VARS) LOCAL_MODULE := jni
LOCAL_SRC_FILES := jni.c
#增加log函数对应的log库
LOCAL_LDLIBS += -llog include $(BUILD_SHARED_LIBRARY)
#include "shulaing_han_ndk_callback_DataProvider.h"
#include "first.h"
#include <string.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__) JNIEXPORT void JNICALL Java_shulaing_han_ndk_1callback_DataProvider_callCcode
(JNIEnv * env, jobject obj)
{
//调用DataProvider对象中的helloFromJava()方法
//获取到某个对象, 获取对象中的方法, 调用获取到的方法
LOGI("in code");
//DataProvider完整类名 shulaing.han.ndk_callback.DataProvider
char* classname = "shulaing/han/ndk_callback/DataProvider"; jclass dpclazz = (*env)->FindClass(env, classname);
if(dpclazz == 0)
LOGI("class not find !!!");
else
LOGI("class find !!!"); //参数介绍 : 第二个参数是Class对象, 第三个参数是方法名,第四个参数是方法的签名, 获取到调用的method
jmethodID methodID = (*env)->GetMethodID(env, dpclazz, "helloFromJava", "()V");
if(methodID == 0)
LOGI("method not find !!!");
else
LOGI("method find !!!"); /*
* 调用方法 void (*CallVoidMethod)(JNIEnv*, jobject, jmethodID, ...);
* 参数介绍 : 后面的 ... 是可变参数, 如果该返回值void的方法有参数, 就将参数按照次序排列
*/
LOGI("before call method");
(*env)->CallVoidMethod(env, obj, methodID);
LOGI("after call method"); } JNIEXPORT void JNICALL Java_shulaing_han_ndk_1callback_DataProvider_callCcode1
(JNIEnv *env, jobject obj)
{
//调用DataProvider对象中的helloFromJava()方法
//获取到某个对象, 获取对象中的方法, 调用获取到的方法
LOGI("in code");
//DataProvider完整类名 shulaing.han.ndk_callback.DataProvider
char* classname = "shulaing/han/ndk_callback/DataProvider"; jclass dpclazz = (*env)->FindClass(env, classname);
if(dpclazz == 0)
LOGI("class not find !!!");
else
LOGI("class find !!!"); //参数介绍 : 第二个参数是Class对象, 第三个参数是方法名,第四个参数是方法的签名, 获取到调用的method
jmethodID methodID = (*env)->GetMethodID(env, dpclazz, "printString", "(Ljava/lang/String;)V");
if(methodID == 0)
LOGI("method not find !!!");
else
LOGI("method find !!!"); /*
* 调用方法 void (*CallVoidMethod)(JNIEnv*, jobject, jmethodID, ...);
* 参数介绍 : 后面的 ... 是可变参数, 如果该返回值void的方法有参数, 就将参数按照次序排列
*/
LOGI("before call method");
(*env)->CallVoidMethod(env, obj, methodID, (*env)->NewStringUTF(env, "printString method callback success!!"));
LOGI("after call method");
} /*
* 实际开发的情况
* C代码工程师给我们 first.h first.c , 我们只需要将first.h引入, 然后就可以使用其中的方法了
*/
JNIEXPORT void JNICALL Java_shulaing_han_ndk_1callback_DataProvider_callCcode2
(JNIEnv *env, jobject obj)
{
//调用DataProvider对象中的helloFromJava()方法
//获取到某个对象, 获取对象中的方法, 调用获取到的方法
LOGI("in code");
//DataProvider完整类名 shulaing.han.ndk_callback.DataProvider
char* classname = "shulaing/han/ndk_callback/DataProvider"; jclass dpclazz = (*env)->FindClass(env, classname);
if(dpclazz == 0)
LOGI("class not find !!!");
else
LOGI("class find !!!"); //参数介绍 : 第二个参数是Class对象, 第三个参数是方法名,第四个参数是方法的签名, 获取到调用的method
jmethodID methodID = (*env)->GetMethodID(env, dpclazz, "Add", "(II)I");
if(methodID == 0)
LOGI("method not find !!!");
else
LOGI("method find !!!"); /*
* 调用方法 void (*CallVoidMethod)(JNIEnv*, jobject, jmethodID, ...);
* 参数介绍 : 后面的 ... 是可变参数, 如果该返回值void的方法有参数, 就将参数按照次序排列
*/
LOGI("before call method");
(*env)->CallIntMethod(env, obj, methodID, 3, 5);
LOGI("after call method"); }
7. 将程序上传到GitHub中
六. 实际开发中的环境
#ifndef FIRST_H
#define FIRST_H extern int first(int x, int y); #endif /* FIRST_H */
first.c源码 :
#include "first.h" int first(int x, int y)
{
return x + y;
}
在签名函数中, 直接调用 first()方法即可;
七 分析Log日志系统框架的JNI代码
1. 分析Log.java源码
package android.util; import com.android.internal.os.RuntimeInit; import java.io.PrintWriter;
import java.io.StringWriter;
public final class Log { ... ... //打印日志
public static int d(String tag, String msg) {
return println_native(LOG_ID_MAIN, DEBUG, tag, msg);
} //打印日志和异常
public static int d(String tag, String msg, Throwable tr) {
return println_native(LOG_ID_MAIN, DEBUG, tag, msg + '\n' + getStackTraceString(tr));
} //打印日志
public static int i(String tag, String msg) {
return println_native(LOG_ID_MAIN, INFO, tag, msg);
} ... ... //声明native方法
public static native boolean isLoggable(String tag, int level); ... ... /** @hide */ public static final int LOG_ID_MAIN = 0;
/** @hide */ public static final int LOG_ID_RADIO = 1;
/** @hide */ public static final int LOG_ID_EVENTS = 2;
/** @hide */ public static final int LOG_ID_SYSTEM = 3; //声明native方法
/** @hide */ public static native int println_native(int bufID,
int priority, String tag, String msg);
}
2. 分析Log系统JNI层源码
#define LOG_NAMESPACE "log.tag."
#define LOG_TAG "Log_println" #include <assert.h>
#include <cutils/properties.h>
#include <utils/Log.h>
#include <utils/String8.h> #include "jni.h"
#include "utils/misc.h"
#include "android_runtime/AndroidRuntime.h" ... ... namespace android { struct levels_t {
jint verbose;
jint debug;
jint info;
jint warn;
jint error;
jint assert;
};
static levels_t levels; static int toLevel(const char* value)
{
switch (value[0]) {
case 'V': return levels.verbose;
case 'D': return levels.debug;
case 'I': return levels.info;
case 'W': return levels.warn;
case 'E': return levels.error;
case 'A': return levels.assert;
case 'S': return -1; // SUPPRESS
}
return levels.info;
} /*
实现Java层声明的 isLoggable 方法, 注意方法名不符合标准JNI规范
标准的JNI规范方法名应该是 Java_包名_类名_方法名
其中传入了JNIEnv 和 jobject 参数, JNIEnv参数是Java运行环境, 可以与JVM进行交互
jobject参数是包含Native方法的Java类对象
该方法中可以通过JNIEnv调用本地库进行函数处理, 最后返回给Java层函数
*/
static jboolean android_util_Log_isLoggable(JNIEnv* env, jobject clazz, jstring tag, jint level)
{
#ifndef HAVE_ANDROID_OS
return false;
#else /* HAVE_ANDROID_OS */
int len;
char key[PROPERTY_KEY_MAX];
char buf[PROPERTY_VALUE_MAX]; if (tag == NULL) {
return false;
} jboolean result = false; //调用了JNI函数
const char* chars = env->GetStringUTFChars(tag, NULL); if ((strlen(chars)+sizeof(LOG_NAMESPACE)) > PROPERTY_KEY_MAX) {
jclass clazz = env->FindClass("java/lang/IllegalArgumentException");
char buf2[200];
snprintf(buf2, sizeof(buf2), "Log tag \"%s\" exceeds limit of %d characters\n",
chars, PROPERTY_KEY_MAX - sizeof(LOG_NAMESPACE)); // release the chars!
env->ReleaseStringUTFChars(tag, chars); env->ThrowNew(clazz, buf2);
return false;
} else {
strncpy(key, LOG_NAMESPACE, sizeof(LOG_NAMESPACE)-1);
strcpy(key + sizeof(LOG_NAMESPACE) - 1, chars);
} env->ReleaseStringUTFChars(tag, chars); len = property_get(key, buf, "");
int logLevel = toLevel(buf);
return (logLevel >= 0 && level >= logLevel) ? true : false;
#endif /* HAVE_ANDROID_OS */
} /*
* In class android.util.Log:
* public static native int println_native(int buffer, int priority, String tag, String msg)
*/
static jint android_util_Log_println_native(JNIEnv* env, jobject clazz,
jint bufID, jint priority, jstring tagObj, jstring msgObj)
{
const char* tag = NULL;
const char* msg = NULL; if (msgObj == NULL) {
jclass npeClazz; npeClazz = env->FindClass("java/lang/NullPointerException");
assert(npeClazz != NULL); env->ThrowNew(npeClazz, "println needs a message");
return -1;
} if (bufID < 0 || bufID >= LOG_ID_MAX) {
jclass npeClazz; npeClazz = env->FindClass("java/lang/NullPointerException");
assert(npeClazz != NULL); env->ThrowNew(npeClazz, "bad bufID");
return -1;
} if (tagObj != NULL)
tag = env->GetStringUTFChars(tagObj, NULL); //调用JNI函数
msg = env->GetStringUTFChars(msgObj, NULL); int res = __android_log_buf_write(bufID, (android_LogPriority)priority, tag, msg); if (tag != NULL)
env->ReleaseStringUTFChars(tagObj, tag); //调用JNI函数释放资源
env->ReleaseStringUTFChars(msgObj, msg); //调用JNI函数释放资源 return res;
} /*
* JNI registration. JNI方法注册
*/
static JNINativeMethod gMethods[] = {
/* name, signature, funcPtr */
{ "isLoggable", "(Ljava/lang/String;I)Z", (void*) android_util_Log_isLoggable },
{ "println_native", "(IILjava/lang/String;Ljava/lang/String;)I", (void*) android_util_Log_println_native },
}; int register_android_util_Log(JNIEnv* env)
{
jclass clazz = env->FindClass("android/util/Log"); if (clazz == NULL) {
LOGE("Can't find android/util/Log");
return -1;
} levels.verbose = env->GetStaticIntField(clazz, env->GetStaticFieldID(clazz, "VERBOSE", "I"));
levels.debug = env->GetStaticIntField(clazz, env->GetStaticFieldID(clazz, "DEBUG", "I"));
levels.info = env->GetStaticIntField(clazz, env->GetStaticFieldID(clazz, "INFO", "I"));
levels.warn = env->GetStaticIntField(clazz, env->GetStaticFieldID(clazz, "WARN", "I"));
levels.error = env->GetStaticIntField(clazz, env->GetStaticFieldID(clazz, "ERROR", "I"));
levels.assert = env->GetStaticIntField(clazz, env->GetStaticFieldID(clazz, "ASSERT", "I")); return AndroidRuntime::registerNativeMethods(env, "android/util/Log", gMethods, NELEM(gMethods));
} }; // namespace android
3. 声明JNI 与 Native 方法的映射关系
typedef struct {
const char* name; //Java层Native函数方法名
const char* signature; //Java层Native函数的签名
void* fnPtr; //JNI层实现的方法
} JNINativeMethod;
.
/*
* JNI registration.
*/
static JNINativeMethod gMethods[] = {
/* name, signature, funcPtr */
{ "isLoggable", "(Ljava/lang/String;I)Z", (void*) android_util_Log_isLoggable },
{ "println_native", "(IILjava/lang/String;Ljava/lang/String;)I", (void*) android_util_Log_println_native },
};
JNINativeMethod结构体作用 : JNINativeMethod是一个结构体类型, 声明了Native方法 与 JNI方法 的映射关系;
4. 注册JNI方法到虚拟机中
int register_android_util_Log(JNIEnv* env)
{
jclass clazz = env->FindClass("android/util/Log"); if (clazz == NULL) {
LOGE("Can't find android/util/Log");
return -1;
} levels.verbose = env->GetStaticIntField(clazz, env->GetStaticFieldID(clazz, "VERBOSE", "I"));
levels.debug = env->GetStaticIntField(clazz, env->GetStaticFieldID(clazz, "DEBUG", "I"));
levels.info = env->GetStaticIntField(clazz, env->GetStaticFieldID(clazz, "INFO", "I"));
levels.warn = env->GetStaticIntField(clazz, env->GetStaticFieldID(clazz, "WARN", "I"));
levels.error = env->GetStaticIntField(clazz, env->GetStaticFieldID(clazz, "ERROR", "I"));
levels.assert = env->GetStaticIntField(clazz, env->GetStaticFieldID(clazz, "ASSERT", "I")); return AndroidRuntime::registerNativeMethods(env, "android/util/Log", gMethods, NELEM(gMethods));
} }; // namespace android
核心方法 : 该函数调用了 AndroidRuntime::registerNativeMethods(env, "android/util/Log", gMethods, NELEM(gMethods)) 方法注册JNI方法;
5. 解析registerNativeMethod函数
/*
* Register native methods using JNI.
*/
/*static*/ int AndroidRuntime::registerNativeMethods(JNIEnv* env,
const char* className, const JNINativeMethod* gMethods, int numMethods)
{
return jniRegisterNativeMethods(env, className, gMethods, numMethods);
}
registerNativeMethods 方法只是对 jniRegisterNativeMethods 方法的封装, 在JNIHelp.h中找到该方法的声明:
/*
* Register one or more native methods with a particular class.
*/
int jniRegisterNativeMethods(C_JNIEnv* env, const char* className,
const JNINativeMethod* gMethods, int numMethods);
在JNIHelp.c 中找到该方法的实现 : 最终方法中调用了 JNIEnv 的RegisterNatives 函数, 将gMethods中存放的JNINativeMethod结构体(存放Native方法 与 JNI方法关联信息) 传递到java虚拟机;
/*
* Register native JNI-callable methods.
*
* "className" looks like "java/lang/String".
*/
int jniRegisterNativeMethods(JNIEnv* env, const char* className,
const JNINativeMethod* gMethods, int numMethods)
{
jclass clazz; LOGV("Registering %s natives\n", className);
clazz = (*env)->FindClass(env, className);
if (clazz == NULL) {
LOGE("Native registration unable to find class '%s'\n", className);
return -1;
} int result = 0;
if ((*env)->RegisterNatives(env, clazz, gMethods, numMethods) < 0) {
LOGE("RegisterNatives failed for '%s'\n", className);
result = -1;
} (*env)->DeleteLocalRef(env, clazz);
return result;
}
6. JNI的规范
Android 开发 之 JNI入门 - NDK从入门到精通的更多相关文章
- 【转】 Android 开发 之 JNI入门 - NDK从入门到精通
原文网址:http://blog.csdn.net/shulianghan/article/details/18964835 NDK项目源码地址 : -- 第一个JNI示例程序下载 : GitHub ...
- 【Android 应用开发】Android 开发 之 JNI入门 - NDK从入门到精通
NDK项目源码地址 : -- 第一个JNI示例程序下载 : GitHub - https://github.com/han1202012/NDKHelloworld.git -- Java传递参数给C ...
- Android Studio使用JNI和NDK进行开发
想要学习一下在Android Studio中进行JNI的开发,文章挺多的,但是几乎没有一个完整的说明的,中间总是有一两步漏掉.分享技术就应该完整的让读者学会,藏着掖着不是君子所为.对于那些故意含糊过去 ...
- Android开发学习之路--NDK、JNI之初体验
好久没有更新博客了,最近一直在看一个仿微信项目,然后看源码并自己实现下,相信经过这个项目可以让自己了解一个项目中的代码以及种种需要注意的事项.不知不觉中博客已经快要40w访问量,而且排名也即将突破30 ...
- Android Studio通过JNI调用NDK程序
NDK开发,其实是为了项目需要调用底层的一些C/C++的一些东西:另外就是为了效率更加高些,安全性更高. 如果你在Eclipse+ADT下开发过NDK就能体会到要么是配置NDK还要下载Cygwin,配 ...
- 《Android开发艺术探索》读书笔记 (13) 第13章 综合技术、第14章 JNI和NDK编程、第15章 Android性能优化
第13章 综合技术 13.1 使用CrashHandler来获取应用的Crash信息 (1)应用发生Crash在所难免,但是如何采集crash信息以供后续开发处理这类问题呢?利用Thread类的set ...
- Android 开发一定要看的15个实战项目
前言: 虽说网上有太多的Android课程,但是大多都是视频,有Android在线开发环境的几乎没有,但是对于学习Android的人来说拥有在线的Android开发环境是非常好的,可以随时动手操作学习 ...
- Android开发学习之路--Android Studio cmake编译ffmpeg
最新的android studio2.2引入了cmake可以很好地实现ndk的编写.这里使用最新的方式,对于以前的android下的ndk编译什么的可以参考之前的文章:Android开发学习之路– ...
- Android开发环境——Eclipse ADT相关内容汇总
Android开发环境将分为SDK相关内容.Eclipse ADT相关内容.模拟器AVD相关内容.调试器DDMS相关内容.日志LogCat相关内容.连接驱动ADB相关内容.内存泄露检测工具MAT相关 ...
随机推荐
- WARNING: IPv4 forwarding is disabled. Networking will not work.
1:Test environment [root@docker-node1 ~]# cat /etc/redhat-release CentOS Linux release 7.5.1804 (Cor ...
- QueryableHelper
using System; using System.Collections.Generic; using System.Linq; using System.Linq.Expressions; us ...
- day 18 类与类之间的关系
类与类之间的关系 在我们的世界中事物和事物之间总会有一些联系. 在面向对象中,类和类之间也可以产生相关的关系 1.依赖关系 执行某个动作的时候. 需要xxx来帮助你完成这个操作, ...
- python教程(二)·循环语句
计算机程序中常常需要重复执行某些语句,我们总不能将同一语句写上百遍吧?所以在python中,当然其它计算机语言也是,有一种语句可以重复执行相同的操作,这种语句就是 "循环语句",而 ...
- Python学习笔记一:第一个Python程序,变量,字符编码与二进制,用户交互程序
第一个python程序 Windows:设置环境变量,X:\pthonxxx,xxx是版本号 在命令提示符下 输入python,进入解释器 >>>print(“Hello World ...
- Python 爬虫 七夕福利
祝大家七夕愉快 妹子图 import requests from lxml import etree import os def headers(refere):#图片的下载可能和头部的referer ...
- java 用接口实现加减乘除计算器
class Test{ public static void main(String[] args) { fun i=new fun(); jiafa s1=new jiafa(); jianfa s ...
- HTTP报文中的100状态码
HTTP状态码(status codes)是HTTP协议中,响应报文的起始行中包含的一种服务器用于向客户端说明操作状态的三位数字.例如在一个正常的GET请求完成后,服务器会向客户端返回 HTTP/ O ...
- tensorflow 教程 文本分类 IMDB电影评论
昨天配置了tensorflow的gpu版本,今天开始简单的使用一下 主要是看了一下tensorflow的tutorial 里面的 IMDB 电影评论二分类这个教程 教程里面主要包括了一下几个内容:下载 ...
- See You Again——我最后的汇编程序
汇编语言:课程设计2 前言 由于本人水平不够,这里的课程设计2的程序实现并没有像王爽书中所说的那样可以不依赖于操作系统运行. 这里的程序依然要在dos下运行,而且没有实现引导现有操作系统的功能. 该程 ...