JNI学习(1)

使用NDK编程的好处:

1.   代码的保护,由于apk的java层代码很容易被反编译,而C/C++库反汇难度较大。

2. 在NDK中调用第三方C/C++库,因为大部分的开源库都是用C/C++代码编写的。

3. 便于移植,用C/C++写得库可以方便在其他的嵌入式平台上再次使用

Windows上使用NDK编程的一般步骤

1,在应用中加入库    static {

       System.loadLibrary("hello-jni");

}表明程序开始运行的时候会加载hello-jni,  static区声明的代码会先于onCreate方法执行。如果你的程序中有多个类,而且如果HelloJni这个类不是你应用程序的入口,那么hello-jni(完整的名字是libhello-jni.so)这个库会在第一次使用HelloJni这个类的时候加载

声明本地方法public native String stringFromJNI();编译生成.class文件,注意这个类不能继承android的系统类,否则下面编译会失败。

2,在工程目录下创建JNI目录,在cmd下进入此目录,

使用javah命令对于生成的class文件生成头文件,

例如:javah –classpath ../bin/classes com.sample.ndk.hello.hellondk

此命令位于C:\Program Files\Java\jdk1.7.0_02\bin中,将它加入path环境变量中。

成功生成com.sample.ndk.hello.hellondk.h之后,c层根据此头文件开发c程序。并写好make文件。启动cygwin进入$NDK目录的c层目录,执行命令ndk-build编译c层代码,此时在lib目录下就会生成.so文件,复制到工程相应lib目录下。

许多成熟的C引擎要移植到Android平台上使用 ,一般都会提供一些接口,Android sdk jdk实现。

 

下文将会介绍 C 如何通过 JNI 层调用 Java的静态和非静态方法。

1、主要流程

1 新建一个测试类TestProvider.java

a)        该类提供了2个方法

b)       一个静态的方法,一个非静态的方法

2  JNI中新建Provider.c

a)        该文件中需要把Java中的类TestProvider映射到C

b)       TestProvider的两个方法映射到C

c)        新建TestProvider对象,这里就类似c++了。

d)       调用两个方法

3  Android上层调用 JNI

4  JNI层调用C

5  C层调用 Java方法

关键代码说明

C中定义映射的类、方法、对象

 

jclass TestProvider;

jobject mTestProvider;

jmethodID getTime;

jmethodID sayHello;

C 中映射类

      TestProvider= (*jniEnv)->FindClass(jniEnv,"com/duicky/TestProvider");

//或者:TestProvider =(jclass)(*jniEnv)->NewGlobalRef(jniEnv,clazz);

获得方法ID:

      jmethodID construction_id = (*jniEnv)->GetMethodID(jniEnv,TestProvider,"<int>","()V");

//建立类对象。

TestProvider mTestProvider =(*jniEnv)->NewObject(jniEnv,TestProvider,construction_id);

C 中映射方法

      静态:

getTime =(*jniEnv)->GetStaticMethodID(jniEnv, TestProvider,"getTime","()Ljava/lang/String;");

      非静态:

sayHello =(*jniEnv)->GetMethodID(jniEnv, TestProvider,"sayHello","(Ljava/lang/String;)V");

C 中调用 Java的方法

      静态:

(*jniEnv)->CallStaticObjectMethod(jniEnv,TestProvider, getTime);

      非静态:

(*jniEnv)->CallVoidMethod(jniEnv, mTestProvider,sayHello,jstrMSG);

注意 GetXXXMethodID CallXXXMethod

 

第一个XXX 表示的是映射方法的类型,如:静态跟非静态

 

第二个 XXX 表示调用方法的返回值,如:Void,Object,等等。(调用静态方法的时候Call后面要加Static

 

详细映射方法和调用方法请参考 JNI文档,这个很重要!

 

3      Java上层关键代码

 

TestProvider.Java 的两个方法

 

package com.duicky;

 

public class TestProvider {

   public static String getTime() {

 

       LogUtils.printWithSystemOut( "Call From C Java StaticMethod"   );

 

       LogUtils.toastMessage(MainActivity.mContext, "Call From C JavaStatic Method"   );

 

        returnString.valueOf(System.currentTimeMillis());

    }

 

   public void sayHello(String msg) {

 

     LogUtils.printWithSystemOut("Call From C Java Not Static Method" +msg);

 

 

       LogUtils.toastMessage(MainActivity.mContext, "Call From C Java NotStatic Method" + msg

    }

}

 

4      Android.mk文件关键代码

LOCAL_PATH := $(call my-dir)

 

 

include $(CLEAR_VARS)

 

 

LOCAL_C_INCLUDES := $(LOCAL_PATH)/include

LOCAL_LDLIBS += -L$(SYSROOT)/usr/lib -llog

 

 

 

 

LOCAL_MODULE := NDK_04

LOCAL_SRC_FILES := \

 

CToJava.c \

Provider.c

 

 

include $(BUILD_SHARED_LIBRARY)

5 JNI文件夹下文件

Provider.h

#include <string.h>

#include <jni.h>

void GetTime() ;

 

void SayHello();

Provider.c

#include "Provider.h"

#include <android/log.h>

extern JNIEnv* jniEnv;

jclass TestProvider;

jobject mTestProvider;

jmethodID getTime;

jmethodID sayHello;

int GetProviderInstance(jclass obj_class);

/**

* 初始化类、对象、方法

*/

int InitProvider() {

__android_log_print(ANDROID_LOG_INFO,"JNIMsg", "InitProvider Begin 1" );

if(jniEnv == NULL) {

return 0;

}

if(TestProvider == NULL) {

TestProvider =(*jniEnv)->FindClass(jniEnv,"com/duicky/TestProvider");

if(TestProvider == NULL){

return -1;

}

__android_log_print(ANDROID_LOG_INFO,"JNIMsg", "InitProvider Begin 2 ok");

}

if (mTestProvider == NULL) {

if (GetProviderInstance(TestProvider) != 1){

(*jniEnv)->DeleteLocalRef(jniEnv,TestProvider);

return -1;

}

__android_log_print(ANDROID_LOG_INFO,"JNIMsg", "InitProvider Begin 3 ok");

}

 

 

if (getTime == NULL) {

getTime = (*jniEnv)->GetStaticMethodID(jniEnv,TestProvider, "getTime","()Ljava/lang/String;");

if (getTime == NULL) {

(*jniEnv)->DeleteLocalRef(jniEnv,TestProvider);

(*jniEnv)->DeleteLocalRef(jniEnv,mTestProvider);

return -2;

}

__android_log_print(ANDROID_LOG_INFO,"JNIMsg", "InitProvider Begin 4 ok");

}

if (sayHello == NULL) {

sayHello =(*jniEnv)->GetMethodID(jniEnv, TestProvider,"sayHello","(Ljava/lang/String;)V");

if (sayHello == NULL) {

(*jniEnv)->DeleteLocalRef(jniEnv,TestProvider);

(*jniEnv)->DeleteLocalRef(jniEnv, mTestProvider);

(*jniEnv)->DeleteLocalRef(jniEnv,getTime);

return -3;

}

__android_log_print(ANDROID_LOG_INFO,"JNIMsg", "InitProvider Begin 5 ok");

}

__android_log_print(ANDROID_LOG_INFO,"JNIMsg", "InitProvider Begin 6" );

return 1;

}

int GetProviderInstance(jclass obj_class) {

if(obj_class == NULL) {

return 0;

}

jmethodID construction_id =(*jniEnv)->GetMethodID(jniEnv, obj_class,

"<init>", "()V");

if (construction_id == 0) {

return -1;

}

mTestProvider =(*jniEnv)->NewObject(jniEnv, obj_class,

construction_id);

 

 

if (mTestProvider == NULL) {

 

return -2;

}

 

 

return 1;

 

}

 

 

/**

* 获取时间 ----调用 Java方法

 

*/

void GetTime() {

 

if(TestProvider == NULL || getTime == NULL){

int result = InitProvider();

 

if (result != 1) {

return;

 

}

}

 

 

jstring jstr = NULL;

 

char* cstr = NULL;

__android_log_print(ANDROID_LOG_INFO,"JNIMsg", "GetTime Begin" );

 

jstr =(*jniEnv)->CallStaticObjectMethod(jniEnv, TestProvider, getTime);

cstr = (char*)(*jniEnv)->GetStringUTFChars(jniEnv,jstr, 0);

 

__android_log_print(ANDROID_LOG_INFO,"JNIMsg", "Success Get Time from Java , Value = %s",cstr );

__android_log_print(ANDROID_LOG_INFO,"JNIMsg", "GetTime End" );

 

 

(*jniEnv)->ReleaseStringUTFChars(jniEnv,jstr, cstr);

 

(*jniEnv)->DeleteLocalRef(jniEnv, jstr);

}

 

 

/**

 

* SayHello ---- 调用 Java方法

*/

 

void SayHello() {

if(TestProvider == NULL || mTestProvider ==NULL || sayHello == NULL) {

 

int result = InitProvider() ;

if(result != 1) {

 

return;

}

 

}

()Ljava/lang/String;

()表示函数的参数为空(这里为空是指除了JNIEnv *, jobject 这两个参数之外没有其他参数,JNIEnv*,  jobject是所有jni函数必有的两个参数,分别表示jni环境和对应的java类(或对象)本身),

Ljava/lang/String; 表示函数的返回值是java的String

jstring jstrMSG = NULL;

jstrMSG =(*jniEnv)->NewStringUTF(jniEnv,"Hi,I'm From C");

__android_log_print(ANDROID_LOG_INFO,"JNIMsg", "SayHello Begin" );

(*jniEnv)->CallVoidMethod(jniEnv,mTestProvider, sayHello,jstrMSG);

 

__android_log_print(ANDROID_LOG_INFO,"JNIMsg", "SayHello End" );

 

 

(*jniEnv)->DeleteLocalRef(jniEnv,jstrMSG);

}

CToJava.c

#include <string.h>

#include <android/log.h>

 

#include <jni.h>

#include "Provider.h"

 

 

JNIEnv* jniEnv;

 

 

/**

 

* Java 中声明的native getTime方法的实现

*/

 

voidJava_com_duicky_MainActivity_getTime(JNIEnv* env, jobject thiz)

{

 

 

if(jniEnv == NULL) {

 

jniEnv = env;

}

 

 

GetTime();

 

}

 

 

/**

* Java 中声明的native sayHello方法的实现

 

*/

voidJava_com_duicky_MainActivity_sayHello(JNIEnv* env, jobject thiz)

 

{

if (jniEnv == NULL) {

 

jniEnv = env;

}

 

SayHello();

 

}

3、运行效果

1、点击“C调用java静态方法”按钮

C成功调用了Java中的getTime方法,通过C方法打印出上层调用得到的时间,并且上层成功吐司出调用信息出来。

4C调用Java注意点

a) C 映射java方法时对应的签名

getTime =(*jniEnv)->GetStaticMethodID(jniEnv, TestProvider,"getTime","()Ljava/lang/String;");

故事情节还没发展这么快,下一章才会专门介绍下这个签名的使用

b)映射方法的时候需要区别静态和非静态GetStaticMethodID,GetMethodID

c)调用的时候也需要区分CallStaticObjectMethod,CallVoidMethod而且还需要区分返回值类型

 

但是一个比较典型的是当为了给java层次的应用提供调用的接口,需要实现c代码,并封装成JNI给应用层java类。

android移植的工作由核心库,Dalvik虚拟机,硬件抽象层,linux内核层和硬件系统协同完成。一般驱动包含驱动层和适配层。

java里,每一个process可以产生多个java vm对象,但是在android上,每一个process只有一个Dalvik虚拟机对象,也就是在android进程中是通过有且只有一个虚拟器对象来服务所有javac/c++代码。

java dex字节码和c/c++*.so同时运行Dalvik虚拟机之内,共同使用一个进程空间。之所以可以相互调用,也是因为有Dalvik虚拟机。

java 代码需要c/c++代码时,在Dalvik虚拟机加载进*.so库时,会先调用JNI_Onload(),此时就会把JAVA VM对象的指针存储于cjni组件的全局环境中,在java层调用C层的本地函数时,调用c本地函数的线程必然通过Dalvik虚拟机来调用c层的本地函数,此时,Dalvik虚拟机会为本地的C组件实例化一个JNIEnv指针,该指针指向Dalvik虚拟机的具体的函数列表,当JNIc组件调用java层的方法或者属性时,需要通过JNIEnv指针来进行调用。

  当本地c/c++想获得当前线程所要使用的JNIEnv时,可以使用Dalvik虚拟机对象的java vm* jvm->GetEnv()返回当前线程所在的JNIEnv*

c++组件主动调用java的方法或者属性时,需要通过JNIc组件把JNIEnv指针传递给c++组件,c++组件即可通过JNIEnv指针来掌控java层代码。

android中添加扩展驱动程序的步骤:

1,在linux内核中移植硬件驱动,实现系统调用接口。

2,在HAL层中把此驱动调用封装成Stub.

3,为上层应用的服务实现本地库,由Dalvik虚拟机调用本地库来完成上层java代码的实现。

4,在应用中调用上述接口。

 

linux下内核和应用空间通讯的方式:

/proc下创建一个节点。

使用原始套接字?,

使用mmap将内核中的特定部分的内存空间映射到用户空间去,读写操作要加锁控制。

使用relay实现内核到用户空间的高速传输。适合大数据。提供一种relay通道,提供缓冲区和文件,实现读写分离,大量突发性写入时不受内核限制。分别提供面向用户和内核空间的API.用户有:open(),mmap();read();sendfile():将数据传输到一个文件中,poll():每次缓冲区边界被越过时,会通知用户。close()将通道。缓冲区的引用数减1。在内核空间也有打开读写等操作,需要=以一个驱动实现方式实现。

 

 

应用与系统的交互:一个入侵检测程序的实现:

  1,将所有的系统调用动作记录下来:将信息记录在内核缓冲区中,另外一个进程从此缓冲区读到大的应用空间。应用程序再来分析它。

 

你可能感兴趣的:(JNI学习(1))