栏目分类:
子分类:
返回
名师互学网用户登录
快速导航关闭
当前搜索
当前分类
子分类
实用工具
热门搜索
名师互学网 > IT > 软件开发 > 后端开发 > C/C++/C#

JNI使用探究

C/C++/C# 更新时间: 发布时间: IT归档 最新发布 模块sitemap 名妆网 法律咨询 聚返吧 英语巴士网 伯小乐 网商动力

JNI使用探究

JNI使用探究

​ JNI是Java Native Interface的缩写,也就是java与native语言的交互,一般Android中,native就是C++。JNI在Android中,主要负责framework/base仓和其他native代码的交互。使用起来并不复杂,但是还是需要了解一些特定写法。

数据类型

JNI的针对数据类型是有专门定义的,也是属于JNI的一些专门写法吧。注册的native函数入参和返回值都是要用这些JNI类型来传递的。以下是对照表:

Java类型JNI类型描述
booleanJboolean无符号8位
byteJbyte无符号8位
charJchar无符号16位
shortJshort有符号16位
intJint有符号32位
longJlong有符号64位
floatJfloat有符号32位
doubleJdouble有符号64位

​ 表1 - 基本类型对照表

Java类型JNI类型
boolean[]JbooleanArray
byte[]JbyteArray
char[]JcharArray
short[]JshortArray
int[]JintArray
long[]JlongArray
float[]JfloatArray
double[]JdoubleArray
Objectjobject
java.lang.Classjclass
java.lang.Stringjstring
Object[]jobjectArray
java.lang.Throwablejthrowable

​ 表2 - 引用类型对照表

初始化 Android.mk

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)
JNI_OnLoad

​ 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类型类型签名
booleanZ
byteB
charC
shortS
intI
longL
floatF
doubleD
类(String)Ljava/lang/util/String;
数组(String[])[Ljava/lang/util/String;

​ 表3 - 类型签名对照表

以上代码注册完成后,JAVA端就可以通过native函数调用到相应的C++函数了。

JNI函数
static void
android_media_MediaPlayer_setVideoSurface(JNIEnv *env, jobject thiz, jobject jsurface)
{
    setVideoSurface(env, thiz, jsurface, true );
}

函数必须与方法签名中定义的一样,包括函数名,返回值,和参数。可以再看下定义的:

   {"_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'
JNIEnv

所有的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回调

​ 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 =new SinkCallback();
    sp p = getPlayer(env, thiz);
    p->setCallback(sinkCallback);

}

以上做了三步:

  1. env->GetJavaVM(&g_VM);通过这里的JNIEnv获取JVM句柄,并存入全局变量g_VM中。(JavaVM *g_VM;)
  2. 将ISinkPlayerCallback对象存入全局变量mCallback中。(jobject mCallback;)
  3. JNI处的SinkCallback对象设置给C++逻辑层。

那么看下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 =new SinkCallback();
sp p = getPlayer(env, thiz);
p->setCallback(sinkCallback);

将SinkCallback传入c++逻辑层,

status_t SinkPlayer::setCallback(const sp& 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类存入全局,供回调的时候调用。

转载请注明:文章转载自 www.mshxw.com
本文地址:https://www.mshxw.com/it/869279.html
我们一直用心在做
关于我们 文章归档 网站地图 联系我们

版权所有 (c)2021-2022 MSHXW.COM

ICP备案号:晋ICP备2021003244-6号