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方法打印出上层调用得到的时间,并且上层成功吐司出调用信息出来。
4、C调用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进程中是通过有且只有一个虚拟器对象来服务所有java和c/c++代码。
java 的dex字节码和c/c++的*.so同时运行Dalvik虚拟机之内,共同使用一个进程空间。之所以可以相互调用,也是因为有Dalvik虚拟机。
当java 代码需要c/c++代码时,在Dalvik虚拟机加载进*.so库时,会先调用JNI_Onload(),此时就会把JAVA VM对象的指针存储于c层jni组件的全局环境中,在java层调用C层的本地函数时,调用c本地函数的线程必然通过Dalvik虚拟机来调用c层的本地函数,此时,Dalvik虚拟机会为本地的C组件实例化一个JNIEnv指针,该指针指向Dalvik虚拟机的具体的函数列表,当JNI的c组件调用java层的方法或者属性时,需要通过JNIEnv指针来进行调用。
当本地c/c++想获得当前线程所要使用的JNIEnv时,可以使用Dalvik虚拟机对象的java vm* jvm->GetEnv()返回当前线程所在的JNIEnv*
当c++组件主动调用java的方法或者属性时,需要通过JNI的c组件把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,将所有的系统调用动作记录下来:将信息记录在内核缓冲区中,另外一个进程从此缓冲区读到大的应用空间。应用程序再来分析它。