Android - JNI

RelativeLayout源码解析 为什么学习 一直对RelativeLayout如何把xml中的属性转换成子view的真实坐标比较感兴趣,并且网上对RelativeLayout这部分资料比较少,希望将自己对RelativeLayout这部分源码理解写出来,跟大家分享一下。什么是RelativeLayout 中文名叫相对布局,可以指定内部子view之间的相互关系,从而可以绘制出更精确的UI 但是相比于LinearLayout,R 阅读详情

JNI是JAVA语言和C/C++(native)语言之间互相调用的接口。


如何使用JNI

JAVA调用C/C++

使用JNI还是很方便,主要分两步:

1. 加载C/C++生成的动态库

如 System.loadLibrary("media_jni"),就是要加载libmedia_jni.so动态库。

2. 声明native函数

如 private static native final void native_init(),这里面有个修饰符是native,说明该函数是native库里面的函数。


C/C++调用JAVA

我们以MediaRecorder的一个应用为例,录像的文件大小可以设置,在录制的过程中,当达到设置的大小时,C++部分就会通知Java应用程序,这个过程就是C++调用Java函数。这个回调Java主要的处理环节在android_media_MediaRecorder.cpp的JNIMediaRecorderListener:: notify函数:

void JNIMediaRecorderListener::notify(int msg, int ext1, int ext2)
{
    LOGV("JNIMediaRecorderListener::notify");

    JNIEnv *env = AndroidRuntime::getJNIEnv();
    env->CallStaticVoidMethod(mClass, fields.post_event, mObject, msg, ext1, ext2, 0);
}
该函数完成两个任务
1. 获得JNI的环境参数JNIEnv;
2. 通过CallStaticVoidMethod函数调用Java函数,这个函数就是mClass的fields.post_event方法,后面的其他参数为Java的post_event函数的参数;

这样就可以调用Java某个类的方法了。

可以看出,在这里面最重要的一个中间传递者就是JNIEnv,它可以获得Java层类对象以及类对象的方法和成员,对于更详细介绍JNIEnv,请链接到JNIEnv小节。下面是android_media_MediaRecorder.cpp的获得类和类方法和成员的代码:

static void
android_media_MediaRecorder_native_init(JNIEnv *env)
{
    jclass clazz;

    clazz = env->FindClass("android/media/MediaRecorder");
    if (clazz == NULL) {
        jniThrowException(env, "java/lang/RuntimeException", "Can't find android/media/MediaRecorder");
        return;
    }

    fields.context = env->GetFieldID(clazz, "mNativeContext", "I");
    if (fields.context == NULL) {
        jniThrowException(env, "java/lang/RuntimeException", "Can't find MediaRecorder.mNativeContext");
        return;
    }

    fields.surface = env->GetFieldID(clazz, "mSurface", "Landroid/view/Surface;");
    if (fields.surface == NULL) {
        jniThrowException(env, "java/lang/RuntimeException", "Can't find MediaRecorder.mSurface");
        return;
    }

    jclass surface = env->FindClass("android/view/Surface");
    if (surface == NULL) {
        jniThrowException(env, "java/lang/RuntimeException", "Can't find android/view/Surface");
        return;
    }

    fields.surface_native = env->GetFieldID(surface, ANDROID_VIEW_SURFACE_JNI_ID, "I");
    if (fields.surface_native == NULL) {
        jniThrowException(env, "java/lang/RuntimeException", "Can't find Surface.mSurface");
        return;
    }

    fields.post_event = env->GetStaticMethodID(clazz, "postEventFromNative",
                                               "(Ljava/lang/Object;IIILjava/lang/Object;)V");
    if (fields.post_event == NULL) {
        jniThrowException(env, "java/lang/RuntimeException", "MediaRecorder.postEventFromNative");
        return;
    }
}

1. 通过FindClass获得了android/media/MediaRecorder类;

2. 通过GetFieldID获得了android/media/MediaRecorder类的mNativeContext和mSurface成员;

3. 通过FindClass获得了android/view/Surface类;

4. 通过GetFieldID获得了android/view/Surface类的mNativeSurface成员;

5. 通过GetStaticMethodID获得了postEventFromNative方法;

Java native 函数和C/C++函数的挂钩

虽然,JNI用起来很简单,如何才能将Java声明的native函数和实际的C/C++函数挂钩呢?通过查询,有两种办法,Android常用的是动态注册,还有一个静态注册方法。


动态注册

我们以android_media_MediaRecorder.cpp为例,MediaRecoder.java声明了“public native void start()”函数,该start函数要和android_media_MediaRecorder.cpp文件的android_media_MediaRecorder_start函数挂钩,他俩是如何联系到一起的呢。

通过查阅资料,当Java加载动态库后,它会执行该库的JNI_OnLoad函数,该函数包含如下几行代码:

    if (register_android_media_MediaRecorder(env) < 0) {
        LOGE("ERROR: MediaRecorder native registration failed\n");
        goto bail;
    }

通过函数名字是注册MediaRecorder,register_android_media_MediaRecorder(env) 定义如下

int register_android_media_MediaRecorder(JNIEnv *env)
{
    return AndroidRuntime::registerNativeMethods(env,
                "android/media/MediaRecorder", gMethods, NELEM(gMethods));
}
从中,有两个参量很重要,一个是 "android/media/MediaRecorder",它是Java程序的相应的类;一个是gMethods结构体数组,这个数组包含很多如下格式的结构体 {"start", "()V", (void *)android_media_MediaRecorder_start},从表面意思就是将Java的"android/media/MediaRecorder"类的start函数和android_media_MediaRecorder_start函数挂钩,这样就实现了注册。AndroidRuntime::registerNativeMethods定义如下

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

/*
 * Register native JNI-callable methods.
 *
 * "className" looks like "java/lang/String".
 */
int jniRegisterNativeMethods(JNIEnv* env, const char* className,
    const JNINativeMethod* gMethods, int numMethods)
{
    jclass clazz;

    LOGV("Registering %s natives\n", className);
    clazz = (*env)->FindClass(env, className);
    if (clazz == NULL) {
        LOGE("Native registration unable to find class '%s'\n", className);
        return -1;
    }

    int result = 0;
    if ((*env)->RegisterNatives(env, clazz, gMethods, numMethods) < 0) {
        LOGE("RegisterNatives failed for '%s'\n", className);
        result = -1;
    }

    (*env)->DeleteLocalRef(env, clazz);
    return result;
}

/*
 * Register one or more native functions in one class.
 *
 * This can be called multiple times on the same method, allowing the
 * caller to redefine the method implementation at will.
 */
static jint RegisterNatives(JNIEnv* env, jclass jclazz,
    const JNINativeMethod* methods, jint nMethods)
{
    JNI_ENTER();

    ClassObject* clazz = (ClassObject*) dvmDecodeIndirectRef(env, jclazz);
    jint retval = JNI_OK;
    int i;

    if (gDvm.verboseJni) {
        LOGI("[Registering JNI native methods for class %s]\n",
            clazz->descriptor);
    }

    for (i = 0; i < nMethods; i++) {
        if (!dvmRegisterJNIMethod(clazz, methods[i].name,
                methods[i].signature, methods[i].fnPtr))
        {
            retval = JNI_ERR;
        }
    }

    JNI_EXIT();
    return retval;
}

以上的步骤基本如下:

1. 通过传入的Class字符串查找Java中是否有对应的类;

2. 通过Java虚拟机提供的注册函数接口,将gMethods数组的函数对注册到虚拟机中;

这样就完成了动态注册。


静态注册

静态注册方法是通过函数名字进行挂钩的方法,如Java start函数,那么它就到对应的动态库中寻找名字为“android_media_MediaRecorder_start”的函数,其中“android_media_MediaRecordert”表示对应的Java类名,_start表示函数的名字,这样做的方法有以下几点弊端,同时也是动态注册的优点:

1. C/C++定义的函数名字必须遵循这样的规定,否则两个函数无法挂钩;

2. 第一次调用函数时,必须到动态库中搜索这个函数,会影响速度,以后就会将对应的函数地址记录下来,所以第二次及以后使用该函数就和动态注册效率一致;


JNIEnv

在上面两节都用到了JNIEnv,看来通过它可以获得Java层有什么类对象,以及类对象的成员和方法;可以将Java层的native函数和C/C++函数挂钩,那么这个JNIEnv是如何来的呢?

在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) {
        LOGE("ERROR: GetEnv failed\n");
        goto bail;
    }
    assert(env != NULL);

    if (register_android_media_MediaPlayer(env) < 0) {
        LOGE("ERROR: MediaPlayer native registration failed\n");
        goto bail;
    }
1. JNI_OnLoad第一个参数是JavaVM,这表示和java虚拟机有关的参量,一个进程只有一个这个参数;

2. 通过vm->GetEnv就可以获得JNIEnv参量,通过JNIEnv参量就可以让Java获得C/C++对应的函数;让C/C++获得Java相应的方法和成员变量;


JNIEnv就是JNI技术的关键。JNIEnv再向下执行,就是调用Java虚拟提供的功能接口了,我们就不作分析了,我只要懂得怎么使用JNI技术即可,我们要明白,我们既可以在Java层获得C/C++程序,也可以在C/C++层调用Java程序。


引用计数

JNI共提供了三种引用计数,和C++的引用计数还有很大的不同。


Local Reference  

本地引用,jni层函数使用的非全局引用对象都是local reference,包括函数调用时传入的jobject和在JNI层函数中创建的jobject。

当所在函数退出时,这些jobject才可能被垃圾回收。

上面传达了两层意思:

1. 函数退出时,本地引用不一定马上回收;

2. 函数没退出时,本地引用不会被回收;

所以,当出现类似下面的情况时,

For(int i = 0; i < 100; i++){
    Jstring pathStr = mEnv->NewStringUTF(path);
    //do something
}

在函数退出前,会出现100个pathStr,所以内存可能很快被消耗完,那怎么办呢?

如果在每个循环里面调用env->DeleteLocalRef(pathStr),那么当执行完一个循环,pathStr就会被回收。

所以,DeleteLocalRef是本地引用一个很重要的操作。


Global Reference

如下面的一段JNI代码:

Android_media_MediaScanner_processFile(JNIEnv *env, jobject thiz,jstring path, jstring mimeType, jobject client){
。。。。。。;
//保存Java层换入的jobject对象,代表MediaScanner对象
Save_this = thiz;
。。。。。。;
Return;
}

这里面是有问题的,因为JNI里面Save_this = thiz这句话是不会改变引用计数的,所以,如果其他函数有调用Save_this的地方,可能对应的thiz对象已经被释放了。那怎么办呢?

JNI提供了全局引用,全局引用对象如不主动释放,它永远不会被垃圾回收。

所以如

Save_this = (jclass)env->NewGlobalRef(thiz);

将Save_this设为全局引用,那么对应的thiz对象是不会被释放的。

只有当调用env->DeleteGlobalRef(Save_this)时,对应的thiz对象才有可能被回收,否则永远不会被释放。







Android开发重点难点:RelativeLayout(相对布局)详解 阅读详情

相关推荐

Android RelativeLayout核心解析:从相对布局原理到实战迁移指南

在Android UI开发中,布局系统是构建用户界面的基础。其核心原理在于如何高效、灵活地定位和排列视图组件。RelativeLayout作为Android早期布局体系的基石,通过定义视图相对于父容器或兄弟视图的位置关系,实现了描述式布局逻辑,极大提升了复杂界面构建的灵活性与直观性。这种相对布局思想不仅降低了UI开发的学习门槛,其基于依赖关系的测量布局机制(两次遍历)也深刻影响了后续高性能布局组件的设计理念。理解RelativeLayout的属性和工作原理,对于掌握ConstraintLayout的约束概念

weixin_34111790的博客 310

【系统调试篇】1. ‘Native registration unable to find class ‘com/android/internal/os/RuntimeInit‘;

写在前面: 本系列博文主要枚举在开发AndroidAutomotiveOS过程中遇到的各种问题,比如不开机,重启,WTD,Panic等。 问题描述:编译frameworks/base/core,并push到手机重启后,无法开机,卡开机动画; 初步分析:system_server无法启动,发生tombstone,提示Abort message: 'Native registration unable to find class 'com/android/internal/os/RuntimeIni.

从0到1,突破自己 5758

Android RelativeLayout核心机制与实战优化指南

在Android开发中,布局系统是构建用户界面的基础,它决定了UI组件的排列与定位方式。RelativeLayout作为Android经典布局之一,其核心原理是基于视图间的相对位置关系进行仲裁与测量,通过依赖规则描述组件与父容器或兄弟视图的空间关联。这种设计在中等复杂度界面构建中提供了灵活性,能有效减少嵌套层级,提升代码可读性。从技术价值看,掌握RelativeLayout有助于理解Android布局体系的设计哲学,并为维护存量项目提供必要基础。在实际应用场景中,它特别适合处理表单布局、信息卡片及需要视图层

weixin_34226182的博客 353

Native registration unable to find class 'com/android/server/Watchdog'问题

今天编译完android4.0系统之后,烧写进去,启动一直停留在“android”字样,查看logcat,信息如下: adb logcat --------- beginning of /dev/log/system I/Vold    ( 1028): Vold 2.1 (the revenge) firing up D/Vold    ( 1028): Volume sdcard

sunnywalden 4320

AndroidStudio相对布局RelativeLayout

相对布局

m0_65621953的博客 597

布局ViewGroup原理解析(三):RelativeLayout

Android中最基础的三大布局为FrameLayout、LinearLayout与RelativeLayout。 记得刚初学Android 的时候最喜欢用LinearLayout因为它简单易用,但是越做到后面越喜欢使用RelativeLayout,因为它的灵活性和适用范围都要比前两者要好,但是这里也要提醒初学者,不要用RelativeLayout做太多嵌套,会产生性能问题。今天讲的就是在项目开发过程中遇到的一个关于RelativeLayout的问题。 问题出现与初步解决 在一次版本迭代中,UI宝宝给出了一

bugmiao的博客 390

JNI 中创建对象数组

JNI 回调实例方法与静态方法 在上一篇中介绍了JNI中创建对象。本文是JNI系列的第十篇,介绍JNI中的如何调在Native代码中创建对象数组。 系列文章的大纲如下: JNI 简介 JNI 基本类型 JNI String JNI 数组 JNI 实例变量 JNI 静态变量 JNI 回调实例方法与静态方法 JNI 调用Java中的super.method() JNI 中创建对象 JNI 中创建对象数组 JNI 中局部引用和全局引用 JNI 动态注册 使用Android NDK编译Android的Native

枫竹梦 3975

RelativeLayout(相对布局)

而padding代表的则是填充,而填充的对象针对的是组件中的元素,比如TextView中的文字比如为TextView设置paddingleft="5dp",则是在组件里的元素的左边填充5dp的空间!margin针对的是容器中的组件,而padding针对的是组件中的元素,要区分开来!相信很多朋友都不知道一点吧,平时我们设置margin的时候都习惯了是正数的,其实是可以用负数的,下面写个简单的程序演示下吧,模拟进入软件后,弹出广告页面的,右上角的cancle按钮的margin则是使用负数的!

xiaoaque的博客 1067

这是一份全面&详细的Android 车载系统开发入门指南

目前的就业形势越来越严峻,很多大中小厂因为业务停滞不前都选择“精简人员”,节约成本。对于Android开发来说,市场的冷静,明显可以感知到企业招聘门槛的提高。就未来发展来说,选择一个正处于时代风口的细分领域深耕最佳。车载开发无疑是具有这个潜力的领域,人工智能,物联网的发展,车企“缺芯少魂”刺激了车载开发走上风口。想要抓住转行车载行业的好机会,大家应该紧锣密鼓的开始准备起来了。为了帮助大家更好的掌握...

Android技术之家 3561

Android零基础入门第28节:轻松掌握RelativeLayout相对布局

相对布局通常有两种形式,一种是相对于容器而言的,一种是相对于控件而言的。

鑫鱻的博客 1513

Android基础教程(二)之五大布局对象---FrameLayout,LinearLayout ,AbsoluteLayout,RelativeLayout,TableLayout.

 大家好,我们这一节讲一下Android对用五大布局对象,它们分别是FrameLayout(框架布局:不知道是不是这么翻译的),LinearLayout (线性布局),AbsoluteLayout(绝对布局),RelativeLayout(相对布局),TableLayout(表格布局). FrameLayout: FrameLayout是最简单的一个布局对象。它被定制为你屏幕上

Android_Tutor的专栏 12万+

基于RelativeLayout实现自动换行标签控件

在开发中,我们有时会遇到显示标签的功能。比如做社交app,用户有标签;做电商app,商品也同样有标签......显示标签时,我们需要一次展示出多个标签,但是单个标签的字数可能不同,当一行不足以容纳时,需要换行显示。系统自带控件无法实现该效果,所以我们需要自定义控件来解决。 实现效果如下: 关于自动换行,有多种实现方式,这里基于RelativeLayout来实现。 实现思路:

ruancoder的博客 4361

用RelativeLayout 实现简单的九宫格布局

//RelativeLayout是item中内嵌的布局 RelativeLayout mainLayout = (RelativeLayout) findViewById(R.id.activity_main); //获取屏幕宽度 int screenWidth = getResources().getDisplayMetrics().widthPixels; //每行

lzc394049722的博客 2615

(四)安卓开发中的相对布局(RelativeLayout)使用详解

相对布局通过其丰富的定位属性,开发者可以轻松实现视图的精确排列和对齐。无论是简单的居中显示,还是复杂的相对定位,相对布局都能胜任。同时,注意性能优化和布局设计的合理性,能让相对布局在实际开发中发挥最大价值。安卓开发中的相对布局(RelativeLayout)是一种非常灵活的布局方式,它允许开发者根据父容器或其他视图的位置来定位子视图。以下是对相对布局的详细讲解,包括其基本概念、主要属性、代码示例以及具体的使用场景。相对布局是一种视图容器(ViewGroup),可以包含其他视图(View)或视图组。

cuijiying的博客 1808

Android RelativeLayout点击切换背景

我们的目标是日月星辰 为什么要写这篇博客? 很常见的一个需求就是点击一个布局,切换布局的显示状态,对于TextView和ImageView来说这是很容易实现的,想着RelativeLayout应该也很简单,确实也有setBackground()这个属性,然而。。。 没办法,只能想别的办法,无意间想到洋神有一篇博客,我这篇博客充其量就算是对洋神博客的整理,大家也可以直接去看洋神的博客 ...

蒙奇·D·路飞的博客 3104

安卓布局LinearLayout —— RelativeLayout —— TableLayout —— GridLayout

LinearLayout 线性布局:只能横向或者纵向布局 取决于orientation属性 <?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:orientation="horizont...

weixin_43795939的博客 363

android 之 android.widget.RelativeLayout.LayoutParams

android.widget.RelativeLayout.LayoutParams 有一个构造函数:RelativeLayout.LayoutParams(int w, int h), 参数指定了子 View 的宽度和高度, 这一点和其父类是一样的。 而实现相对布局的关键在它的 两个 addRule 方法上。anchor 参数指定可以是 View 的 id(“相对于谁”)、Relativ

hlglinglong的专栏 1082

别再只盯着DDR5了!聊聊3D DRAM:它凭什么能解决AI算力的内存墙难题?

本文深入探讨了3D DRAM技术如何突破AI算力的内存墙限制。通过立体堆叠架构,3D DRAM在存储密度、带宽和延迟方面实现显著提升,有效解决AI训练和推理中的内存瓶颈问题。文章还分析了三星、SK海力士等巨头的技术路线图,并指出商业化面临的挑战与机遇。

weixin_42685438的博客 189
上一篇: UML - 类图几种关系
下一篇: 不相交集合
G机器猫
博客等级 码龄16年 106粉丝 74原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

当前余额3.43元 前往充值 >
需支付:10.00元
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付元
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值