首页 文章 精选 留言 我的

精选列表

搜索[android things],共10000篇文章
优秀的个人博客,低调大师

Android JNI开发系列(七)访问数组

JNI访问数组 JNI 中的数组分为基本类型数组和对象数组,它们的处理方式是不一样的,基本类型数组中的所有元素都是 JNI 的基本数据类型,可以直接访问。而对象数组中的所有元素是一个类的实例或其它数组的引用,和字符串操作一样,不能直接访问 Java 传递给 JNI 层的数组,必须选择合适的 JNI 函数来访问和设置 Java 层的数组对象。 访问基本数据类型数组 // // Created by Peng Cai on 2018/10/10. // #include <jni.h> #include <stdlib.h> #include <string.h> JNIEXPORT jint JNICALL Java_org_professor_jni_bean_Student_sum(JNIEnv *env, jobject instance, jintArray stuScore_) { //GetIntArrayElements 第三个参数表示返回的数组指针是原始数组, // 还是拷贝原始数据到临时缓冲区的指针,如果是 JNI_TRUE:表示临时缓冲区数组指针, // JNI_FALSE:表示临时原始数组指针。开发当中,我们并不关心它从哪里返回的数组指针, // 这个参数填 NULL 即可,但在获取到的指针必须做校验,因为当原始数据在内存当中不是连续存放的情况下, // JVM 会复制所有原始数据到一个临时缓冲区,并返回这个临时缓冲区的指针。 // 有可能在申请开辟临时缓冲区内存空间时,会内存不足导致申请失败,这时会返回 NULL。 //NULL 相当于 JNI_FALSE 代表不拷贝数组中的内容到缓冲区 //JNI_TRUE 拷贝数组中的内容到缓冲区 //*stuScore 指针指向一个int 类型数组 //C语言的内存有程序员来管理也就是说 手动手动分配与释放 //调用该函数式最安全的 当GC扫描到stuScore_该对象,会给该对象加锁,本地方法会处于阻塞状态(block) //可能数组中的元素在内存中是不连续的,JVM可能会复制所有原始数据到缓冲区,然后返回这个缓冲区的指针 jint *stuScore = (*env)->GetIntArrayElements(env, stuScore_, NULL); if(stuScore == NULL){ return 0; //JVM复制原始数据到缓冲区失败 } int sum = 0; int length = (*env)->GetArrayLength(env, stuScore); for (int i = 0; i < length; ++i) { sum += stuScore[i]; } //释放stuScore 指向的缓冲区 (*env)->ReleaseIntArrayElements(env, stuScore_, stuScore, 0); return sum; } //这种写法传递数组元素非常少时候效率高 JNIEXPORT jfloat JNICALL Java_org_professor_jni_bean_Student_average(JNIEnv *env, jobject instance, jfloatArray stuScore_) { // jfloat *stuScore = (*env)->GetFloatArrayElements(env, stuScore_, NULL); jsize length = (*env)->GetArrayLength(env, stuScore_); //创建数组 float *stuScoreTmp = (float *) malloc(sizeof(jfloat) * length); //申请缓冲区 memset(stuScoreTmp,0, sizeof(jfloat)*length);//初始化缓冲区 (*env)->GetFloatArrayRegion(env, stuScore_, 0, length, stuScoreTmp); //拷贝Java数组中的所有元素到缓冲区中 int sum = 0; for (int i = 0; i < length; i++) { sum += *(stuScoreTmp + i); } jfloat ave = sum / length; free(stuScoreTmp);// 释放存储数组元素的缓冲区 // (*env)->ReleaseFloatArrayElements(env, stuScore_, stuScore, 0); return ave; } JNIEXPORT jfloat JNICALL Java_org_professor_jni_bean_Student_ave(JNIEnv *env, jobject instance, jfloatArray stuScore_) { //(*GetPrimitiveArrayCritical)(JNIEnv*, jarray, jboolean*); //直接获取一个指针获取原始数组 调用该函数会暂停GC线程,不能调用其他线程的阻塞或者等待式函数(wait notify) jfloat *stuScore = (*env)->GetPrimitiveArrayCritical(env, stuScore_, NULL); jsize length = (*env)->GetArrayLength(env, stuScore_); float sum = 0; for (int i = 0; i < length; i++) { sum += *(stuScore + i); } float ave = sum / length; (*env)->ReleasePrimitiveArrayCritical(env, stuScore_, stuScore, 0); //释放需要与上面对应 return ave; } 在 Java 中创建的对象全都由 GC(垃圾回收器)自动回收,不需要像 C/C++ 一样需要程序员自己管理内存。GC 会实时扫描所有创建的对象是否还有引用,如果没有引用则会立即清理掉。当我们创建一个像 int 数组对象的时候,当我们在本地代码想去访问时,发现这个对象正被 GC 线程占用了,这时本地代码会一直处于阻塞状态,直到等待 GC 释放这个对象的锁之后才能继续访问。为了避免这种现象的发生,JNI 提供了 Get/ReleasePrimitiveArrayCritical这对函数,本地代码在访问数组对象时会暂停 GC 线程。不过使用这对函数也有个限制,在 Get/ReleasePrimitiveArrayCritical 这两个函数期间不能调用任何会让线程阻塞或等待 JVM 中其它线程的本地函数或JNI函数,和处理字符串的 Get/ReleaseStringCritical 函数限制一样。这对函数和 GetIntArrayElements 函数一样,返回的是数组元素的指针。 小结 对于小量的、固定大小的数组,应该选择 Get/SetArrayRegion 函数来操作数组元素是效率最高的。因为这对函数要求提前分配一个 C 临时缓冲区来存储数组元素,你可以直接在 Stack(栈)上或用 malloc 在堆上来动态申请,当然在栈上申请是最快的。有童鞋可能会认为,访问数组元素还需要将原始数据全部拷贝一份到临时缓冲区才能访问而觉得效率低?我想告诉你的是,像这种复制少量数组元素的代价是很小的,几乎可以忽略。这对函数的另外一个优点就是,允许你传入一个开始索引和长度来实现对子数组元素的访问和操作(SetArrayRegion函数可以修改数组),不过传入的索引和长度不要越界,函数会进行检查,如果越界了会抛出 ArrayIndexOutOfBoundsException 异常。 如果不想预先分配 C 缓冲区,并且原始数组长度也不确定,而本地代码又不想在获取数组元素指针时被阻塞的话,使用 Get/ReleasePrimitiveArrayCritical 函数对,就像 Get/ReleaseStringCritical 函数对一样,使用这对函数要非常小心,以免死锁。 Get/ReleaseArrayElements 系列函数永远是安全的,JVM 会选择性的返回一个指针,这个指针可能指向原始数据,也可能指向原始数据的复制。 访问对象数组 JNI 提供了两个函数来访问对象数组,GetObjectArrayElement 返回数组中指定位置的元素,SetObjectArrayElement 修改数组中指定位置的元素。与基本类型不同的是,我们不能一次得到数据中的所有对象元素或者一次复制多个对象元素到缓冲区。因为字符串和数组都是引用类型,只能通过 Get/SetObjectArrayElement 这样的 JNI 函数来访问字符串数组或者数组中的数组元素。 JNIEXPORT jobjectArray JNICALL Java_org_professor_jni_bean_Student_initInt2DArray(JNIEnv *env, jobject instance, jint size) { jobjectArray result; jclass clsIntArray; jint i, j; // 1.获得一个int型二维数组类的引用 clsIntArray = (*env)->FindClass(env, "[I"); if (clsIntArray == NULL) { return NULL; } // 2.创建一个数组对象(里面每个元素用clsIntArray表示) result = (*env)->NewObjectArray(env, size, clsIntArray, NULL); if (result == NULL) { return NULL; } // 3.为数组元素赋值 for (i = 0; i < size; ++i) { jint buff[256]; jintArray intArr = (*env)->NewIntArray(env, size); if (intArr == NULL) { return NULL; } for (j = 0; j < size; j++) { buff[j] = i + j; } (*env)->SetIntArrayRegion(env, intArr, 0, size, buff); (*env)->SetObjectArrayElement(env, result, i, intArr); (*env)->DeleteLocalRef(env, intArr); } return result; } 本地函数 initInt2DArray 首先调用 JNI 函数 FindClass 获得一个 int 型的二维数组类的引用,传递给FindClass 的参数"[I"是 JNI class descript(JNI 类型描述符,后面为详细介绍),它对应着 JVM 中的int[]类型。如果 int[]类加载失败的话,FindClass 会返回 NULL,然后抛出一个java.lang.NoClassDefFoundError: [I异常。 接下来,NewObjectArray 创建一个新的数组,这个数组里面的元素类型用 intArrCls(int[])类型来表示。函数NewObjectArray 只能分配第一维,JVM 没有与多维数组相对应的数据结构,JNI 也没有提供类似的函数来创建二维数组。由于 JNI 中的二维数组直接操作的是 JVM 中的数据结构,相比 JAVA 和 C/C++创建二维数组要复杂很多。给二维数组设置数据的方式也非常直接,首先用 NewIntArray 创建一个 JNI 的 int 数组,并为每个数组元素分配空间,然后用 SetIntArrayRegion 把 buff[]缓冲中的内容复制到新分配的一维数组中去,最后在外层循环中依次将 int[]数组赋值到 jobjectArray 数组中,一维数组中套一维数组,就形成了一个所谓的二维数组。 另外,为了避免在循环内创建大量的 JNI 局部引用,造成 JNI 引用表溢出,所以在外层循环中每次都要调用DeleteLocalRef 将新创建的 jintArray 引用从引用表中移除。在 JNI 中,只有 jobject 以及子类属于引用变量,会占用引用表的空间,jint,jfloat,jboolean 等都是基本类型变量,不会占用引用表空间,即不需要释放。引用表最大空间为 512 个,如果超出这个范围,JVM 就会挂掉。

优秀的个人博客,低调大师

Android 单例模式的正确姿势

单例模式是使用得最多的设计模式,模版代码也很多。但是如果使用不当还是容易出问题。 DCL模式(双重检查锁定模式)的正确使用方式 一般我们使用DCL方法来实现单例模式时都是这样的模版代码: private static Singleton mSingleton = null; private Singleton () {} public static Singleton getInstance() { if (mSingleton == null) { synchronized (Singleton.class) { if (mSingleton == null) { mSingleton = new Singleton(); } } } return mSingleton; } 实际上,上述方法在多线程的环境下,还是会有可能创建多个实例。为什么呢? mConstant = new Constant()这行代码虚拟机在执行的时候会有多个操作,大致包括: 为新的对象分配内存 调用Constant的构造方法,初始化成员变量 将mConstant这个引用指向新创建的Constant对象的地址 在多线程环境下,每个线程的私有内存空间中都有mSingleton的副本。这导致可能存在下面的情况: 当在一个线程中初始化mSingleton后,主内存中的mSingleton变量的值可能并没有及时更新; 主内存的mSingleton变量已经更新了,但在另一个线程中的mSingleton变量没有即时从主内存中读取最新的值 这样的话就有可能创建多个实例,虽然这种几率比较小。 那怎么解决这个问题呢?答案是使用volatile关键字 volatile关键字能够保证可见性,被volatile修饰的变量,在一个线程中被改变时会立刻同步到主内存中,而另一个线程在操作这个变量时都会先从主内存更新这个变量的值。 更保险的单例模式实现 private volatile static Singleton mSingleton = null; private Singleton () {} public static Singleton getInstance() { if (mSingleton == null) { synchronized (Singleton.class) { if (mSingleton == null) { mSingleton = new Singleton(); } } } return mSingleton; } 使用单例模式,小心内存泄漏了喔~ 单例模式的静态特性导致它的对象的生命周期是和应用一样的,如果不注意这一点就可能导致内存泄漏。下面看看常见的2种情况 Context的泄漏 //SingleInstance.class private volatile static SingleInstance mSingleInstance = null; private SingleInstance (Context context) {} public static SingleInstance getInstance(Context context) { if (mSingleInstance == null) { synchronized (SingleInstance.class) { if (mSingleInstance == null) { mSingleInstance = new SingleInstance(context); } } } return mSingleInstance; } //MyActivity public class MyActivity extends AppCompatActivity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); //这样就容易出问题了 SingleInstance singleInstance = SingleInstance.getInstance(this); } @Override protected void onDestroy() { super.onDestroy(); } } 如上面那样直接传入MyActivity的引用,如果当前MyActivity退出了,但应用还没有退出,singleInstance一直持有MyActivity的引用,MyActivity就不能被回收了。 解决方法也很简单,传入ApplicationContext就可以了。 SingleInstance singleInstance = SingleInstance.getInstance(getApplicationContext()); View的泄漏 如果单例模式的类中有跟View相关的属性,就需要注意了。搞不好也会导致内存泄漏,原因和上面分析的原因一样。 //SingleInstance.class private volatile static SingleInstance mSingleInstance = null; private SingleInstance (Context context) {} public static SingleInstance getInstance(Context context) { if (mSingleInstance == null) { synchronized (SingleInstance.class) { if (mSingleInstance == null) { mSingleInstance = new SingleInstance(context); } } } return mSingleInstance; } //单例模式中这样持有View的引用会导致内存泄漏 private View myView = null; public void setMyView(View myView) { this.myView = myView; } 解决方案是采用弱引用 private volatile static SingleInstance mSingleInstance = null; private SingleInstance (Context context) {} public static SingleInstance getInstance(Context context) { if (mSingleInstance == null) { synchronized (SingleInstance.class) { if (mSingleInstance == null) { mSingleInstance = new SingleInstance(context); } } } return mSingleInstance; } // private View myView = null; // public void setMyView(View myView) { // this.myView = myView; // } //用弱引用 private WeakReference<View> myView = null; public void setMyView(View myView) { this.myView = new WeakReference<View>(myView); } 很多东西虽然简单,还是有我们需要注意的地方。这就需要我们理解它们的特性了。比如上面用了弱引用来解决内存泄漏的问题,那我们就需要明白弱引用的特点,需要注意使用弱引用的变量可能为空的问题 被弱引用关联的对象只能生存到下一次垃圾收集发生之前,当垃圾收集器工作时,无论当前内存是否足够,都会回收掉只被弱引用关联的对象 今天你进步了嘛?欢迎关注我的微信公众号,和我一起每天进步一点点!

资源下载

更多资源
Nacos

Nacos

Nacos /nɑ:kəʊs/ 是 Dynamic Naming and Configuration Service 的首字母简称,一个易于构建 AI Agent 应用的动态服务发现、配置管理和AI智能体管理平台。Nacos 致力于帮助您发现、配置和管理微服务及AI智能体应用。Nacos 提供了一组简单易用的特性集,帮助您快速实现动态服务发现、服务配置、服务元数据、流量管理。Nacos 帮助您更敏捷和容易地构建、交付和管理微服务平台。

Spring

Spring

Spring框架(Spring Framework)是由Rod Johnson于2002年提出的开源Java企业级应用框架,旨在通过使用JavaBean替代传统EJB实现方式降低企业级编程开发的复杂性。该框架基于简单性、可测试性和松耦合性设计理念,提供核心容器、应用上下文、数据访问集成等模块,支持整合Hibernate、Struts等第三方框架,其适用范围不仅限于服务器端开发,绝大多数Java应用均可从中受益。

Rocky Linux

Rocky Linux

Rocky Linux(中文名:洛基)是由Gregory Kurtzer于2020年12月发起的企业级Linux发行版,作为CentOS稳定版停止维护后与RHEL(Red Hat Enterprise Linux)完全兼容的开源替代方案,由社区拥有并管理,支持x86_64、aarch64等架构。其通过重新编译RHEL源代码提供长期稳定性,采用模块化包装和SELinux安全架构,默认包含GNOME桌面环境及XFS文件系统,支持十年生命周期更新。

Sublime Text

Sublime Text

Sublime Text具有漂亮的用户界面和强大的功能,例如代码缩略图,Python的插件,代码段等。还可自定义键绑定,菜单和工具栏。Sublime Text 的主要功能包括:拼写检查,书签,完整的 Python API , Goto 功能,即时项目切换,多选择,多窗口等等。Sublime Text 是一个跨平台的编辑器,同时支持Windows、Linux、Mac OS X等操作系统。

用户登录
用户注册