Android JNI的使用

来源:互联网 发布:阿里巴巴一键上传淘宝 编辑:程序博客网 时间:2024/05/21 09:23

下面是一个简单的JNI编程实例

1 新建Android ApplicationProject。项目名称设为JniTest,其它采用默认配置。

2 新建Test类。写出对固有方法以及它的自变量进行声明的Java代码

package com.example.jnitest;public class Test {public static native void ShowMessage(String msg);static {System.loadLibrary("jnitest");}}

这里只声明了一个方法ShowMessage。

static 代码块中的内容是加载动态库libjnitest.so


3 生成C头文件

编译头Test.java文件,并对编译出来的.class文件运行javah。

步骤如下:

假设工程目录为:F:\workspace\JniTest

执行如下命令

cd /d F:\workspace\JniTest\bin\classes

javah com.example.jnitest.Test

然后会在目录F:\workspace\JniTest\bin\classes

生成一个com_example_jnitest_Test.h文件。javah 会读入类文件,并为每个固有方法声明在C 或C++头文件里生成一个函数原型。

4 完成C或C++代码

新建msg.c文件,其内容如下

#include "com_example_jnitest_Test.h"#include <stdio.h>#include <android/log.h>  //for __android_log_print/*ShowMessage的实现*/JNIEXPORT void JNICALL Java_com_example_jnitest_Test_ShowMessage(JNIEnv *jEnv, jobject this, jstring jMsg){  const char * msg;msg = (*jEnv)->GetStringUTFChars(jEnv, jMsg,0);printf("From jni: %s\n", msg);  //这个打印在eclipse 的logcat看不到,       //下面的打印是为了在logcat中可以看到。__android_log_print(ANDROID_LOG_INFO, "JniTest","%s", msg); (*jEnv)->ReleaseStringUTFChars(jEnv, jMsg,msg);}

5 编译生成动态库

在你配置好NDK环境的系统中新建一个jnitest文件夹,并在其下新建一个jni文件夹。把msg.c和javah生成的头文件拷贝到jnitest/jni目录下。

然后新建Android.mk文件,内容如下:

LOCAL_PATH := $(call my-dir)include $(CLEAR_VARS)LOCAL_CFLAGS := -D__STDC_CONSTANT_MACROS -fvisibility=hiddenLOCAL_ARM_MODE := armLOCAL_LDLIBS :=-L$(SYSROOT)/usr/lib -llogLOCAL_SRC_FILES := msg.cLOCAL_MODULE := msgimplinclude $(BUILD_SHARED_LIBRARY)

cd 到jnitest/jni目录下,执行ndk-build进行编译


6 回到eclipse


如图在libs文件夹下创建一个文件夹并命名为armeabi,然后把生成的libjnitest.so拖到该文件夹下面。


7 java代码中调用ShowMessage()

我们在MainActivity.java中的onCreate函数中调用ShowMessage()5次。

Test.ShowMessage("Msg 1 from java");

Test.ShowMessage("Msg 2 from java");

Test.ShowMessage("Msg 3 from java");

Test.ShowMessage("Msg 4 from java");

Test.ShowMessage("Msg 5 from java");

打印5条不同的消息。


8 运行JniTest

一切正常的话,你会在logcat看到5条这样的消息


证明已经成功调用到C中的实现


下图显示的是JNI的实现流程

JNI理论知识

JNI是Java Native Interface的缩写,中文为Java本地调用。JNI是Java代码与其它语言写的代码进行交互的一个接口。Java代码和本地代码相互协作,共同实现程序的功能。


JNI的存在是有其必要性的:
·标准的java类库可能不支持你的程序所需的特性。
·或许你已经有了一个用其他语言写成的库或程序,而你希望在java程序中使用它。
·你可能需要用底层语言实现一个小型的时间敏感代码,比如汇编,然后在你的java程序中调用这些功能。

相互调用问题

调用问题主要是数据类型之间的转换
基本数据类型之间的对应关系如下


例如我在.java文件中声明如下函数

public static native void typeTest(boolean b, int i, char c, long l, byte bt, float f, double db);
经javah转换后生成的函数声明为
JNIEXPORT void JNICALL Java_com_example_jnitest_Test_typeTest  (JNIEnv *, jclass, jboolean, jint, jchar, jlong, jbyte, jfloat, jdouble);
这些类型可以直接进行转换或赋值。需要注意的一点是,java的char是16位的,long是64位的。

java传递的String转换为C的char *

java传入的String参数,在c文件中被jni转换为jstring的数据类型,在c文件中声明char* msg然后msg= (char*)(*jEnv)->GetStringUTFChars(jEnv, jMsg, 0)注意:msg使用完后,通知虚拟机平台相关代码无需再访问:(*jEnv)->ReleaseStringUTFChars(jEnv, jMsg, msg);
对应的C++风格:msg= jEnv->GetStringUTFChars(jMsg, 0); jEnv->ReleaseStringUTFChars(jMsg, msg)

更严谨的使用是把msg声明为const char *msg。这相当于传递了一个字串,这个字串是只读的。

java传递的bytearray转换为char *并使用

这相当于传递了一个块buffer。可以改变buffer的内容并返回。
char *buffer = (char*) (*env)->GetByteArrayElements(env, bytearray, 0);
...
(*env)->ReleaseByteArrayElements(env, bytearray, buffer, 0);

此外还可以传递和使用Java对象
下面是书上的一个简单示例
Java代码

class MyJavaClass {public void divByTwo() { aValue /= 2; }public int aValue;}public class UseObjects {public static void main(String [] args) {UseObjects app = new UseObjects();MyJavaClass anObj = new MyJavaClass();anObj.aValue = 2;app.changeObject(anObj);System.out.println("Java: " + anObj.aValue);}private native voidchangeObject(MyJavaClass obj);static {System.loadLibrary("UseObjImpl");}}
C代码
JNIEXPORT void JNICALL Java_UseObjects_changeObject(JNIEnv * env, jobject jThis, jobject obj) {jclass cls;jfieldID fid;jmethodID mid;int value;cls = env->GetObjectClass(obj);fid = env->GetFieldID(cls,"aValue", "I");mid = env->GetMethodID(cls,"divByTwo", "()V");value = env->GetIntField(obj, fid);printf("Native: %d\n", value);env->SetIntField(obj, fid, 6);env->CallVoidMethod(obj, mid);value = env->GetIntField(obj, fid);printf("Native: %d\n", value);}


JNI函数

JNI定义了一系列函数,提供本地代码访问java代码的接口。每个Jni函数都会接收一个特殊的自变量作为自己的第一个参数:JNIEnv自变量。这些函数可以划分为下述类别:
■ 获取版本信息
■ 进行类和对象操作
■ 控制对Java对象的全局或局部引用
■ 访问实例字段和静态字段
■ 调用实例方法和静态方法
■ 执行字串和数组操作
■ 产生和控制Java异常
JNI函数的数量相当多,具体可以搜索“JNI 函数”, 也可以参阅所用编译器的JNI文档。

原创粉丝点击