在jni中,或者android系统源代码中,最常用到的是通过JNI_OnLoad(...)方法,对方法进行注册,而不会像前面的测试demo,生成.h头文件,然后写一个.c的去实现.h中的方法,JNI_OnLoad的引入更加注重c++的使用,至少偏向c++,android系统源代码也是如此,到了下面往往是c++做了一个过渡,但是再进一步深入,最终还是c实现的,比如网络协议等,这一点可以看出实际上用到了c++面向对象编程对c做了封装,然后提供给java,而java也是面向对象的语言,对于衔接起了非常重要的作用.
在应用层加载so的时候,虚拟机首先回去/自动执行JNI_OnLoad(...),所以在这里注册所有的方法/函数就好了,加载到虚拟机中,等待上面调用!
下面验证一下,新建一个android工程如下:
<1> : 新建android工程,目录树如下:
<2> : 下面的文件内容如下:
DurianOnLoad.java :
/**
* @Title: DurianOnLoad.java
* @Package com.durian.jnionload.lib
* @Description: TODO
* @author zhibao.liu from durian organization
* @date 2015-12-25 下午04:46:13
* @version V1.0
*/
package com.durian.jnionload.lib;
/**
* @ClassName: DurianOnLoad
* @Description: TODO
* @author zhibao.liu Freelancer
* @email [email protected]
* @date 2015-12-25 下午04:46:13
*
*/
public class DurianOnLoad {
public native float getDurianLibVersion();
public native int DurianAdd(int a,int b);
public native int DurianEqual(int a);
public native float DurianEqualF(float a,float b);
static{
System.loadLibrary("durianjni");
}
}
durianUtils.h
#include
/* Header for class com_durian_jnienv_lib_DurianJni */
#ifndef _Included_com_durian_jnienv_lib_DurianUtils
#define _Included_com_durian_jnienv_lib_DurianUtils
#ifdef __cplusplus
extern "C" {
#endif
#include
#include
#include
#define LOG_TAG "durian"
#define LOGI(...) __android_log_print(ANDROID_LOG_INFO,LOG_TAG,__VA_ARGS__)
#define LOGD(...) __android_log_print(ANDROID_LOG_DEBUG, LOG_TAG, __VA_ARGS__)
#define LOGW(...) __android_log_print(ANDROID_LOG_WARN,LOG_TAG,__VA_ARGS__)
#define LOGE(...) __android_log_print(ANDROID_LOG_ERROR,LOG_TAG,__VA_ARGS__)
#define LOGF(...) __android_log_print(ANDROID_LOG_FATAL,LOG_TAG,__VA_ARGS__)
extern char* Jstring2CStr(JNIEnv* env, jstring jstr);
#ifdef __cplusplus
}
#endif
#endif
durianUtils.c
#include
#include
#include "durianUtils.h"
char* Jstring2CStr(JNIEnv* env, jstring jstr) {
char* rtn = NULL;
jclass clsstring = (env)->FindClass( "java/lang/String");
jstring strencode = (env)->NewStringUTF("GB2312");
jmethodID mid = (env)->GetMethodID(clsstring, "getBytes",
"(Ljava/lang/String;)[B");
jbyteArray barr = (jbyteArray)(env)->CallObjectMethod(jstr, mid,
strencode); // String .getByte("GB2312");
jsize alen = (env)->GetArrayLength(barr);
jbyte* ba = (env)->GetByteArrayElements(barr, JNI_FALSE);
if (alen > 0) {
rtn = (char*) malloc(alen + 1); //new char[alen+1]; "\0"
memcpy(rtn, ba, alen);
rtn[alen] = 0;
}
(env)->ReleaseByteArrayElements(barr, ba, 0); //释放内存
return rtn;
}
jni_onload.h
#include
#ifndef _ON_LOAD_HEADER_H__
#define _ON_LOAD_HEADER_H__
#ifdef __cplusplus
extern "C" {
#endif
#include "durianUtils.h"
JNIEnv* getJNIEnv();
int jniThrowException(JNIEnv *env,const char* className,const char* msg);
int jniRegisterNativeMethods(JNIEnv* env,const char* className,const JNINativeMethod* gMethod,int numMethods);
#ifdef __cplusplus
}
#endif
#endif
jni_onload.c
#include "jni_onload.h"
#include "durian_onload.h"
extern int register_android_jni_durian_android(JNIEnv *env);
/*
extern int register_android_jni_durian_android_1(JNIEnv *env);
...
extern int register_android_jni_durian_android_n(JNIEnv *env);
*/
static JavaVM *sEnv;
int jniThrowException(JNIEnv *env, const char* className, const char* msg) {
jclass exceptionClass = env->FindClass(className);
if (exceptionClass == NULL) {
return -1;
}
if (env->ThrowNew(exceptionClass, msg) != JNI_OK) {
}
return 0;
}
JNIEnv* getJNIEnv() {
JNIEnv* env = NULL;
if (sEnv->GetEnv((void**) &env, JNI_VERSION_1_4) != JNI_OK) {
return NULL;
}
return env;
}
int jniRegisterNativeMethods(JNIEnv* env, const char* className,
const JNINativeMethod* gMethods, int numMethods) {
jclass clazz;
clazz = env->FindClass(className);
if (clazz == NULL) {
return -1;
}
if (env->RegisterNatives(clazz, gMethods, numMethods) < 0) {
return -1;
}
return 0;
}
jint JNI_OnLoad(JavaVM* vm, void* reserved) {
JNIEnv* env = NULL;
jint result = JNI_ERR;
sEnv = vm;
if (vm->GetEnv((void**) &env, JNI_VERSION_1_4) != JNI_OK) {
return result;
}
LOGI("JNI_OnLoad...");
/*ready to register all methods
*/
if (register_android_jni_durian_android(env) != JNI_OK) {
goto end;
}
//following could do it as the previous the way of register
/*if (register_android_jni_durian_android_1(env) != JNI_OK) {
goto end;
}
...
if (register_android_jni_durian_android_n(env) != JNI_OK) {
goto end;
}*/
return JNI_VERSION_1_4;
end: return result;
}
durian_onload.c
#include"jni_onload.h"
#include "durian_onload.h"
static const char *classpath = "com/durian/jnionload/lib/DurianOnLoad";
namespace android {
float getDurianLibVersion() {
return 12.5;
}
jint DurianAdd(JNIEnv *env, jobject thiz, jint a, jint b) {
LOGI("a : %d b : %d",a,b);
return (int)(a+b);
}
int DurianEqual(JNIEnv *env, jobject thiz,int eu){
return eu*2;
}
float DurianEqualF(JNIEnv *env, jobject thiz,float ab,float cd){
return ab+cd;
}
}
using namespace android;
/*typedef struct {
const char* name;
const char* signature;
void* fnPtr;
} JNINativeMethod;
第一个变量name是Java中函数的名字。
第二个变量signature,用字符串是描述了函数的参数和返回值
第三个变量fnPtr是函数指针,指向C函数。
其中比较难以理解的是第二个参数,例如
"()V"
"(II)V"
"(Ljava/lang/String;Ljava/lang/String;)V"
实际上这些字符是与函数的参数类型一一对应的。
"()" 中的字符表示参数,后面的则代表返回值。例如"()V" 就表示void Func();
"(II)V" 表示 void Func(int, int);*/
static JNINativeMethod mMethods[] = {
{ "getDurianLibVersion", "()F",(void *) getDurianLibVersion },
{ "DurianAdd", "(II)I",(void *) DurianAdd },
{ "DurianEqual","(I)I",(void *) DurianEqual},
{ "DurianEqualF","(FF)F",(void *) DurianEqualF}
};
int register_android_jni_durian_android(JNIEnv* env) {
return jniRegisterNativeMethods(env, classpath, mMethods,sizeof(mMethods) / sizeof(mMethods[0]));
}
durian_onload.h这个倒是不是必须的,只是方便查询Signature信息:
/* DO NOT EDIT THIS FILE - it is machine generated */
#include
/* Header for class com_durian_jnionload_lib_DurianOnLoad */
#ifndef _Included_com_durian_jnionload_lib_DurianOnLoad
#define _Included_com_durian_jnionload_lib_DurianOnLoad
#ifdef __cplusplus
extern "C" {
#endif
/*
* Class: com_durian_jnionload_lib_DurianOnLoad
* Method: getDurianLibVersion
* Signature: ()F
*/
JNIEXPORT jfloat JNICALL Java_com_durian_jnionload_lib_DurianOnLoad_getDurianLibVersion
(JNIEnv *, jobject);
/*
* Class: com_durian_jnionload_lib_DurianOnLoad
* Method: DurianAdd
* Signature: (II)I
*/
JNIEXPORT jint JNICALL Java_com_durian_jnionload_lib_DurianOnLoad_DurianAdd
(JNIEnv *, jobject, jint, jint);
#ifdef __cplusplus
}
#endif
#endif
Android.mk :
LOCAL_PATH := $(call my-dir)
include $(CLEAR_VARS)
LOCAL_SHARED_LIBRARY := libnativehelper
LOCAL_MODULE :=durianjni
LOCAL_SRC_FILES := jni_onload.cpp
LOCAL_SRC_FILES += durianUtils.cpp
LOCAL_SRC_FILES += durian_onload.cpp
LOCAL_LDLIBS := -llog
include $(BUILD_SHARED_LIBRARY)
最后主要Android程序如下:
package com.durian.jnionload;
import android.app.Activity;
import android.os.Bundle;
import android.util.Log;
import android.view.Menu;
import com.durian.jnionload.lib.DurianOnLoad;
public class DurianMainActivity extends Activity {
private final static String TAG="DurianMainActivity";
private DurianOnLoad mDurianOnLoad;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.durian_main);
mDurianOnLoad=new DurianOnLoad();
float version=mDurianOnLoad.getDurianLibVersion();
int sum=mDurianOnLoad.DurianAdd(1, 12);
float versions=mDurianOnLoad.getDurianLibVersion();
int eu=mDurianOnLoad.DurianEqual(10);
float ef=mDurianOnLoad.DurianEqualF(10.0f,23.0f);
Log.i(TAG,"sum : "+sum +" versions : "+versions+" eu : "+eu+" ef : "+ef);
}
@Override
public boolean onCreateOptionsMenu(Menu menu) {
// Inflate the menu; this adds items to the action bar if it is present.
getMenuInflater().inflate(R.menu.durian_main, menu);
return true;
}
}
<3> :上面很奇怪,我记得我以前在linux下编译so的时候,不需要将方法写成如下形式,如下:
int DurianEqual(JNIEnv *env, jobject thiz,int eu){
return eu*2;
}
只需要写成真正纯c/c++语言的:
int DurianEqual(int eu){
return eu*2;
}
即函数前面两个参数不需要,今天奇怪了,难道是在windows下和linux下编译环境有差异的结果吗?后面我再到linux下面去验证一下,看来windows下做这样的project还是挺奇葩的.
运行上面的结果: