人间观察

忽有故人心上头,回首山河已是秋。

马上国庆+中秋了~~~

今天我们看一个比较常见的场景:
当我们处理一个密集型计算数据(比如音视频的软编解码处理,bitmap的特效处理等),这时候就需要用c/c++实现。当在c/c++处理完后需要异步回调/通知到java中,这样代码看起来才很优雅有气质。
如果你知道这个知识那就return吧。~~

在Android中你可以用Thread+Handler很容易的来实现,我相信你闭着眼都能写了。但在jni层中不是这么简单的,我们如何实现?

我们先看一下在jni中非子线程中如何回调再看下在子线程如何回调到java层中。

jni中非子线程回调到java方法中

和普通的在jni中调用java的实例方法没啥区别,上代码:

// java回调接口 INativeListener.java
public interface INativeListener {void onCall();
}
public native void nativeCallBack(INativeListener callBack);
// jni_thread_callback.cpp
extern "C"
JNIEXPORT void JNICALL
Java_com_bj_gxz_jniapp_cb_JNIThreadCallBack_nativeCallBack(JNIEnv *env, jobject thiz,jobject call_back) {// 获取java中的对象jclass cls = env->GetObjectClass(call_back);// 获取回调方法的idjmethodID mid = env->GetMethodID(cls, "onCall", "()V");// 调用java中的方法env->CallVoidMethod(call_back, mid);
}

总结: 分三步

  1. 根据java的obj获取jclass。jclass可以理解为java中的class对象(如果熟悉jni就没啥问题)
  2. 根据1步中的jclass和方法名字和方法的签名获取该方法的jmethodID
  3. env调用java实例的方法。(当前如果是静态的只是调用的方法不一样)

jni中的子线程回调到java方法

主要方法是JavaVM中的AttachCurrentThreadDetachCurrentThread两个方法,这两个是对应的。
官方文档:
官网doc地址

有关注释

Attaching to the VM
The JNI interface pointer (JNIEnv) is valid only in the current thread. Should another thread need to access the Java VM,
it must first call AttachCurrentThread() to attach itself to the VM and obtain a JNI interface pointer.
Once attached to the VM, a native thread works just like an ordinary Java thread running inside a native method.
The native thread remains attached to the VM until it calls DetachCurrentThread() to detach itself.The attached thread should have enough stack space to perform a reaonable amount of work.
The allocation of stack space per thread is operating system-specific. For example, using pthreads,
the stack size can be specified in the pthread_attr_t argument to pthread_create.

译一下:

依附到Java虚拟机上
JNI接口指针(JNIEnv)仅在当前线程中有效。
如果另一个线程需要访问jvm,它必须首先调用AttachCurrentThread()将自己附加到 JVM并获取JNI接口指针。
一旦连接到JVM上,本地线程(jni线程)的工作方式与在本地方法中运行的普通Java线程一样。
本机线程在调用DetachCurrentThread()来分离它自己之前一直连接到VM。附加的线程应该有足够的堆栈空间来执行合理数量的任务。
每个线程的堆栈空间分配是取决于操作系统。
例如,使用pthreads,可以在pthread_attr_t参数中为pthread_create指定堆栈大小。

而在调用JavaVM中的AttachCurrentThreadDetachCurrentThread我们需要拿到JavaVM *vm指针。怎么拿到这个呢?一种是调用JNI_CreateJavaVM加载并初始化Java虚拟机,并返回指向JNI接口指针的指针。我们可以用另外一种jint JNICALL JNI_OnLoad(JavaVM *vm, void *reserved)全局变量保存一下vm即可。
如果不了解JNI_OnLoad可以看上一篇文章 jni动态库的函数注册

接下来,我们写一个简单的功能:在jni中创建一个线程实现一个写入随机字符串到文件(用来模拟线程任务的耗时),然后写入完成后给java层一个回调告诉java层写入成功。

// java回调接口 INativeThreadListener.java
public interface INativeThreadListener {void onSuccess(String msg);
}
public native void nativeInThreadCallBack(INativeThreadListener listener);
JavaVM *gvm;
jobject gCallBackObj;
jmethodID gCallBackMid;extern "C"
JNIEXPORT void JNICALL
Java_com_bj_gxz_jniapp_cb_JNIThreadCallBack_nativeInThreadCallBack(JNIEnv *env, jobject thiz,jobject call_back) {// 创建一个jni中的全局引用gCallBackObj = env->NewGlobalRef(call_back);jclass cls = env->GetObjectClass(call_back);gCallBackMid = env->GetMethodID(cls, "onSuccess", "(Ljava/lang/String;)V");// 创建一个线程pthread_t pthread;jint ret = pthread_create(&pthread, nullptr, writeFile, nullptr);LOG_D("pthread_create ret=%d", ret);
}

这里简单说一下线程的几个参数

 pthread_create参数1 pthread_t* pthread 线程句柄参数2  pthread_attr_t const* 线程的一些属性参数3 void* (*__start_routine)(void*) 线程具体执行的函数参数4 void* 传给线程的参数返回值 int  0 创建成功
/*** 相当于java中线程的run方法* @return*/
void *writeFile(void *args) {// 随机字符串写入FILE *file;if ((file = fopen("/sdcard/thread_cb", "a+")) == nullptr) {LOG_E("fopen filed");return nullptr;}for (int i = 0; i < 10; ++i) {fprintf(file, "test %d\n", i);}fflush(file);fclose(file);LOG_D("file write done");// https://docs.oracle.com/javase/1.5.0/docs/guide/jni/spec/invocation.htmlJNIEnv *env = nullptr;// 将当前线程添加到Java虚拟机上,返回一个属于当前线程的JNIEnv指针envif (gvm->AttachCurrentThread(&env, nullptr) == 0) {jstring jstr = env->NewStringUTF("write success");// 回调到java层env->CallVoidMethod(gCallBackObj, gCallBackMid, jstr);// 删除jni中全局引用env->DeleteGlobalRef(gCallBackObj);// 从Java虚拟机上分离当前线程gvm->DetachCurrentThread();}return nullptr;
}

其实还是jni中非子线程回调到java方法中的三个步骤,只不是多了AttachCurrentThreadDetachCurrentThread的操作。基本的注释在代码中体现了,另外关于文件的写入,属于linux下c的基本操作这里不多说了,不了解的可以看下有关知识。

备注:jni中有写入文件的操作,记得加入Android 权限哦。

 <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" /><uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />

测试一下,结果:

// 看一下文件内容,符合预期
tb8765ap1_bsp_1g:/ # cat /sdcard/thread_cb
test 0
test 1
test 2
test 3
test 4
test 5
test 6
test 7
test 8
test 9// 回调,符合预期onSuccess的回调运行在非ui线程中
2020-09-21 21:43:40.441 8004-8004/com.bj.gxz.jniapp D/JNI: JNI_OnLoad Call
2020-09-21 21:43:40.443 8004-8004/com.bj.gxz.jniapp D/JNI: onCall invoked,threadName:main
2020-09-21 21:43:40.443 8004-8004/com.bj.gxz.jniapp D/JNI: pthread_create ret=0
2020-09-21 21:43:40.447 8004-8067/com.bj.gxz.jniapp D/JNI: file write done
2020-09-21 21:43:40.448 8004-8067/com.bj.gxz.jniapp D/JNI: onSuccess invoked,msg:write success
2020-09-21 21:43:40.449 8004-8067/com.bj.gxz.jniapp D/JNI: onSuccess invoked,threadName:Thread-111

源代码:https://github.com/ta893115871/JNIAPP

最后,祝大家中秋国庆做个三好学生(吃好喝好玩好)。

Android-JNI开发系列《二》-在jni层的线程中回调到java层相关推荐

  1. JNI开发笔记(二)--创建JNI基础工程并运行

    创建JNI基础工程并运行 引 1. 创建JNI工程 2. 添加虚拟手机设备 3. 运行JNI基础工程 引 JNI开发笔记(一)–Android Studio安装与环境搭建 1. 创建JNI工程 And ...

  2. Android JNI开发系列(二)HelloWorld

    2019独角兽企业重金招聘Python工程师标准>>> 入门HelloWorld 新建项目 Configure your new project部分选中 Include C++ Su ...

  3. Android 驱动开发系列二

    最近琐碎事太多了,都没什么时间来写blog.现在继续写这个android驱动的开发调试 这一章主要是讲如何测试驱动. 1.驱动的简单测试 在上一篇文章中,我们已经把添加驱动模块做完了,并把驱动下载到了 ...

  4. Android 系统开发系列二

    这一章主要是讲如何测试驱动. 1.驱动的简单测试 在上一篇文章中,我们已经把添加驱动模块做完了,并把驱动下载到了板子上.下面将介绍一下如何测试驱动是否正常. 这个ttt驱动,我们实现了一个读.一个写的 ...

  5. Android自定义控件开发系列(零)——基础原理篇

    在后边的文章中发现在说Android自定义时,有时候要重复解释很多东西,所以想想返回来增加一篇"基础原理篇",直接进入正题吧-- 首先的问题是:在Android项目开发中,什么时候 ...

  6. android 原生开发 3d地图 下载_arcgis api 3.x for js 入门开发系列二不同地图服务展示(附源码下载)...

    前言 关于本篇功能实现用到的 api 涉及类看不懂的,请参照 esri 官网的 arcgis api 3.x for js:esri 官网 api,里面详细的介绍 arcgis api 3.x 各个类 ...

  7. Android蓝牙开发系列文章-蓝牙设备类型知多少?

    在写<Android蓝牙开发系列文章-蓝牙音箱连接>时,计划细化出一篇讲解蓝牙设备类型的文章,现在它来了~ 阅读其他内容,可以点击<Android蓝牙开发系列文章-策划篇>,或 ...

  8. android 相机编程,Android相机开发系列

    Android Camera Develop Series 简介 Android相机开发系列文章循序渐进,教你从一个没有任何功能的相机APP开始,逐步完善实现一般相机APP的各种功能,甚至还能拿来做图 ...

  9. Android商城开发系列

    Android商城开发系列(一)--开篇 Android商城开发系列(二)--App启动欢迎页面制作 Android商城开发系列(三)--使用Fragment+RadioButton实现商城底部导航栏 ...

最新文章

  1. 数学——函数极限知识以及sympy库的limit
  2. Java项目:药店信息管理系统(java+SSM+JSP+layui+maven+mysql)
  3. 机器学习Tips:关于Scikit-Learn的 10 个小秘密
  4. oracle中导入导出数据备份数据库
  5. 下载网页中的图片到本地
  6. 豪宅周边5家盒马却不配送?盒马回应...
  7. centos8共享文件夹挂载_linux挂载群辉的NFS共享文件夹
  8. w讠ndows Boot Manager,开机出现windows boot manager的解决方法和步骤(图文教程)
  9. Arcgis土地利用转移矩阵制作
  10. Python Django 个人博客源码(附个人源码和网站参考)
  11. 在CentOS Linux系统上,启用ssh服务
  12. 微信小程序文件预览(doc、ppt、pdf)
  13. Android4.1
  14. 网页简单整合Skype
  15. 利用OpenCV识别图片背景是否透明
  16. 开篇──纪念调零的百合
  17. 【R语言文本挖掘】:tidy数据格式及词频计算
  18. 浙江新2014挂历制作,供应温州挂历印刷公司
  19. 区块链产业生态发展情况-中国区块链产业生态发展
  20. 计算机学院东北大学奖学金2016,2018年命名奖学金基本评选要求汇总表-东北大学软件学院.PDF...

热门文章

  1. Hibernate之Inverse的用法
  2. JS学习(this关键字)
  3. Robocopy命令实现文件服务器每日镜像备份/增量备份操作
  4. Vue+Webpack常见问题(持续更新)
  5. RAID5中的“左、右循环”与“同步、异步”(2)
  6. 2017-3-17 SQL server 数据库 视图,事务,备份还原,分离附加
  7. Sql Server 事务日志(二)
  8. mvc:view-controller
  9. 【原创】FPGA开发手记(三) PS/2键盘
  10. 《JavaScript高级程序设计2》学习笔记——Ajax与JSON