当前位置: 首页 > 编程日记 > 正文

android jni 调用 java_Android与JNI(二) ---- Java调用C++ 动态调用

目录:

1. 简介

2. JNI 组件的入口函数

3. 使用 registerNativeMethods 方法

4. 测试

5. JNI 帮助方法

6. 参考资料

1. 简介

Android与JNI(一)已经简单介绍了如何在 android  环境下使用 JNI 了。但是遵循 JNI 开发的基本步骤似乎有点死板,而且得到的本地函数名太丑了。所以非常有必要在这里介绍另外一种实现方法。

2. JNI 组件的入口函数

前一篇文章说到 static {System.loadLibrary("HelloJNI");} 会在第一次使用该类的时候加载动态库 libHelloJNI.so 。当 Android 的 VM 执行到 System.loadLibrary() 这个函数时,首先会执行 JNI_OnLoad() 这个函数。与此对应的是卸载时调用 JNI_OnUnLoad() 。

首先调用 c 组件中的 JNI_OnLoad()  的意图包括:

(1) 告诉 VM 此 c 组件使用那一个 JNI 版本。如果动态库中没有提供 JNI_OnLoad(),VM 默认该动态库使用最老的 JNI 1.1 版本。由于新版的 JNI 做了许多扩充,如果需要使用 JNI 的新版功能,例如 JNI 1.4的 java.nio.ByteBuffer,就必须藉由 JNI_OnLoad() 函数来告知 VM。

(2) 另外一个作用就是初始化,例如预先申请资源等。

现在我们先现实一个非常简单的 JNI_OnLoad(),看是不是在真的有调用到它。在 hellojni.cpp 中加入代码:

1 #include

2 #include

3

4 #define LOGD(...) __android_log_print(ANDROID_LOG_DEBUG,"jnitest",__VA_ARGS__)

5 jint JNI_OnLoad(JavaVM* vm, void *reserved)6 {7 LOGD("JNI_OnLoad called");8 returnJNI_VERSION_1_4;9 }

编译:

$ndk-build

Compile++ thumb: hellojni <= hellojni.cpp

SharedLibrary : libHelloJNI.so

/home/eddy/workspace/HelloJNI/obj/local/armeabi/objs/HelloJNI/HelloJNI.o: In function `JNI_OnLoad':

/home/eddy/workspace/HelloJNI/jni/HelloJNI.c:36: undefined reference to `__android_log_print'

collect2: ld returned 1 exit status

make: *** [/home/eddy/workspace/HelloJNI/obj/local/armeabi/libHelloJNI.so] Error 1

出错了。是链接出错,这个很简单,只要链接 liblog.so 这个库就可以了。在 Android.mk 中添加:

LOCAL_LDLIBS := -llog

再编译:

[armeabi] Compile++ thumb: hellojni <= hellojni.cpp

[armeabi] StaticLibrary : libstdc++.a

[armeabi] SharedLibrary : libhellojni.so

[armeabi] Install : libhellojni.so => libs/armeabi/libhellojni.so

**** Build Finished ****

OK,大功告成。如无意外,运行后你会在 logcat 中找到这样的打印:

/dalvikvm( 1956): Trying to load lib /data/data/com.example.hellojni/lib/libHelloJNI.so 0x41345548

D/dalvikvm( 1956): Added shared lib /data/data/com.example.hellojni/lib/libHelloJNI.so 0x41345548

D/ ( 1956): JNI_OnLoad called.

这说明了在加载 JNI 的动态库时,确实调用了 JNI_OnLoad() 。调用了这个库又有什么用呢?下面为你揭晓。

3. 使用 registerNativeMethods 方法

说了这么多,终于来重点了。先把修改后的 hellojni.cpp 列出来,然后再慢慢分析。

#include #include#include#include#include

#define LOGD(...) __android_log_print(ANDROID_LOG_DEBUG,"jni",__VA_ARGS__)#ifdef __cplusplusextern "C"{#endif

static const char* className = "com/example/hellojni/MainActivity";

JNIEXPORT jstring JNICALL stringFromJNI(JNIEnv*env, jclass clazz)

{//return env->NewStringUTF(env, "Hello form JNI!");

return env->NewStringUTF("hello world returned.");

}static JNINativeMethod gMethods[] ={

{"stringFromJNI", "()Ljava/lang/String;", (void *)stringFromJNI },

};//This function only registers the native methods, and is called from JNI_OnLoad

JNIEXPORT int JNICALL register_location_methods(JNIEnv *env)

{

jclass clazz;/*look up the class*/clazz= env->FindClass(className );//clazz = env->FindClass(env, className);

if (clazz ==NULL) {

LOGD("Can't find class %s\n", className);return -1;

}

LOGD("register native methods");/*register all the methods*/

//if ((*env)->RegisterNatives(env, clazz, gMethods, sizeof(gMethods) / sizeof(gMethods[0])) != JNI_OK)

if (env->RegisterNatives(clazz, gMethods, sizeof(gMethods) / sizeof(gMethods[0])) !=JNI_OK)

{

LOGD("Failed registering methods for %s\n", className);return -1;

}/*fill out the rest of the ID cache*/

return 0;

}

jint JNI_OnLoad(JavaVM* vm, void *reserved)

{

JNIEnv* env =NULL;

jint result= -1;

LOGD("%s: +has loaded", __FUNCTION__);//for c//if ((*vm)->GetEnv(vm, (void**) &env, JNI_VERSION_1_4) != JNI_OK) {//for c++

if( vm->GetEnv((void**) &env, JNI_VERSION_1_4) !=JNI_OK) {

LOGD("ERROR: GetEnv failed.\n");returnresult;

}if( register_location_methods(env) < 0)

{

LOGD("ERROR: register location methods failed.\n");returnresult;

}returnJNI_VERSION_1_4;

}void JNI_OnUnload(JavaVM* vm, void *reserved)

{return;

}

#ifdef __cplusplus

}#endif

先说一说这段代码实现了什么,他与 [1] 的结果完全一样,实现了 JAVA 中声明的 stringFromJNI 本地方法,返回一个字符串。至于为什么不再需要以Java_com_example_hellojni_HelloJNI_stringFromJNI 命名,就要慢慢分析了。

还是从 JNI_OnLoad() 这个函数开始说起。该函数的动作包括:获取 JNI 环境对象,登记本地方法,最后返回 JNI 版本。

值得引起注意的是:

(*env)->NewStringUTF(env, "Hello from JNI !");

这一行,这是c的写法,而我的是cpp程序,需要改写成:

env->NewStringUTF( "Hello from JNI !");

否则会编译出错。

登记本地方法,作用是将 c/c++ 的函数映射到 JAVA 中,而在这里面起到关键作用的是结构体 JNINativeMethod 。他定义在 jni.h 中。

1 typedef struct{2 const char* name;     /*java 中声明的本地方法名称*/

3 const char* signature;  /*描述了函数的参数和返回值*/

4 void* fnPtr;    /*c/c++的函数指针*/

5 } JNINativeMethod;

声明实例

static JNINativeMethod gMethods[] ={

{"stringFromJNI", "()Ljava/lang/String;", (void *)stringFromJNI },

};

参数分析:

"stringFromJNI":Java 中声明的本地方法名;

(void *)stringFromJNI:映射对象,本地 c/c++ 函数,名字可以与 Java 中声明的本地方法名不一致。

"()Ljava/lang/String;":这个应该是最难理解的,也就是结构体中的 signature 。他描述了本地方法的参数和返回值。例如

"()V"

"(II)V"

"(Ljava/lang/String;Ljava/lang/String;)V"

实际上这些字符是与函数的参数类型一一对应的。

"()" 中的字符表示参数,后面的则代表返回值。例如"()V" 就表示void Func();

"(II)V" 表示 void Func(int, int);

具体的每一个字符的对应关系如下

字符   Java类型     C类型

V      void         void

Z      jboolean     boolean

I       jint         int

J       jlong        long

D      jdouble       double

F      jfloat            float

B      jbyte            byte

C      jchar           char

S      jshort          short

数组则以"["开始,用两个字符表示

[I     jintArray       int[]

[F     jfloatArray     float[]

[B     jbyteArray     byte[]

[C    jcharArray      char[]

[S    jshortArray      short[]

[D    jdoubleArray    double[]

[J     jlongArray      long[]

[Z    jbooleanArray    boolean[]

上面的都是基本类型,如果参数是 Java 类,则以"L"开头,以";"结尾,中间是用"/"隔开包及类名,而其对应的 C 函数的参数则为 jobject,一个例外是 String 类,它对应的 c 类型为 jstring 。

Ljava/lang/String;     String     jstring

Ljava/net/Socket;      Socket    jobject

如果 JAVA 函数位于一个嵌入类(也被称为内部类),则用$作为类名间的分隔符,例如:"Landroid/os /FileUtils$FileStatus;"。

最终是通过登记所有记录在 JNINativeMethod 结构体中的 JNI 本地方法。

使用 registerNativeMethods 方法不仅仅是为了改变那丑陋的长方法名,最重要的是可以提高效率,因为当 Java 类透过 VM 呼叫到本地函数时,通常是依靠 VM 去动态寻找动态库中的本地函数(因此它们才需要特定规则的命名格式),如果某方法需要连续呼叫很多次,则每次都要寻找一遍,所以使用 RegisterNatives 将本地函数向 VM 进行登记,可以让其更有效率的找到函数。

registerNativeMethods 方法的另一个重要用途是,运行时动态调整本地函数与 Java 函数值之间的映射关系,只需要多次调用 registerNativeMethods 方法,并传入不同的映射表参数即可。

4. 测试

为了对 registerNativeMethods有更好的理解,我在 Java 中再添加一个本地方法。

packagecom.example.hellojni;importandroid.os.Bundle;importandroid.app.Activity;importandroid.util.Log;importandroid.view.Menu;importandroid.widget.TextView;public class MainActivity extendsActivity {

@Overrideprotected voidonCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);

setContentView(R.layout.activity_main);

TextView tv= new TextView(this);

tv.setText( stringFromJNI() );

setContentView(tv);

Log.d("JNI", "max = " + max(10, 100));

}

@Overridepublic booleanonCreateOptionsMenu(Menu menu) {//Inflate the menu; this adds items to the action bar if it is present.

getMenuInflater().inflate(R.menu.main, menu);return true;

}public nativeString stringFromJNI();public native int max(int a,intb);static{

System.loadLibrary("hellojni");

}

}

在 hellojni.cpp 中添加 max 的实现方法。

1 int native_max(JNIEnv* env, jclass clazz, int a, intb)2 {3 return (a > b ?a:b);4 }5

6 static JNINativeMethod gMethods[] ={7 { "stringFromJNI", "()Ljava/lang/String;", (void *)stringFromJNI },8 { "max", "(II)I", (void *)native_max },9 };

用 ndk 编译生成动态库。

$ndk-build

Compile thumb : HelloJNI <= HelloJNI.c

SharedLibrary : libHelloJNI.so

Install : libHelloJNI.so => libs/armeabi/libHelloJNI.so

在模拟器上 run as ---> Android Application 。可以看到打印:

D/dalvikvm( 2174): Trying to load lib /data/data/com.example.hellojni/lib/libHelloJNI.so 0x413488a8

D/dalvikvm( 2174): Added shared lib /data/data/com.example.hellojni/lib/libHelloJNI.so 0x413488a8

D/ ( 2174): JNI_OnLoad: +

D/ ( 2174): register native methods

D/JNI ( 2174): max = 100

证明 max 调用成功。

通过 registerNativeMethods 这种方法,我们可以看到操作的过程中,不需要再使用 javah -jni 生成 jni 头文件。c/c++ 的函数名也可以自由取名。

5. JNI 帮助方法

在 Android 源码中 your_path/dalvik/libnativehelper/include/nativehelper/JNIHelp.h 这个头文件提供了一些关于 JNI 的帮助函数,包括本地方法注册、异常处理等函数。但是使用这些函数需要链接动态库 libnativehelper.so 。还提供了一个计算方法映射表长度的宏定义。

#ifndef NELEM

# define NELEM(x) ((int) (sizeof(x) / sizeof((x)[0])))

#endif

有了这个宏之后,我们就可以这样子写:

(*env)->RegisterNatives(env, clazz, gMethods, NELEM(gMethods));

6. 参考资料

参考链接:http://www.cnblogs.com/eddy-he/archive/2012/08/09/2629974.html

相关文章:

如何查找并干掉僵尸进程

查找命令&#xff1a;ps -A -o stat,pid,ppid,cmd | grep -e ^[Zz] 找到之后 kill掉&#xff0c;然后用top命令查看是否kill成功&#xff0c;如果失败&#xff0c;kill 父进程。 转载于:https://www.cnblogs.com/kfx2007/p/3572249.html

[转] WINCC教学视频

原文地址http://www.ad.siemens.com.cn/club/bbs/post.aspx?b_id5&a_id1049763&s_id17&num0#anch WinCC 变量归档系列视频&#xff1a; http://www.ad.siemens.com.cn/service/elearning/cn/SerialVideo.aspx?vsid136 WinCC亚洲版高级工程师培训视频&#xff1a;…

UNL(Ubiquitous Navigation Lab)

转载于:https://www.cnblogs.com/Forwithy/p/10080078.html

linux 文件IO与内存映射:内存映射

前言 前面几篇我们学习了用户空间的IO缓冲区,以及IO缓冲区的分散聚合IO技术. 为了减少系统调用的次数&#xff0c;提升系统性能&#xff0c;操作系统开发者门提出了这么多的缓存技术。 但是到这里这些技术同样有不足的地方&#xff1a;不论是读或者写文件&#xff0c;都需要将…

Java网页数据采集器[下篇-数据查询]【转载】

本期概述 上一期我们学习了如何将html采集到的数据存储到MySql数据库中,这期我们来学习下如何在存储的数据中查询我们实际想看到的数据. 数据采集页面 2011-2012赛季英超球队战绩 如果是初学者 以下可能对你有帮助 Java如何操作MySql?在使用java 操作MySql数据库之前 我们需要…

loadrunner 调用java_LoadRunner调用Java程序—性能测试

为了充分利用LoadRunner的场景控制和分析器&#xff0c;帮助我们更好地控制脚本加载过程&#xff0c;从而展现更直观有效的场景分析图表。本次将重点讨论LoadRunner如何调用Java测试代码&#xff0c;完成压力测试。通常我们在执行一些Server的压力测试的时候&#xff0c;总会不…

《The Art of Readable Code》 读书笔记 01

放假前在学校图书馆借了一本新书《The Art of Readable Code》&#xff0c;寒假回来看看&#xff0c;写写其中的Key Idea 、summary和一些读书笔记。 Preface 前言部分主要概况讲了本书的核心思想——Code shoule be easy to understand。接着探讨什么是好代码&#xff0c;是内…

吴裕雄 10-MySQL插入数据

语法以下为向MySQL数据表插入数据通用的 INSERT INTO SQL语法&#xff1a;INSERT INTO table_name ( field1, field2,...fieldN ) VALUES ( value1, value2,...valueN );如果数据是字符型&#xff0c;必须使用单引号或者双引号&#xff0c;如&#xff1a;"value"。 通…

编译内核指定模块,筛选当前模块依赖的组件

关于内核模块编译的过程中&#xff0c;往往我们仅仅需要其中一个小的模块&#xff0c;但是却因为内核源码的庞杂而止步与模块依赖的筛选过程中。 为了更加便捷得对内核各个模块进行管理&#xff0c;这里提供一个小脚本来进行指定模块相关得模块留存&#xff0c;不相关的模块代码…

计算机启动和操作系统加载小话

整个启动和加载过程可分为若干步骤&#xff0c;或者称为若干个状态&#xff0c;或者快照&#xff0c;下面的每一段都是描述一个快照。&#xff08;类似自动状态机&#xff09; 1、电源稳定&#xff08;POWER GOOD&#xff09; 按下启动键后&#xff0c;电源首先启动。为了保证安…

java清空栈_java - 如何使用Intent.FLAG_ACTIVITY_CLEAR_TOP清除活动堆栈?

java - 如何使用Intent.FLAG_ACTIVITY_CLEAR_TOP清除活动堆栈&#xff1f;我已经阅读了几篇关于使用它的帖子&#xff0c;但必须遗漏一些因为它不适合我。 我的活动A在清单中有launchmode “singleTop”。 它启动活动B&#xff0c;启动模式“singleInstance”。 活动B打开浏览器…

「学习笔记-Linux」学习Shell Script

学习Shell Script Table of Contents 1 什么是Shell Scipt 1.1 程序书写1.2 程序执行2 简单Shell练习 2.1 例1 接收用户输入2.2 例2 按日期建立相似名字的文件3 判断式 3.1 测试文件是否存在3.2 test常用选项 3.2.1 文件类型3.2.2 权限3.2.3 文件新旧比较3.2.4 整数&#xff0c…

django admin组件

admin实例 from django.contrib import admin from app01 import models from django.utils.safestring import mark_safe # Register your models here. class UserInfoConfig(admin.ModelAdmin):# 自定义显示的东西def xxx(self):return mark_safe(<a href>xx</a>…

C语言网络编程:close或者shutdown断开通信连接

文章目录前言close函数介绍shutdown函数介绍前言 这里在主要通过实例进行描述close函数在网络编程中的使用 TCP编程模型中客户端或者服务器只要主动通过close发起断开连接的请求&#xff0c;则通信连接可以中断。 可以通过在主进程中抓取通信端的断开信号&#xff0c;比如SIGI…

Await, and UI, and deadlocks! Oh my!

It’s been awesome seeing the level of interest developers have had for the Async CTP and how much usage it’s getting. Of course, with any new technology there are bound to be some hiccups. One issue I’ve seen arise now multiple times is developers acc…

传智播客java基础的习题_传智播客java基础班(集合与IO)阶段测试题

本帖最后由 zhaodecang 于 2016-6-8 19:38 编辑单选题&#xff1a;(每道题目2分)1. ArrayList类的底层数据结构是( )a) 数组结构b) 链表结构 c) 哈希表结构 d) 红黑树结构2. LinkedList类的特点是( )a) 查询快b) 增删快c) 元素不重复 d) 元素自然排序3. Vector类的特点…

$@ 与 $* 差在哪?

$ 与 $* 差在哪&#xff1f; 要说 $ 与 $* 之前&#xff0c;需得先从 shell script 的 positional parameter 谈起...我们都已经知道变量(variable)是如何定义及替换的&#xff0c;这个不用再多讲了。但是&#xff0c;我们还需要知道有些变量是 shell 内定的&#xff0c;且其名…

[源码和文档分享]基于Netty和WebSocket的Web聊天室

一、背景 伴随着Internet的发展与宽带技术的普及&#xff0c;人们可以通过Internet交换动态数据&#xff0c;展示新产品&#xff0c;与人进行沟通并进行电子商务贸易。作为构成网站的重要组成部分&#xff0c;留言管理系统为人们的交流提供了一个崭新的平台。同时&#xff0c;聊…

t-tcpdump

文章目录写入和读取数据包抓取数据包抓取指定网卡流量指定数据的输出格式数据包抓取的方向输出信息的详细程度的可控选项抓取指定协议的数据包表达式介绍逻辑连接符的使用type的确定写入和读取数据包 在工作或者生活中的网络故障排除时最有力的方式就是抓包分析网络状况&#…

java jdk 8u111_8u111-jdk-alpine在java开发中的NullPointerException错误解决方案

问题描述在部署一个验证码服务的容器服务时遇到了一个空指针错误&#xff0c;错误代码为&#xff1a;java.lang.NullPointerExceptionat sun.awt.FontConfiguration.getVersion(FontConfiguration.java:1264)at sun.awt.FontConfiguration.readFontConfigFile(FontConfiguratio…

sprintf函数做什么用?

sprintf函数原型为 int sprintf(char *str, const char *format, ...)。作用是格式化字符串&#xff0c;具体功能如下所示&#xff1a; &#xff08;1&#xff09;将数字变量转换为字符串。 &#xff08;2&#xff09;得到整型变量的16进制和8进制字符串。 &#xff08;3&#…

Yii学习笔记【3】

加载控制器及其方法&#xff1a; 根据route信息&#xff0c;获得当前控制器| 初始化当前控制器&#xff0c;CController::init()&#xff0c;默认为空| 执行当前控制器&#xff0c;CController::run()||----> 创建action&#xff0c;为空则默认为index|得到CInlineAction的实…

验证码相似问题

产生随机验证码时&#xff0c;类似数字1和小写字母l经常容易让人混淆分不清楚&#xff0c; 因此&#xff0c;产生随机验证码时应避免此情况 1&#xff08;一&#xff09;、l&#xff08;哎哦&#xff09;、I &#xff08;哎&#xff09;中三个任意两个或者全部不可同时存在 0&a…

C语言网络编程:accept函数详解

文章目录前言函数描述代码实例如何得到客户端的IP 和 端口号前言 当使用tcp服务器使用socket创建通信文件描述符&#xff0c;bind绑定了文件描述符&#xff0c;服务器ip和端口号&#xff0c;listen将服务器端的主动描述符转为被动描述符进行监听之后&#xff0c;接口accept通过…

java 声明静态类_java静态类声明--java类可以声明为static吗

为了理解static关键字在类声明中的使用&#xff0c;首先我们需要了解类声明。有两种类&#xff0c;一种是top-level class&#xff1b;一种是inner class。Top-level classestop-level class可以被声明为包成员&#xff0c;每一个top-level类对应于一个文件名与类名相同的java文…

单元测试资料汇总

从安装到配置 首先到官网http://www.nunit.org/下载如下图的资料&#xff0c;安装NUnit-2.6.1.msi包。 然后挂在VS2010外部工具这个地方来使用&#xff0c;工具—>外部工具—>添加—>标题&#xff1a;Nunit—>命令&#xff1a;安装路径—>确定。 然后打开Nunit&…

rhel5+nis+autofs+nfs

创建NIS服务器用户&#xff0c;用于客户端登陆 NIS服务器相关包&#xff1a;ypserv、ypbind(在RHEL5中默认已安装)、yp-tools(在RHEL5中默认已安装)。 运行nisdomainname test.com并把加入到如下位置 设置NIS服务器的域名 在NIS环境中将以NIS服务器上的所有用户用于NIS环境中所…

Beta冲刺 (1/7)

Part.1 开篇 队名&#xff1a;彳艮彳亍团队 组长博客&#xff1a;戳我进入 作业博客&#xff1a;班级博客本次作业的链接 Part.2 成员汇报 组员1&#xff08;组长&#xff09;柯奇豪 过去两天完成了哪些任务 熟悉并编写小程序的自定义控件展示GitHub当日代码/文档签入记录接下来…

C语言网络编程:listen函数详解

文章目录前言函数描述代码实例TCP服务器为什么调用listen前言 根据TCP编程模型中我们可以看到之前的socket和bind接口是tcp服务器在为接收客户端的链接做准备&#xff0c;保证tcp的面向字节流&#xff0c;面向连接的可靠通信服务正常进行。接下来的listen端口则为我们进行三次…

MVC页面加载速度优化小记

前言&#xff1a;最近做一个地图展示页面&#xff0c;业务初期没什么问题&#xff0c;运行一阵后报错&#xff1a; Error during serialization or deserialization using the JSON JavaScriptSerializer. The length of the string exceeds the value set on the maxJsonLeng…