赞
踩
JNI是Java Native Interface的缩写,也就是java与native语言的交互,一般Android中,native就是C++。JNI在Android中,主要负责framework/base仓和其他native代码的交互。使用起来并不复杂,但是还是需要了解一些特定写法。
JNI的针对数据类型是有专门定义的,也是属于JNI的一些专门写法吧。注册的native函数入参和返回值都是要用这些JNI类型来传递的。以下是对照表:
Java类型 | JNI类型 | 描述 |
---|---|---|
boolean | Jboolean | 无符号8位 |
byte | Jbyte | 无符号8位 |
char | Jchar | 无符号16位 |
short | Jshort | 有符号16位 |
int | Jint | 有符号32位 |
long | Jlong | 有符号64位 |
float | Jfloat | 有符号32位 |
double | Jdouble | 有符号64位 |
表1 - 基本类型对照表
Java类型 | JNI类型 |
---|---|
boolean[] | JbooleanArray |
byte[] | JbyteArray |
char[] | JcharArray |
short[] | JshortArray |
int[] | JintArray |
long[] | JlongArray |
float[] | JfloatArray |
double[] | JdoubleArray |
Object | jobject |
java.lang.Class | jclass |
java.lang.String | jstring |
Object[] | jobjectArray |
java.lang.Throwable | jthrowable |
表2 - 引用类型对照表
JNI注册主要还是依赖libandroid_runtime和libnativehelper。这里有一些native代码需要的依赖,就不删了,一并贴上:
LOCAL_PATH:= $(call my-dir) include $(CLEAR_VARS) LOCAL_SRC_FILES:= \ SinkPlayer.cpp \ ivygroup_wfdplayer_sinkplayer.cpp \ LOCAL_C_INCLUDES:= \ $(TOP)/frameworks/av/include/media/stagefright \ $(TOP)/frameworks/av/media/libstagefright/wifi-display/sink/sink \ $(TOP)/frameworks/native/include/media/openmax \ $(TOP)/frameworks/av/media/libstagefright/mpeg2ts \ $(PV_INCLUDES) \ $(JNI_H_INCLUDE) \ $(call include-path-for, corecg graphics) LOCAL_SHARED_LIBRARIES:= \ libandroid_runtime \ libstagefright_wfd_sink \ libstagefright_foundation \ libutils \ libnativehelper \ libui \ libgui \ libskia \ liblog \ LOCAL_PROGUARD_ENABLED:= disabled LOCAL_MODULE:= libwfd_jni LOCAL_MODULE_TAGS:= optional include $(BUILD_SHARED_LIBRARY)
Android的APP或者framework java层代码是通过System.loadLibrary来加载JNI的so,而此时Android虚拟机首先会执行so中的JNI_OnLoad函数。所以JNI的注册一般需要重写JNI_OnLoad(),获取JNIEnv.JNIEnv代表java环境,通过JNIEnv*指针就可以对java端的代码进行操作。
jint JNI_OnLoad(JavaVM* vm, void* reserved) {
JNIEnv* env = NULL;
jint result = -1;
if (vm->GetEnv((void**) &env, JNI_VERSION_1_4) != JNI_OK) {
return result;
}
if (env == NULL) {
return result;
}
if (registerNativeMethods(env) != 0) {
return result;
}
result = JNI_VERSION_1_4;
return result;
}
同时还需要将native函数注册到java层的类里去,我们可以通过JNIEnv的RegisterNatives函数。正如前面表格定义的那样,java中的class在JNI中类型为jclass,可以通过FindClass(包名)来获取类,传入RegisterNatives中。
int registerNativeMethods(JNIEnv* env) {
int result = -1;
jclass clazz = env->FindClass("com/cyl/miracast/SinkPlayer");
if (NULL != clazz) {
if (env->RegisterNatives(clazz, nativeMethods, sizeof(nativeMethods)
/ sizeof(nativeMethods[0])) == JNI_OK) {
result = 0;
}
}
return result;
}
nativeMethods:
static JNINativeMethod nativeMethods[] = {
// {"native_possibleEncoding", "([B)Ljava/lang/String;", (void*)possibleEncoding}
{"native_init", "()V", (void *)cyl_miracast_sinkplayer_native_init},
{"_release", "()V", (void *)cyl_miracast_sinkplayer_release},
{"_setVideoSurface", "(Landroid/view/Surface;)V", (void *)android_media_MediaPlayer_setVideoSurface},
{"native_startSink", "(Ljava/lang/String;I)V", (void*)cyl_miracast_sinkplayer_startSink},
{"native_setCallback", "(Lcom/cyl/miracast/bean/ISinkPlayerCallback;)V",(void*)cyl_miracast_sinkplayer_setCallback},
};
以上是JNI方法的特定写法,也就是方法签名,第一个字段为注册成JNI方法的名字,也就是java层调用的方法名。第二个字段为参数和返回值,也就是类型签名。第三个字段为该方法所对应执行的具体C++层函数。
方法签名是为了让JNI能更好地区分JAVA中的重载方法。所以其中更重要的就是类型签名,如上代码所示,()中为方法参数,()后为返回值,规则如下:
多个基本参数可以连续,比如:3个boolean 参数,可以这么写(ZZZ)。
JAVA类等复杂类型需要L加上全限定名,末尾需要加上;,即包名+类名,比如:参数为java中定义的类ISinkPlayerCallback,可以这么写(Lcom/cyl/miracast/bean/ISinkPlayerCallback;)。
数组需要在开头加上[,比如:byte数组,可以这么写([B)。
返回值写法与上面参数规则一致
以下是类型对照表:
Java类型 | 类型签名 |
---|---|
boolean | Z |
byte | B |
char | C |
short | S |
int | I |
long | L |
float | F |
double | D |
类(String) | Ljava/lang/util/String; |
数组(String[]) | [Ljava/lang/util/String; |
表3 - 类型签名对照表
以上代码注册完成后,JAVA端就可以通过native函数调用到相应的C++函数了。
static void
android_media_MediaPlayer_setVideoSurface(JNIEnv *env, jobject thiz, jobject jsurface)
{
setVideoSurface(env, thiz, jsurface, true /* mediaPlayerMustBeAlive */);
}
函数必须与方法签名中定义的一样,包括函数名,返回值,和参数。可以再看下定义的:
{"_setVideoSurface", "(Landroid/view/Surface;)V", (void *)android_media_MediaPlayer_setVideoSurface},
V对应返回值void,正常参数比如byte类型 B,这里第三个参数就应该是Jbyte,如果是其他复杂类型,比如上面的Surface类,在参数的地方都用jobject。
参数从第三个开始写。前面两个是固定的:JNIEnv env, jobject thiz,JNIEnv就是JNI的环境,提供了大量JNI函数可供使用。
第二个参数需要注意一下,第二个参数是调用者的Class,但本地验证证明这个类并不是这个JNI函数调用者的类,而是注册加载JNI的类。比如:该JNI是加载在SinkPlayer_A.java中的,而调用_setVideoSurface的类是SinkPlayer_B.java,那么这里的thiz就是SinkPlayer_A.java,想要调用SinkPlayer_B就需要在定义的时候,参数多一个SinkPlayer_B类参数。
这点需要特别注意,尤其是通过class获取method之后回调,class如果写错了,会出现如下类别错误:
11-01 00:51:27.219 3620 3620 F DEBUG : Abort message: 'java_vm_ext.cc:534] JNI DETECTED ERROR IN APPLICATION: can't call void com.cyl.miracast.bean.ISinkPlayerCallback.signalTearDown() on instance of com.cyl.miracast.SinkPlayer'
所有的JNI调用都使用了JNIEnv*类型的指针,总结一下JNIEnv的主要函数
GetStringUTFChars:返回指向字符串UTF编码的指针,如果不能创建这个字符数组,返回null。这个指针在调用ReleaseStringUTFChar()函数之前一直有效。
const char *tmp = env->GetStringUTFChars(host, NULL);
ReleaseStringUTFChars:通知虚拟机本地代码不再需要通过chars访问Java字符串,与GetStringUTFChars配套使用
env->ReleaseStringUTFChars(host, tmp);
GetJavaVM:获取JVM对象
env->GetJavaVM(&g_VM);
NewGlobalRef:创建全局变量,用完需要调用DeleteGlobalRef
jobject mCallback = (jobject)env->NewGlobalRef(jCallback);
GetObjectClass:根据JAVA传入的jobject对象,获取该JAVA类
jclass thisClass = env->GetObjectClass(mCallback);
GetMethodID:根据class获取该类的某个方法
jmethodID midCallBack = env->GetMethodID(thisClass, "signalTearDown", "()V");
CallVoidMethod(jclass , jmethodID ,XXXX):调用java类的方法midCallBack,参数不定长,调用的方法入参,接在函数参数里。
env->CallVoidMethod(mCallback, midCallBack);
DeleteGlobalRef:删除全局变量
env->DeleteGlobalRef(mCallback);
待补充
JNI比较常见的就是回调JAVA层,毕竟由于C++和JAVA之间类型差异,没有办法直接传下JAVA类,可供C++直接回调,所以我的办法就是在JNI处做一个"中转处理"。首先JAVA层定义一个类:ISinkPlayerCallback.java
class ISinkPlayerCallback {
public void signalTearDown() {
Log.e("cyl","signalTearDown callback");
}
}
然后看下之前写过的注册到JNI的方法签名,JAVA层通过native_setCallback传下ISinkPlayerCallback对象。
{"native_setCallback", "(Lcom/cyl/miracast/bean/ISinkPlayerCallback;)V", (void*)cyl_miracast_sinkplayer_setCallback},
然后看下cyl_miracast_sinkplayer_setCallback
static void cyl_miracast_sinkplayer_setCallback(JNIEnv *env, jobject thiz,jobject jCallback)
{
//JavaVM是虚拟机在JNI中的表示,等下再其他线程回调java层需要用到
env->GetJavaVM(&g_VM);
mCallback = (jobject)env->NewGlobalRef(jCallback);
sp<SinkCallback> sinkCallback =new SinkCallback();
sp<SinkPlayer> p = getPlayer(env, thiz);
p->setCallback(sinkCallback);
}
以上做了三步:
那么看下SinkCallback的定义,先看看父类ISinkPlayerCallback:
namespace android {
class ISinkPlayerCallback: virtual public RefBase {
public:
virtual void signalTearDown();
virtual ~ISinkPlayerCallback(){};
};
}
然后SinkCallback继承ISinkPlayerCallback,SinkCallback中是实际执行回调的逻辑:
class SinkCallback :public ISinkPlayerCallback { public: void signalTearDown(){ JNIEnv *env; //通过之前setCallback存的全局变量g_VM获取JNIEnv对象 if (g_VM->AttachCurrentThread(&env, NULL) != JNI_OK) { ALOGE("%s: AttachCurrentThread() failed", __FUNCTION__); return; } if(mCallback != NULL) { //通过之前setCallback存的全局变量mCallback获取之前设置下来的ISinkPlayerCallback(JAVA)类 jclass thisClass = env->GetObjectClass(mCallback); //根据ISinkPlayerCallback(JAVA)类获取signalTearDown方法 jmethodID midCallBack = env->GetMethodID(thisClass, "signalTearDown", "()V"); //回调ISinkPlayerCallback(JAVA)的signalTearDown env->CallVoidMethod(mCallback, midCallBack); //释放全局变量 env->DeleteGlobalRef(mCallback); } } ~SinkCallback(){} };
再回到之前cyl_miracast_sinkplayer_setCallback函数:
sp<SinkCallback> sinkCallback =new SinkCallback();
sp<SinkPlayer> p = getPlayer(env, thiz);
p->setCallback(sinkCallback);
将SinkCallback传入c++逻辑层,
status_t SinkPlayer::setCallback(const sp<ISinkPlayerCallback>& callback) {
ALOGI("callback is null=%d",(callback==NULL));
mSinkCallback = callback;
return OK;
}
mSink = new WifiDisplaySink(mNetSession, mSurfaceTexture,mSinkCallback);
WifiDisplaySink.cpp:
if(mSinkCallback != NULL) {
mSinkCallback->signalTearDown();
}
以上就形成了一个回调的流程,C++和JAVA层两个Callback类,在JNI处做了一个中转,比较重要的就是要将JAVA设置下来的Callback类存入全局,供回调的时候调用。
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。