我们知道Android中的apk开发一般都是用Java语言编写的,但是用到的系统服务和一些操作系统相关的都是用C/C++写的。而Java和C/C++之间的互相访问我们一般都是通过JNI来打通通道的。Java和C/C++之间的调用主要分两种:Java域访问C域,这也是最常见的。和C域访问Java域,例如Android启动过程中Zygote的启动。
Java域调用C域的函数
1)在Java中某个类中,在要调用的jni方法前面加上native修饰符
2)在Java中使用该方法前,首先用 System.loadLibrary()装载库。
3)在JNI层相应的.cpp文件中定义JNI_OnLoad(JavaVM* vm, void* reserved)函数,通过AndroidRuntime::registerNativeMethods()将我们的函数注册起来,和Java层调用的JNI方法联系到一起。这样在Java中调用方法时能自动找到JNI的函数。
如:Android的MediaPlayer在Java中的代码为:
frameworks/base/media/java/android/media/MediaPlayer.java文件:
class MediaPlayer {
...............
static {
System.loadLibrary("media_jni");
native_init(); }
private static native final void native_init();
}
这里在使用MediaPlayer这个类之前Dalvik虚拟机会去装载libmedia_jni.so库,调用Jni层的JNI_OnLoad函数,这个函数负责将native函数等级到VM里面。
frameworks/base/media/jni/android_media_MediaPlayer.cpp文件:
jint JNI_OnLoad(JavaVM* vm, void* reserved)
{
JNIEnv* env = NULL;
vm->GetEnv((void**)&env, JNI_VERSION_1_4);
register_android_media_MediaPlayer(env);
return JNI_VERSION_1_4;
}
static int register_android_media_MediaPlayer(JNIEnv* env)
{
return AndroidRuntime::registerNativeMethods(env, "android/media/MediaPlayer", gMethods, NELEM(gMethods));
}
其中gMethods就是一个全局数组,里面包含JAVA函数和JNI函数是怎么一一对应的。
typedef struct {
const char* name; // Java中函数的名字
const char* signature; // 对应的签名信息,描述函数的参数和返回值
void* fnPtr; // 函数指针,指向JNI函数
}JNINativeMethod;
static JNINativeMethod gMethods[] = {
{"setDataSource", "(Ljava/lang/String;)V", (void *)android_media_MediaPlayer_setDataSource},
......
};
这里表面:在Java中我们调用的void setDataSource(String path) 对应着JNI中的void android_media_MediaPlayer_setDataSource函数。这里我们看看函数原型为:
void android_media_MediaPlayer_setDataSource(JNIEnv *env, jobject thiz, jstring path)函数
我们发现:在Jni中的实现函数签名中返回值和Java中声明函数函数的原型是一致的,如在Java里面的int类型对应着JNI层的jint,Java里面的String类型对应着JNI里面的jstring等。
Java类型 JNI类型 本地类型 字节(bit)
boolean jboolean unsigned char 8, unsigned
byte jbyte signed char 8
char jchar unsigned short 16, unsigned
short jshort short 16
int jint long 32
long jlong __int64 64
float jfloat float 32
double jdouble double 64
但是参数列表里却多了两个。
JNIEnv *env: 是虚一个与线程相关的代表虚拟机运行的环境的结构体,可以用于调用Java函数,操作jobject对象等。通过调用JavaVM中的AttachCurrentThread函数,可以得到这个线程的JNIEnv结构体。
jobject thiz:
调用改函数的对应Java中的对象,这样我们在JNI层就能区分是哪个Java类调用的这个函数
C域调用Java域的变量
在JNI中用jfieldID 和 jMethodID可以表示Java类的成员变量和成员函数,用JNIEnv提供的函数可以获取Java中的成员变量和成员函数。
jfieldID GetFieldID(jclass clazz, const char* name, const char* sig)
jmethodID GetMethodID(jclass clazz, const char* name, const char* sig);
参数:jclass表示对应的Java类,name表示成员函数或成员变量名,sIg为这个成员函数或成员变量的签名信息。
既然我们能够获取到Java域的成员变量和成员函数,那我们肯定就能访问它。
例如Android中的Camera:
在文件frameworks/base/core/jni/AndroidRuntime.cpp:
void AndroidRuntime::start(const char* className, const char* options)
{
.............
JNIEnv *env;
startVm(&mJavaVM, &env); 启动虚拟机
startReg(env); 开始注册JNI函数
}
frameworks/base/core/jni/android_hardware_Camera.cpp:
int register_android_hardware_Camera(JNIEnv *env)
{
field fields_to_find[] = {
{ "android/hardware/Camera", "mNativeContext", "I", &fields.context },
{ "android/view/Surface", ANDROID_VIEW_SURFACE_JNI_ID, "I", &fields.surface },
{ "android/graphics/SurfaceTexture",
ANDROID_GRAPHICS_SURFACETEXTURE_JNI_ID, "I", &fields.surfaceTexture },
{ "android/hardware/Camera$CameraInfo", "facing", "I", &fields.facing },
{ "android/hardware/Camera$CameraInfo", "orientation", "I", &fields.orientation },
{ "android/hardware/Camera$Face", "rect", "Landroid/graphics/Rect;", &fields.face_rect },
{ "android/hardware/Camera$Face", "score", "I", &fields.face_score },
{ "android/graphics/Rect", "left", "I", &fields.rect_left },
{ "android/graphics/Rect", "top", "I", &fields.rect_top },
{ "android/graphics/Rect", "right", "I", &fields.rect_right },
{ "android/graphics/Rect", "bottom", "I", &fields.rect_bottom },
};
if (find_fields(env, fields_to_find, NELEM(fields_to_find)) < 0)
return -1;
jclass clazz = env->FindClass("android/hardware/Camera");
fields.post_event = env->GetStaticMethodID(clazz, "postEventFromNative",
"(Ljava/lang/Object;IIILjava/lang/Object;)V");
if (fields.post_event == NULL) {
LOGE("Can't find android/hardware/Camera.postEventFromNative");
return -1;
}
// Register native functions
return AndroidRuntime::registerNativeMethods(env, "android/hardware/Camera",
camMethods, NELEM(camMethods));
}
其中fields_to_find是一个field类型的结构体:
struct field{
const char* class_name; // 类的名字
const char* field_name; // Java中成员变量的名字
const char* field_type; // Java中成员变量的类型
jfieldID *jfield; // 对应的JNI域中变量
};
这样我们将Java中对应类的各个变量ID保存到JNI结构体中:
struct fields_t {
jfieldID context;
jfieldID surface;
jfieldID surfaceTexture;
jfieldID facing;
jfieldID orientation;
jfieldID face_rect;
jfieldID face_score;
jfieldID rect_left;
jfieldID rect_top;
jfieldID rect_right;
jfieldID rect_bottom;
jmethodID post_event;
jmethodID rect_constructor;
jmethodID face_constructor;
};
所以现在我们可以在JNI域直接修改设置Java域的这些变量了:如当我们Java层需要获得CameraInfo信息时,我们会调用android.hardware.Camera::getCameraInfo()函数,会调用到JNI的android_hardware_Camera_getCameraInfo();
// info_obj是Java中传进来的CameraInfo对象,标志着是Java中的哪个对象调用的当前函数
static void android_hardware_Camera_getCameraInfo(JNIEnv* env, jobject thiz, jint cameraId, jobject info_obj)
{
CameraInfo cameraInfo;
Camera::getCameraInfo(cameraId, &cameraInfo);
....
env->SetIntField(info_obj, fields.facing, cameraInfo.facing);
env->SetIntField(info_obj, fields.orientation, cameraInfo.orientation);
}
通过CPP获取到cameraInfo信息,然后调用env->SetIntField()函数将cameraInfo.facing信息设置到info_obj.facing中去。
总结:
1) 通过JNIEnv::FindClass()找到Java域中类对应的jclass
jclass clazz = env->FindClass("android/hardware/Camera$CameraInfo"); 其中$表面是嵌套类
2)通过JNIEnv::GetFieldID()找到类中属性变量的jfieldID
jfieldID fieldId = env->GetFIeldID(clazz, "facing", "I");
3)通过JNIEnv::GetTYPEField()/SetTYPEField() 获取/设置Java对象的属性
env->SetIntField(info_obj, fieldId, camerainfo.facing);
C域调用Java域的函数
在Android中C域访问Java域的方法和访问属性相类似。如android.hardware.Camera中的postEventFromNative是如何被JNI调用的。
我们注意到在前面:register_android_hardware_Camera(JNIEnv *env)中,在我们正式注册native函数之前我们已经将Java域中的方法postEventFromNative的jmethodID获取了。
1)获得methodID
jclass clazz = env->FindClass("android/hardware/Camera");
fields.post_event = env->GetStaticMethod(clazz, "postEventFromNative", "(Ljava/lang/Object;IIILjava/lang/Object;)V");
这样Java域中android.hardware.Camera类的postEventFromNative函数的methodID就被保存下来了。
2)调用方法
JNIEnv *env = AndroidRuntime::getJNIEnv();
env->CallStaticVoidMethod(mCameraJClass, fields.post_event, mCameraJObjectWeak, msgType, ext1, ext2, NULL);
C域生成的对象的保存与使用
在C域中某次被调用生成的对象,在其他函数调用时是不可见的,虽然可以设置为全局变量但不是最好的解决方式。在Android中通常是在Java域中定义一个int型变量,在c域生成对象的地方,与这个Java域的变量关联。然后在别的使用到的地方,再从这个变量中获取。
如JNICameraContext:
在正式注册native函数之前我们已经将Java域中android.hardware.Camera类的变量mNativeContext保存到本地了。
jclass clazz = env->FindClass("android/hardware/Camera");
jfieldID field = env->GetFieldID(clazz, "mNativeContext", "I");
在生成cpp对象时,通过JNIEnv::SetIntField()设置为Java对象的属性
static void android_hardware_Camera_native_setup(JNIEnv* env, jobject thiz, jobject weak_this, jint cameraId)
{
......
sp<JNICameraContext> context = new JNICameraContext(env, weak_this, clazz, camera);
.......
env->SetIntField(thiz, fields.context, (int)context.get());
}
当我们要使用时,通过JNIEnv::GetIntField()获取Java对象的属性,并转化为JNICameraContext类型:
JNICameraContext* context = reinterpret_cast<JNICameraContext*>(env->GetIntField(thiz, fields.context));
总结:
1) 通过JNIEnv::FindClass()找到对应的jclass
2) 通过JNIEnv::GetFieldID()找到类中属性的jfieldID
3)生成cpp对象时,通过JNIEnv::SetIntField()设置为Java对象的属性
4)使用的时候,通过JNIEnv::GetIntField()获取Java对象的属性,再转化为真实对象类型
声明:本文是在网上另外一篇文章修改而来,加入了自己的一些理解。原文地址:http://blog.csdn.net/thl789/article/details/7212822