关闭 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层的交互有了导致的了解。

    上一篇返回首页 下一篇

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

    别人在看

    正版 Windows 11产品密钥怎么查找/查看?

    还有3个月,微软将停止 Windows 10 的更新

    Windows 10 终止支持后,企业为何要立即升级?

    Windows 10 将于 2025年10 月终止技术支持,建议迁移到 Windows 11

    Windows 12 发布推迟,微软正全力筹备Windows 11 25H2更新

    Linux 退出 mail的命令是什么

    Linux 提醒 No space left on device,但我的空间看起来还有不少空余呢

    hiberfil.sys文件可以删除吗?了解该文件并手把手教你删除C盘的hiberfil.sys文件

    Window 10和 Windows 11哪个好?答案是:看你自己的需求

    盗版软件成公司里的“隐形炸弹”?老板们的“法务噩梦” 有救了!

    IT头条

    公安部:我国在售汽车搭载的“智驾”系统都不具备“自动驾驶”功能

    02:03

    液冷服务器概念股走强,博汇、润泽等液冷概念股票大涨

    01:17

    亚太地区的 AI 驱动型医疗保健:2025 年及以后的下一步是什么?

    16:30

    智能手机市场风云:iPhone领跑销量榜,华为缺席引争议

    15:43

    大数据算法和“老师傅”经验叠加 智慧化收储粮食尽显“科技范”

    15:17

    技术热点

    SQL汉字转换为拼音的函数

    windows 7系统无法运行Photoshop CS3的解决方法

    巧用MySQL加密函数对Web网站敏感数据进行保护

    MySQL基础知识简介

    Windows7和WinXP下如何实现不输密码自动登录系统的设置方法介绍

    windows 7系统ip地址冲突怎么办?windows 7系统IP地址冲突问题的

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

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