关闭 x
IT技术网
    技 采 号
    ITJS.cn - 技术改变世界
    • 实用工具
    • 菜鸟教程
    IT采购网 中国存储网 科技号 CIO智库

    IT技术网

    IT采购网
    • 首页
    • 行业资讯
    • 系统运维
      • 操作系统
        • Windows
        • Linux
        • Mac OS
      • 数据库
        • MySQL
        • Oracle
        • SQL Server
      • 网站建设
    • 人工智能
    • 半导体芯片
    • 笔记本电脑
    • 智能手机
    • 智能汽车
    • 编程语言
    IT技术网 - ITJS.CN
    首页 » 安卓开发 »Android Framework层JNI的使用浅析

    Android Framework层JNI的使用浅析

    2015-01-05 00:00:00 出处:LittlePanpc的博客
    分享

    JNI技术对于多java开发的朋友相信并不陌生,即(java native interface),本地调用接口,主要功能有以下两点:

    1、java层调用C/C++层代码

    2、C/C++层调用java层代码

    可能有些人会觉得jni技术破坏了Java语言的跨平台性,有这种想法可能是因为你对java理解得还不够深,假如你看看jdk源码,你会发现在jdk里面大量使用了jni技术,而且java虚拟机就是用本地语言写的,所以导致jvm并不能跨平台性,所以说java的跨平台性并不是100%的跨平台的。相反你应该看到使用Jni的优势:

    1、因为C/C++语言本来机比java语言诞生早,所以很多库代码都是使用C/C++写的,有了Jni我们就可以直接使用了,不用重复造轮子。

    2、不可否认,C/C++执行效率比java 高,对于一些对效率有要求的功能,必须使用C/C++.

    由于打算研究Android 中java层和native层是如何连接起来的,所以想研究一下Android中的jni技术(在阅读之前,最好了解jni中的基本知识,如jni中数据类型,签名格式,不然看起来可能有些吃力),由于工作和MediaPlayer有关,这里就使用MediaPlayer为例吧。

    下面给出一张图,通过此图,我们简要说明一下jni是如何连接Java层和本地层的。

    当我们的app要播放视频的时候,我们使用的是java层的MediaPlayer类,我们进入到MediaPlayer.java看看(提醒:我这里使用的是源码4.1)

    主要注意的有两点:

    1、静态代码块:

     static {
            System.loadLibrary("media_jni");
            native_init();
        }

    2、native_init的签名:

    private static native final void native_init();

    看到静态代码块后,我们可以知道MediaPlayer对应的jni层代码在Media_jni.so库中

    本地层对应的so库是libmedia.so,所以MediaPlayer.java通过Media_jni.so和MediaPlayer.cpp(libmedia.so)进行交互

    下面我们就深入到细节吧。不过在深入细节前,我先要告诉你一个规则,在Android中,通常java层类和jni层类的名字有如下关系,拿MediaPlayer为例,java层叫android.media.MediaPlayer.java,那么jni层叫做android_media_MediaPlayer.cpp

    由于native_init是一个本地方法,那么我们就到android_media_MediaPlayer.cpp找到native_init的对应方法吧

    static void
    android_media_MediaPlayer_native_init(JNIEnv *env)
    {
        jclass clazz;
    
        clazz = env->FindClass("android/media/MediaPlayer");
        if (clazz == NULL) {
            return;
        }
    
        fields.context = env->GetFieldID(clazz, "mNativeContext", "I");
        if (fields.context == NULL) {
            return;
        }
    
        fields.post_event = env->GetStaticMethodID(clazz, "postEventFromNative",
                                                   "(Ljava/lang/Object;IIILjava/lang/Object;)V");
        if (fields.post_event == NULL) {
            return;
        }
    
        fields.surface_texture = env->GetFieldID(clazz, "mNativeSurfaceTexture", "I");
        if (fields.surface_texture == NULL) {
            return;
        }
    }

    对应上面的代码,假如你对java中的反射理解得很透彻的话,其实很好理解,首先找到java层的MediaPlayer的Class对象,jclass是java层Class在native层的代码,然后分别保存mNaviceContext字段,postEventFromNative方法,mNativeSurfaceTexture字段。

    其实这里我最想说明的是另外一个问题,就是MediaPlayer中的native_init方法时如何跟android_media_MediaPlayer.cpp中的android_media_MediaPlayer_native_init对应起来的,因为我们知道假如使用javah自动生成的头文件,那么在jni层的名字应该是java_android_media_MediaPlayer_native_linit。其实这里涉及到一个动态注册的过程。

    其实在java层代用System.loadLibrary成功后,就会调用jni文件中的JNI_onLoad方法,android_media_MediaPlayer.cpp中的JNI_onLoad方法如下(截取部分)

    jint JNI_OnLoad(JavaVM* vm, void* reserved)
    {
        JNIEnv* env = NULL;
        jint result = -1;
    
        if (vm->GetEnv((void**) &env, JNI_VERSION_1_4) != JNI_OK) {
            ALOGE("ERROR: GetEnv failedn");
            goto bail;
        }
        assert(env != NULL);
    
        if (register_android_media_MediaPlayer(env) < 0) {
            ALOGE("ERROR: MediaPlayer native registration failedn");
            goto bail;
        }
    
        /* success -- return valid version number */
        result = JNI_VERSION_1_4;
    
    bail:
        return result;
    }

    这里有一个方法叫做register_android_media_MediaPlayer,我们进入此方法,看看注册了什么

    static int register_android_media_MediaPlayer(JNIEnv *env)
    {
        return AndroidRuntime::registerNativeMethods(env,
                    "android/media/MediaPlayer", gMethods, NELEM(gMethods));
    }

    这里就是调用了AndroidRuntime提供的registerNativeMethods方法,这里涉及到一个gMethods的变量,它其实是一个结构体

    typedef struct {
    const char* name;
    const char* signature;
    void* fnPtr;
    } JNINativeMethod;

    name:就是在java层方法名称

    signature:就是方法在签名

    fnPtr:在jni层对应的函数名称

    ,那么我们找到native_init在gMethods对应的值吧

    static JNINativeMethod gMethods[] = {
        {
            "_setDataSource",
            "(Ljava/lang/String;[Ljava/lang/String;[Ljava/lang/String;)V",
            (void *)android_media_MediaPlayer_setDataSourceAndHeaders
        },
    
    	....
        {"native_init",         "()V",                              (void *)android_media_MediaPlayer_native_init},
        ...
    };

    接下来,我们看看AndroidRuntime中的registerNativeMethods做了什么吧

    /*static*/ int AndroidRuntime::registerNativeMethods(JNIEnv* env,
        const char* className, const JNINativeMethod* gMethods, int numMethods)
    {
        return jniRegisterNativeMethods(env, className, gMethods, numMethods);
    }

    调用了jniRegisterNativeMethods

    extern "C" int jniRegisterNativeMethods(C_JNIEnv* env, const char* className,
        const JNINativeMethod* gMethods, int numMethods)
    {
        JNIEnv* e = reinterpret_cast<JNIEnv*>(env);
    
        ALOGV("Registering %s natives", className);
    
        scoped_local_ref<jclass> c(env, findClass(env, className));
        if (c.get() == NULL) {
            ALOGE("Native registration unable to find class '%s', aborting", className);
            abort();
        }
    
        if ((*env)->RegisterNatives(e, c.get(), gMethods, numMethods) < 0) {
            ALOGE("RegisterNatives failed for '%s', aborting", className);
            abort();
        }
    
        return 0;
    }

    最终调用了env的RegisterNativers完成了注册。

    其实写到这里,我们已经知道了java层和jni是如何联系起来的,接下来我想说的是jni是如何将java层和native联系起来的,还是用MediaPlayer为例吧,我们进入MediaPlayer的构造函数。

        public MediaPlayer() {
    
            Looper looper;
            if ((looper = Looper.myLooper()) != null) {
                mEventHandler = new EventHandler(this, looper);
            } else if ((looper = Looper.getMainLooper()) != null) {
                mEventHandler = new EventHandler(this, looper);
            } else {
                mEventHandler = null;
            }
    
            /* Native setup requires a weak reference to our object.
             * It's easier to create it here than in C++.
             */
            native_setup(new WeakReference<MediaPlayer>(this));
        }

    这里创建了一个mEventHandler对象,并调用了native_setup方法,我们进入到android_media_MediaPlayer.cpp的对应方法看看

    static void
    android_media_MediaPlayer_native_setup(JNIEnv *env, jobject thiz, jobject weak_this)
    {
        ALOGV("native_setup");
        sp<MediaPlayer> mp = new MediaPlayer();
        if (mp == NULL) {
            jniThrowException(env, "java/lang/RuntimeException", "Out of memory");
            return;
        }
    
        // create new listener and give it to MediaPlayer
        sp<JNIMediaPlayerListener> listener = new JNIMediaPlayerListener(env, thiz, weak_this);
        mp->setListener(listener);
    
        // Stow our new C++ MediaPlayer in an opaque field in the Java object.
        setMediaPlayer(env, thiz, mp);
    }

    这里创建了一个本地MediaPlayer对象,并且设置了listener,(假如做过播放器的同学应该知道这个listener应该知道干啥,不知道也没关系),最后调用了setMediaPlayer方法,这个才是我们需要关注的。

    static sp<MediaPlayer> setMediaPlayer(JNIEnv* env, jobject thiz, const sp<MediaPlayer>& player)
    {
        Mutex::Autolock l(sLock);
        sp<MediaPlayer> old = (MediaPlayer*)env->GetIntField(thiz, fields.context);
        if (player.get()) {
            player->incStrong(thiz);
        }
        if (old != 0) {
            old->decStrong(thiz);
        }
        env->SetIntField(thiz, fields.context, (int)player.get());
        return old;
    }

    其实就是先拿到fields.context的对应的值,还记得这个这个值是什么吗,不记得的可以回到上面看看

    fields.context = env->GetFieldID(clazz, "mNativeContext", "I");

    其实就是java层mNativeContext对应的值,就是将本地MediaPlayer的地址存放到mNativeContext中。

    现在加入大家要播放一个本地Mp4视频,那么使用如下代码即可

    mediaPlayer.setDataSource("/mnt/sdcard/a.mp4");   
    mediaPlayer.setDisplay(surface1.getHolder());  
    mediaPlayer.prepare();  
    mediaPlayer.start();

    其实这里调用的 几个都是本地方法,这里我就是用prepare方法为例,讲解MediaPlaeyr.java和MediaPlayer.cpp的交互

    当在java层调用prepare方法时,在jni层会调用如下方法

    static void
    android_media_MediaPlayer_prepare(JNIEnv *env, jobject thiz)
    {
        sp<MediaPlayer> mp = getMediaPlayer(env, thiz);
        if (mp == NULL ) {
            jniThrowException(env, "java/lang/IllegalStateException", NULL);
            return;
        }
    
        // Handle the case where the display surface was set before the mp was
        // initialized. We try again to make it stick.
        sp<ISurfaceTexture> st = getVideoSurfaceTexture(env, thiz);
        mp->setVideoSurfaceTexture(st);
    
        process_media_player_call( env, thiz, mp->prepare(), "java/io/IOException", "Prepare failed." );
    }

    这里通过getMediaPlayer方法拿到本地的MediaPlayer对象,调用调用本地方法process_media_player_call,并将本地MediaPlayer调用parepare方法的结果传递给此方法。

    static void process_media_player_call(JNIEnv *env, jobject thiz, status_t opStatus, const char* exception, const char *message)
    {
        if (exception == NULL) {  // Don't throw exception. Instead, send an event.
            if (opStatus != (status_t) OK) {
                sp<MediaPlayer> mp = getMediaPlayer(env, thiz);
                if (mp != 0) mp->notify(MEDIA_ERROR, opStatus, 0);
            }
        } else {  // Throw exception!
            if ( opStatus == (status_t) INVALID_OPERATION ) {
                jniThrowException(env, "java/lang/IllegalStateException", NULL);
            } else if ( opStatus == (status_t) PERMISSION_DENIED ) {
                jniThrowException(env, "java/lang/SecurityException", NULL);
            } else if ( opStatus != (status_t) OK ) {
                if (strlen(message) > 230) {
                   // if the message is too long, don't bother displaying the status code
                   jniThrowException( env, exception, message);
                } else {
                   char msg[256];
                    // append the status code to the message
                   sprintf(msg, "%s: status=0x%X", message, opStatus);
                   jniThrowException( env, exception, msg);
                }
            }
        }
    }

    在这个里面根据prepare返回的状态,假如exception==null 并且prepare执行失败,测试不抛异常,而是调用本地MediaPlayer的notify方法。

    void MediaPlayer::notify(int msg, int ext1, int ext2, const Parcel *obj)
    {
        ALOGV("message received msg=%d, ext1=%d, ext2=%d", msg, ext1, ext2);
        bool send = true;
        bool locked = false;
    
       ...
    
        switch (msg) {
        case MEDIA_NOP: // interface test message
            break;
        case MEDIA_PREPARED:
            ALOGV("prepared");
            mCurrentState = MEDIA_PLAYER_PREPARED;
            if (mPrepareSync) {
                ALOGV("signal application thread");
                mPrepareSync = false;
                mPrepareStatus = NO_ERROR;
                mSignal.signal();
            }
            break;
        case MEDIA_PLAYBACK_COMPLETE:
            ALOGV("playback complete");
            if (mCurrentState == MEDIA_PLAYER_IDLE) {
                ALOGE("playback complete in idle state");
            }
            if (!mLoop) {
                mCurrentState = MEDIA_PLAYER_PLAYBACK_COMPLETE;
            }
            break;
        case MEDIA_ERROR:
            // Always log errors.
            // ext1: Media framework error code.
            // ext2: Implementation dependant error code.
            ALOGE("error (%d, %d)", ext1, ext2);
            mCurrentState = MEDIA_PLAYER_STATE_ERROR;
            if (mPrepareSync)
            {
                ALOGV("signal application thread");
                mPrepareSync = false;
                mPrepareStatus = ext1;
                mSignal.signal();
                send = false;
            }
            break;
        case MEDIA_INFO:
            // ext1: Media framework error code.
            // ext2: Implementation dependant error code.
            if (ext1 != MEDIA_INFO_VIDEO_TRACK_LAGGING) {
                ALOGW("info/warning (%d, %d)", ext1, ext2);
            }
            break;
        case MEDIA_SEEK_COMPLETE:
            ALOGV("Received seek complete");
            if (mSeekPosition != mCurrentPosition) {
                ALOGV("Executing queued seekTo(%d)", mSeekPosition);
                mSeekPosition = -1;
                seekTo_l(mCurrentPosition);
            }
            else {
                ALOGV("All seeks complete - return to regularly scheduled program");
                mCurrentPosition = mSeekPosition = -1;
            }
            break;
        case MEDIA_BUFFERING_UPDATE:
            ALOGV("buffering %d", ext1);
            break;
        case MEDIA_SET_VIDEO_SIZE:
            ALOGV("New video size %d x %d", ext1, ext2);
            mVideoWidth = ext1;
            mVideoHeight = ext2;
            break;
        case MEDIA_TIMED_TEXT:
            ALOGV("Received timed text message");
            break;
        default:
            ALOGV("unrecognized message: (%d, %d, %d)", msg, ext1, ext2);
            break;
        }
    
        sp<MediaPlayerListener> listener = mListener;
        if (locked) mLock.unlock();
    
        // this prevents re-entrant calls into client code
        if ((listener != 0) && send) {
            Mutex::Autolock _l(mNotifyLock);
            ALOGV("callback application");
            listener->notify(msg, ext1, ext2, obj);
            ALOGV("back from callback");
        }
    }

    做过播放器的同学应该对上面几个消息都不陌生吧,由于刚才调用prepare方法失败了,所以这里应该执行MEDIA_ERROR分支,最后调用listener的notify代码,这个listener就是在native_setup中设置的

    void JNIMediaPlayerListener::notify(int msg, int ext1, int ext2, const Parcel *obj)
    {
        JNIEnv *env = AndroidRuntime::getJNIEnv();
        if (obj && obj->dataSize() > 0) {
            jobject jParcel = createJavaParcelObject(env);
            if (jParcel != NULL) {
                Parcel* nativeParcel = parcelForJavaObject(env, jParcel);
                nativeParcel->setData(obj->data(), obj->dataSize());
                env->CallStaticVoidMethod(mClass, fields.post_event, mObject,
                        msg, ext1, ext2, jParcel);
            }
        } else {
            env->CallStaticVoidMethod(mClass, fields.post_event, mObject,
                    msg, ext1, ext2, NULL);
        }
        if (env->ExceptionCheck()) {
            ALOGW("An exception occurred while notifying an event.");
            LOGW_EX(env);
            env->ExceptionClear();
        }
    }

    还记得fields.post_event保存的是什么吗

    fields.post_event = env->GetStaticMethodID(clazz, "postEventFromNative",
                                                   "(Ljava/lang/Object;IIILjava/lang/Object;)V");

    就是java层MediaPlayer的postEventFromNative方法,也就是说假如播放出错了,那么就通过调用postEventFromNative方法来告诉java层的MediaPlayer。

        private static void postEventFromNative(Object mediaplayer_ref,
                                                int what, int arg1, int arg2, Object obj)
        {
            MediaPlayer mp = (MediaPlayer)((WeakReference)mediaplayer_ref).get();
            if (mp == null) {
                return;
            }
    
            if (what == MEDIA_INFO && arg1 == MEDIA_INFO_STARTED_AS_NEXT) {
                // this acquires the wakelock if needed, and sets the client side state
                mp.start();
            }
            if (mp.mEventHandler != null) {
                Message m = mp.mEventHandler.obtainMessage(what, arg1, arg2, obj);
                mp.mEventHandler.sendMessage(m);
            }
        }

    这个时间最终通过mEventHandler处理,也就是在我们app进程中处理这个错误。

    写到这里,相信你应该对java层和native层的交互有了导致的了解。

    上一篇返回首页 下一篇

    声明: 此文观点不代表本站立场;转载务必保留本文链接;版权疑问请联系我们。

    别人在看

    电脑屏幕不小心竖起来了?别慌,快捷键搞定

    Destoon 模板存放规则及语法参考

    Destoon系统常量与变量

    Destoon系统目录文件结构说明

    Destoon 系统安装指南

    Destoon会员公司主页模板风格添加方法

    Destoon 二次开发入门

    Microsoft 将于 2026 年 10 月终止对 Windows 11 SE 的支持

    Windows 11 存储感知如何设置?了解Windows 11 存储感知开启的好处

    Windows 11 24H2 更新灾难:系统升级了,SSD固态盘不见了...

    IT头条

    Synology 更新 ActiveProtect Manager 1.1 以增强企业网络弹性和合规性

    00:43

    新的 Rubrik Agent Cloud 加速了可信的企业 AI 代理部署

    00:34

    宇树科技 G1人形机器人,拉动一辆重达1.4吨的汽车

    00:21

    Cloudera 调查发现,96% 的企业已将 AI 集成到核心业务流程中,这表明 AI 已从竞争优势转变为强制性实践

    02:05

    投资者反对马斯克 1 万亿美元薪酬方案,要求重组特斯拉董事会

    01:18

    技术热点

    大型网站的 HTTPS 实践(三):基于协议和配置的优化

    ubuntu下右键菜单添加新建word、excel文档等快捷方式

    Sublime Text 简明教程

    用户定义SQL Server函数的描述

    怎么在windows 7开始菜单中添加下载选项?

    SQL Server 2016将有哪些功能改进?

      友情链接:
    • IT采购网
    • 科技号
    • 中国存储网
    • 存储网
    • 半导体联盟
    • 医疗软件网
    • 软件中国
    • ITbrand
    • 采购中国
    • CIO智库
    • 考研题库
    • 法务网
    • AI工具网
    • 电子芯片网
    • 安全库
    • 隐私保护
    • 版权申明
    • 联系我们
    IT技术网 版权所有 © 2020-2025,京ICP备14047533号-20,Power by OK设计网

    在上方输入关键词后,回车键 开始搜索。Esc键 取消该搜索窗口。