目录:

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

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

  1. 腾讯内核java调用,taip: TAIP是调用腾讯AI的Java客户端,为调用腾讯AI功能的开发人员提供了一系列的交互方法。...

    OCR Java SDK目录结构 cn.xsshome.taip ├── base //基类 ├── http //Http通信相关类 ├── imageclassify │ └── TAipImag ...

  2. vbs调用c++dll_COM编程攻略(八 动态调用与IDispatch接口)

    上一篇我们实现了一个本地进程服务. Froser:COM编程攻略(七 COM跨进程组件开发实战)​zhuanlan.zhihu.com 这一篇,我们将对这个本地进程服务做一些修改,让它能支持如下VBS ...

  3. java与js交互视频_Android与H5交互—Java调用Js、Js调Java、H5页面调用Android播放视频...

    content="ANZO,GAME,LOL,DNF,DOTA,撸啊撸,地下城与勇士,掌游宝,攻略,宝典,模拟器,加点,战报,英雄,最新,最全,最强,视频"> content ...

  4. java 调用一个抛出异常的函数,Java VS C++(14) 调用可能抛出异常的函数

    调用可能抛出异常的函数 (1)java看是否是受检异常,如果是必须try catch, 如果是非受检异常,则不用 try catch void test()throws MyException; (2 ...

  5. 调用布尔变量java_关于java的参数的调用,还有布尔的理解,这有一段代码,我有些不太理解,希望能够帮我分析下,谢谢...

    第一段代码 public class CustomerBiz { String[] names=new String[30]; //创建学生姓名数组 //实现姓名的添加 public void add ...

  6. Java的getter_java动态调用getter方法

    不知道反射能不能满足你的需求 package test; import java.lang.reflect.InvocationTargetException; import java.lang.re ...

  7. C#动态调用web服务 远程调用技术WebService

    一.课程介绍 一位伟大的讲师曾经说过一句话:事物存在即合理!意思就是说:任何存在的事物都有其存在的原因,存在的一切事物都可以找到其存在的理由,我们应当把焦点放在因果关联的本质上.所以在本次分享课开课之 ...

  8. 转【C#调用DLL的几种方法,包括C#调用C\C++\C#DLL】

    C#中dll调用方法 一.      DLL与应用程序 动态链接库(也称为DLL,即为"Dynamic Link Library"的缩写)是Microsoft Windows最重要 ...

  9. C#程序实现动态调用DLL的研究(转)

    摘 要:在<csdn开发高手>2004年第03期中的<化功大法--将DLL嵌入EXE>一文,介绍了如何把一个动态链接库作为一个资源嵌入到可执行文件,在可执行文件运行时,自动从资 ...

最新文章

  1. typedef的四个用途和两大陷阱
  2. java 中启动线程的正确方式
  3. 数学建模——智能优化之模拟退火模型详解Python代码
  4. mac word维吾尔文字体_字加软件更新啦!万款字体一键激活!
  5. 【转载】一起聊天的wz132
  6. python+Django学习资源汇总-更新中
  7. 单片机模拟计算机课设,单片机课程设计题目汇总(全)
  8. Java基础篇:介绍嵌套类和内部类
  9. Android解决异常apk on device '0292bea1': Unable to open sync connection!
  10. 《线性代数》(同济版)——教科书中的耻辱柱
  11. 【图像融合】基于NSST结合PCNN实现图像融合附matlab代码
  12. 线程的三种创建方式以及区别
  13. 汽车HUD抬头显示全产业链深度解析报告
  14. FutureMapping:空间人工智能的计算结构
  15. 看完这篇 Linux 的基本操作你就会了!
  16. 互联网之子:亚伦·斯沃茨
  17. 微软的teredo服务器,深度完美W10系统通过teredo连接IPv6的方法
  18. 弘辽科技:拼多多类目选错了有什么影响?怎么办?
  19. Arduino使用TM1637四位数码管
  20. Android获取系统启动器、电话、短信和相机包名

热门文章

  1. ATS和闰秒那些事儿
  2. 虚幻引擎C++终极射手教程 Unreal Engine C++ The Ultimate Shooter Course
  3. r-rpm常用命令集
  4. java课堂测试样卷-----简易学籍管理系统
  5. 排序算法之直接插入排序
  6. Android 常见异常及解决办法
  7. C# 实现Oracle中的数据与Excel之间的转换
  8. @class和#import
  9. java遍历给定目录,树形结构输出所有文件,包括子目录中的文件
  10. 很多学ThinkPHP的新手会遇到的问题