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 目录, 使用 javahshuliang.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)
--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 add ./* , 将目录中所有文件添加;
-- 查看状态 : git status ;
-- 提交到远程GitHub仓库 : git push -u origin master ;
3. 项目讲解
(1) Android.mk文件讲解
LOCAL_PATH := $(call my-dir) include $(CLEAR_VARS) LOCAL_MODULE := hello LOCAL_SRC_FILES := hello.c include $(BUILD_SHARED_LIBRARY)
(2) 自动生成方法签名
/* 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*);
#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);
/* * 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; }
相关推荐
务必确认机身编号与文件名机编一致,如不一致,请勿下载 机身编号一般在机子背面的贴纸上 升级方法: 1、下载数据,压缩包解压,将“Haier638Upgrade.bin”文件拷贝到U盘根目录下(U盘要求使用FAT32格式,建议4G-8G的品牌U盘,刷机成功率会高) 2、电视关机拔下电源,插入U盘,按住机身按键板上的“菜单”键不放,插电开机,直到LED灯开始闪表示升级正在进行,升级成功后机器会自动重起。 3、重启之后,重新交流上电,升级完成。 注意: 1、升级到结束,大约需要8-30分钟,中途绝对不能断电 2、升级重启第一次进入系统,请等完全正常进入开机桌面之后,才能拨下U盘
**C语言电脑系统测试项目** 本项目是一个基于C语言的实用工具,旨在为您的电脑系统提供全面的性能测试与评估。通过执行一系列精心设计的测试用例,该工具能够评估您的电脑在处理器速度、内存管理、磁盘性能以及网络连接等方面的表现。 项目的核心功能包括: 1. **处理器性能测试**:通过执行复杂的算法和计算任务,评估处理器的运算速度和效率。 2. **内存测试**:检查内存分配、读写速度和稳定性,确保系统的内存管理达到最佳状态。 3. **磁盘性能测试**:评估硬盘或固态硬盘的读写速度、I/O性能和文件系统效率。 4. **网络性能测试**:测量网络连接的速度和稳定性,包括上传和下载速度以及延迟。 此外,该项目还提供了详细的测试报告功能,帮助您全面了解系统的优势和潜在瓶颈。测试结果以直观易懂的图表和数字形式呈现,便于分析和解读。 此项目采用模块化设计,方便您进行二次开发和定制,以满足特定需求。无论是硬件评测爱好者还是系统管理员,都能从该项目中受益良多。
Python库是一组预先编写的代码模块,旨在帮助开发者实现特定的编程任务,无需从零开始编写代码。这些库可以包括各种功能,如数学运算、文件操作、数据分析和网络编程等。Python社区提供了大量的第三方库,如NumPy、Pandas和Requests,极大地丰富了Python的应用领域,从数据科学到Web开发。Python库的丰富性是Python成为最受欢迎的编程语言之一的关键原因之一。这些库不仅为初学者提供了快速入门的途径,而且为经验丰富的开发者提供了强大的工具,以高效率、高质量地完成复杂任务。例如,Matplotlib和Seaborn库在数据可视化领域内非常受欢迎,它们提供了广泛的工具和技术,可以创建高度定制化的图表和图形,帮助数据科学家和分析师在数据探索和结果展示中更有效地传达信息。
强化学习的主要算法:包括Q-learning、SARSA、DQN、A3C、TRPO、PPO和SAC等。这些算法各有特点,适用于不同的场景和任务。例如,Q-learning和SARSA是基于值函数的强化学习算法,旨在学习最优策略以最大化累积奖励;而DQN则是深度强化学习算法,使用神经网络来估计值函数,并通过反向传播算法更新网络参数。 强化学习在多个领域具有广泛应用。在自动驾驶系统中,强化学习可以帮助车辆感知周围环境并做出决策,实现自主行驶。在医疗领域,强化学习可以用于辅助医生进行病例分析、诊断和治疗方案制定,提高医疗服务的准确性和效率。此外,强化学习还在智能物流和仓储管理、金融投资决策等领域发挥着重要作用。
封面 标题:基于物联网的智能家居系统年度总结 报告人信息:[姓名]、[职位/角色]、[所属机构/公司] 日期:[具体日期] 目录 引言 年度工作回顾 系统进展与亮点 技术创新与应用 市场反馈与用户评价 存在问题与挑战 未来展望与计划 结束语与感谢 一、引言 简要介绍智能家居系统的重要性和发展趋势 回顾本年度的工作目标和重点 二、年度工作回顾 系统建设与维护 完成的项目与里程碑 系统稳定性与可靠性提升 团队建设与培训 团队成员构成与职责 培训与技能提升活动 合作伙伴与资源整合 与供应商、合作伙伴的合作情况 资源整合与利用 三、系统进展与亮点 功能扩展与优化 新增功能介绍与效果评估 现有功能的优化与改进 用户体验提升 界面设计与交互优化 用户反馈与改进措施 四、技术创新与应用 物联网技术的应用 传感器与通信技术的升级 大数据分析与应用 智能家居的智能化管理 自动化控制与节能策略 安全防护与预警系统 五、市场反馈与用户评价 市场反馈分析 市场需求与竞争态势 市场占有率与增长趋势 用户评价总结 用户满意度调查结果
numpy安装
Python库是一组预先编写的代码模块,旨在帮助开发者实现特定的编程任务,无需从零开始编写代码。这些库可以包括各种功能,如数学运算、文件操作、数据分析和网络编程等。Python社区提供了大量的第三方库,如NumPy、Pandas和Requests,极大地丰富了Python的应用领域,从数据科学到Web开发。Python库的丰富性是Python成为最受欢迎的编程语言之一的关键原因之一。这些库不仅为初学者提供了快速入门的途径,而且为经验丰富的开发者提供了强大的工具,以高效率、高质量地完成复杂任务。例如,Matplotlib和Seaborn库在数据可视化领域内非常受欢迎,它们提供了广泛的工具和技术,可以创建高度定制化的图表和图形,帮助数据科学家和分析师在数据探索和结果展示中更有效地传达信息。
Python库是一组预先编写的代码模块,旨在帮助开发者实现特定的编程任务,无需从零开始编写代码。这些库可以包括各种功能,如数学运算、文件操作、数据分析和网络编程等。Python社区提供了大量的第三方库,如NumPy、Pandas和Requests,极大地丰富了Python的应用领域,从数据科学到Web开发。Python库的丰富性是Python成为最受欢迎的编程语言之一的关键原因之一。这些库不仅为初学者提供了快速入门的途径,而且为经验丰富的开发者提供了强大的工具,以高效率、高质量地完成复杂任务。例如,Matplotlib和Seaborn库在数据可视化领域内非常受欢迎,它们提供了广泛的工具和技术,可以创建高度定制化的图表和图形,帮助数据科学家和分析师在数据探索和结果展示中更有效地传达信息。
进制转换器,支持对有符号数进行转换。可以在10进制数、16进制数、2进制数之间相互转换。 亮点:16进制数/2进制数可转换为有符号数整型。
伺服
Python库是一组预先编写的代码模块,旨在帮助开发者实现特定的编程任务,无需从零开始编写代码。这些库可以包括各种功能,如数学运算、文件操作、数据分析和网络编程等。Python社区提供了大量的第三方库,如NumPy、Pandas和Requests,极大地丰富了Python的应用领域,从数据科学到Web开发。Python库的丰富性是Python成为最受欢迎的编程语言之一的关键原因之一。这些库不仅为初学者提供了快速入门的途径,而且为经验丰富的开发者提供了强大的工具,以高效率、高质量地完成复杂任务。例如,Matplotlib和Seaborn库在数据可视化领域内非常受欢迎,它们提供了广泛的工具和技术,可以创建高度定制化的图表和图形,帮助数据科学家和分析师在数据探索和结果展示中更有效地传达信息。
全国企业家活动日ppt模板x.pptx
Python库是一组预先编写的代码模块,旨在帮助开发者实现特定的编程任务,无需从零开始编写代码。这些库可以包括各种功能,如数学运算、文件操作、数据分析和网络编程等。Python社区提供了大量的第三方库,如NumPy、Pandas和Requests,极大地丰富了Python的应用领域,从数据科学到Web开发。Python库的丰富性是Python成为最受欢迎的编程语言之一的关键原因之一。这些库不仅为初学者提供了快速入门的途径,而且为经验丰富的开发者提供了强大的工具,以高效率、高质量地完成复杂任务。例如,Matplotlib和Seaborn库在数据可视化领域内非常受欢迎,它们提供了广泛的工具和技术,可以创建高度定制化的图表和图形,帮助数据科学家和分析师在数据探索和结果展示中更有效地传达信息。
2024生态环境保护主题活动宣传服务项目方案ss.pptx
国际知名家居品牌整合营销全案ss.pptx
**C/C++课程设计项目合集** 本次提供的C/C++课程设计项目包括:KTV歌曲系统、学生档案管理系统、个人收支系统以及职工管理系统。这些项目不仅适用于课程设计,还可作为实际应用的初步探索。每个项目均经过精心设计和测试,确保代码质量和功能的完整性。 * **KTV歌曲系统**:实现了歌曲的增删改查、播放控制以及用户管理等功能,适用于各类KTV场所。 * **学生档案管理系统**:提供学生信息的录入、查询、修改和删除功能,帮助教育机构高效管理学生档案。 * **个人收支系统**:以用户为中心,记录日常收入和支出,并生成详细的统计报告,方便个人理财。 * **职工管理系统**:针对企业需求,对职工信息进行集中管理,支持多条件查询和报表生成。 这些项目采用C/C++编程语言,基于面向对象的编程思想,充分利用了现代软件工程的技术和方法。代码结构清晰,注释详细,易于理解和维护。同时,为了满足不同用户的需求,项目提供了灵活的定制选项,可以根据实际需求进行二次开发。
使用方法: 将脚本保存为iOSAppBuildAndDeploy.sh文件。 将YourProject.xcodeproj替换为你的Xcode项目文件路径。 将YourScheme替换为你的Xcode项目的Scheme名称。 创建一个ExportOptions.plist文件,并配置导出选项(例如,方法、团队ID等),然后指定其路径到EXPORT_OPTIONS_PLIST变量。 打开终端,导航到包含该脚本的目录。 给脚本执行权限:chmod +x iOSAppBuildAndDeploy.sh。 运行脚本:./iOSAppBuildAndDeploy.sh。 注意事项: 确保你的Mac上已经安装了Xcode和相应的命令行工具。 根据你的需求,你可能需要修改或扩展脚本,例如添加上传IPA到TestFlight或其他分发渠道的代码。 脚本中的ExportOptions.plist文件是Xcode构建过程中用于配置导出选项的关键文件。你需要根据你的应用分发需求来创建和配置这个文件。 如果脚本执行过程中遇到错误,请检查Xcode构建日志和脚本输出信息,以便定位问题。
2000-2021年各省产业集聚度/就业密度数据 1、时间:2000-2021年 2、来源:人口就业统计年鉴、各省年鉴 3、指标:就业人数、行政区划面积、产业集聚度/就业密度 4、范围:31省 5、计算说明:产业集聚度/就业密度=就业人数/行政区划面积
QYResearch是全球知名的大型咨询公司,行业涵盖各高科技行业产业链细分市场,横跨如半导体产业链(半导体设备及零部件、半导体材料、集成电路、制造、封测、分立器件、传感器、光电器件)、光伏产业链(设备、硅料/硅片、电池片、组件、辅料支架、逆变器、电站终端)、新能源汽车产业链(动力电池及材料、电驱电控、汽车半导体/电子、整车、充电桩)、通信产业链(通信系统设备、终端设备、电子元器件、射频前端、光模块、4G/5G/6G、宽带、IoT、数字经济、AI)、先进材料产业链(金属材料、高分子材料、陶瓷材料、纳米材料等)、机械制造产业链(数控机床、工程机械、电气机械、3C自动化、工业机器人、激光、工控、无人机)、食品药品、医疗器械、农业等。 邮箱:market@qyresearch.com
高级网络人才培训专家_X00070002 第29章 配置HDLC