当前位置:   article > 正文

JNI使用探究_jni detected error in application

jni detected error in application

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)
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
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;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15

同时还需要将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;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12

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},
};
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8

​ 以上是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 /* mediaPlayerMustBeAlive */);
}
  • 1
  • 2
  • 3
  • 4
  • 5

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

   {"_setVideoSurface", "(Landroid/view/Surface;)V", (void *)android_media_MediaPlayer_setVideoSurface},
  • 1

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'
  • 1
JNIEnv

所有的JNI调用都使用了JNIEnv*类型的指针,总结一下JNIEnv的主要函数

  • GetStringUTFChars:返回指向字符串UTF编码的指针,如果不能创建这个字符数组,返回null。这个指针在调用ReleaseStringUTFChar()函数之前一直有效。

    const char *tmp = env->GetStringUTFChars(host, NULL);
    
    • 1
  • ReleaseStringUTFChars:通知虚拟机本地代码不再需要通过chars访问Java字符串,与GetStringUTFChars配套使用

    env->ReleaseStringUTFChars(host, tmp);
    
    • 1
  • GetJavaVM:获取JVM对象

    env->GetJavaVM(&g_VM);
    
    • 1
  • NewGlobalRef:创建全局变量,用完需要调用DeleteGlobalRef

    jobject mCallback = (jobject)env->NewGlobalRef(jCallback);
    
    • 1
  • GetObjectClass:根据JAVA传入的jobject对象,获取该JAVA类

    jclass thisClass = env->GetObjectClass(mCallback);
    
    • 1
  • GetMethodID:根据class获取该类的某个方法

    jmethodID midCallBack = env->GetMethodID(thisClass, "signalTearDown", "()V");
    
    • 1
  • CallVoidMethod(jclass , jmethodID ,XXXX):调用java类的方法midCallBack,参数不定长,调用的方法入参,接在函数参数里。

    env->CallVoidMethod(mCallback, midCallBack);
    
    • 1
  • DeleteGlobalRef:删除全局变量

    env->DeleteGlobalRef(mCallback);
    
    • 1

待补充

JNI回调

​ JNI比较常见的就是回调JAVA层,毕竟由于C++和JAVA之间类型差异,没有办法直接传下JAVA类,可供C++直接回调,所以我的办法就是在JNI处做一个"中转处理"。首先JAVA层定义一个类:ISinkPlayerCallback.java

class ISinkPlayerCallback {
    public void signalTearDown() {
	Log.e("cyl","signalTearDown callback");
	}
}
  • 1
  • 2
  • 3
  • 4
  • 5

然后看下之前写过的注册到JNI的方法签名,JAVA层通过native_setCallback传下ISinkPlayerCallback对象。

{"native_setCallback", "(Lcom/cyl/miracast/bean/ISinkPlayerCallback;)V", (void*)cyl_miracast_sinkplayer_setCallback},
  • 1

然后看下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);

}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11

以上做了三步:

  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(){};
};
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7

然后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(){}
};

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23

再回到之前cyl_miracast_sinkplayer_setCallback函数:

sp<SinkCallback> sinkCallback =new SinkCallback();
sp<SinkPlayer> p = getPlayer(env, thiz);
p->setCallback(sinkCallback);
  • 1
  • 2
  • 3

将SinkCallback传入c++逻辑层,

status_t SinkPlayer::setCallback(const sp<ISinkPlayerCallback>& callback) {
    ALOGI("callback is null=%d",(callback==NULL));
    mSinkCallback = callback;
    return OK;
}
  • 1
  • 2
  • 3
  • 4
  • 5
 mSink = new WifiDisplaySink(mNetSession, mSurfaceTexture,mSinkCallback);
  • 1

WifiDisplaySink.cpp:

            if(mSinkCallback != NULL) {
               mSinkCallback->signalTearDown();
            }
  • 1
  • 2
  • 3

以上就形成了一个回调的流程,C++和JAVA层两个Callback类,在JNI处做了一个中转,比较重要的就是要将JAVA设置下来的Callback类存入全局,供回调的时候调用。

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/weixin_40725706/article/detail/158496
推荐阅读
相关标签
  

闽ICP备14008679号