一. JNI介绍
JNI(Java Native Interface),通过使用 Java本地接口,实现Java(或kotlin)代码和其他语言(c/c++)的代码的交互,交互是JNI的精髓,意味着java和c++之间可以很方便地进行相互访问变量,调用对方的函数,如java可以调用c++的函数,c++也能调用java的函数。
介绍JNI中几个比较重要的变量:
一。JavaVM:虚拟机的代表,一个进程创建一个。可以从JavaVM中获取JNIEnv指针,kotlin层访问C++层的时候会自动创建,C++访问kotlin时不会自动创建,因此可以考虑在JNI_OnLoad函数中将自动创建的JavaVM存为全局的。
二。JNIEnv:指代Java本地接口环境,定义了JNI函数接口,线程持有,只在当前线程有效,子线程可以通过AttachCurrentThread函数将当前线程被附加到JVM上,并返回一个属于当前线程的JNIEnv指针(后面讲例子)。
三。jobject:kotlin类的实例引用,JNI函数执行完后会释放,因此如果想保存,需要new出来:env->NewGlobalRef(object),释放:env->DeleteGlobalRef(object)。
四。jclass:kotlin类的类引用,可以通过object获取:(jclass)env->NewGlobalRef(env->GetObjectClass(object)),需要释放:env->DeleteGlobalRef(clazz)。
(JNI函数第二个参数有两种:jobject和jclass,kotlin中声明为静态的JNI函数参数是jclass,非静态是jobject)
五。jmethodID, jfieldID :指向内部 Runtime 数据结构的指针,用于缓存的静态对象:第一次查找会做一次字符串比较,但后面再次调用就能直接读取而变得很快;不需要手动释放的,当然也不能作为 JNI 全局引用。
二. kotlin调C++
JNI原理简单说就是动态库加载和函数的动态注册(相较于静态注册,效率高,接口更清晰)。下面以超声波模块为例说明。
c++层代码编译成动态库(libultrasonic_native.so),kotlin层通过调用System.loadLibrary()方法加载此动态库,此时虚拟机就会调用JNI库中的 JNI_OnLoad()方法进行动态注册,即实现kotlin层和c++层的函数映射, 映射后,kotlin层调_openUltrasonic方法,就会调到c++的openUltrasonic方法。
kotlin层:
System.loadLibrary("ultrasonic_native") //kotlin层加载动态库
//kotlin层,JNI函数声明
external fun openUltrasonic(devices: Array<String>): Boolean
external fun closeUltrasonic()
C++层:
JNINativeMethod methodMapping[] = //kotlin层和c++层的函数映射,
{
{"_openUltrasonic", "([Ljava/lang/String;)Z", (void *) UltrasonicNative::openUltrasonic},
{"_closeUltrasonic", "()V", (void *) UltrasonicNative::closeUltrasonic},
}; //参数依次是:kotlin函数名,参数,c++函数名
int regist(JNIEnv *env) //动态注册
{
jclass clazz = env->FindClass("com/pudutech/mirsdk/hardware/ultrasonic/Ultrasonic"); //找到kotlin层的类
CHECK_RTN_VAL(!clazz, JNI_ERR)
CHECK_RTN_VAL(env->RegisterNatives(clazz, methodMapping, ARRAY_SIZE(methodMapping)) < 0, JNI_ERR) //注册
env->DeleteLocalRef(clazz);
return JNI_OK;
}
JNIEXPORT jint JNICALL JNI_OnLoad(JavaVM* vm, void* reserved)
{
JNIEnv* env;
if (vm->GetEnv((void**)&env, JNI_VERSION_1_6) != JNI_OK) {
return JNI_ERR; // JNI version not supported.
}
vmGlobal = vm;
regist(env); //动态注册
return JNI_VERSION_1_6;
}
三. C++调kotlin
C++可以访问或修改kotlin类的成员变量,或者调用kotlin类的成员函数。
- C++获取kotlin类的成员变量的值
首先获取kotlin类的成员变量mNativeContext的参数ID(可以认为是指向该成员变量的指针),调用GetLongField获取该成员变量的值,类型不同,方法名不同。如int类型调GetIntField。需要注意:获取参数ID是获取类引用的参数ID,因此使用类引用jclass,获取值是获取类实例的值,因此要用类实例的引用jobject。
static pudu::Ultrasonic *getNativeUltrasonic(JNIEnv *env, jobject object) {
jclass clazz = env->GetObjectClass(object); //获取kotlin的类
jfieldID ultrasonicContext = env->GetFieldID(clazz, "mNativeContext", "J"); //获取kotlin类的成员变量的参数ID
pudu::Ultrasonic *ultrasonic = reinterpret_cast<pudu::Ultrasonic *>(env->GetLongField(object, ultrasonicContext)); //获取long类型的成员变量的值
return ultrasonic;
}
- C++修改kotlin类的成员变量的值
第一步同样是获取kotlin类的成员变量mNativeContext的参数ID,然后调用SetLongField方法修改该成员变量的值。
static void setNativeUltrasonic(JNIEnv *env, jobject object, pudu::Ultrasonic *ultrasonic) {
jclass clazz = env->GetObjectClass(object);
jfieldID ultrasonicContext = env->GetFieldID(clazz, "mNativeContext", "J");
env->SetLongField(object, ultrasonicContext, reinterpret_cast<jlong>(ultrasonic));
}
- C++调用kotlin类成员函数
方法和获取成员变量类似,首先调用GetMethodID获取kotlin类成员函数postEventFromNative的方法ID,然后调用CallVoidMethod执行该函数。
jmethodID nativeFunction = env->GetMethodID(clazz, "postEventFromNative", "(Ljava/lang/String;)V"); //获取kotlin类成员函数postEventFromNative的方法ID
CHECK_DO_RTN(!nativeFunction, LOGW(TAG) << "get method postEventFromNative failed")
env->CallVoidMethod(obj, nativeFunction, env->NewStringUTF(msg.c_str())); //运行kotlin函数, 第二个参数是方法ID,后面是该函数的参数
比较常规的一种用法是:主线程在C++层打开设备,然后开一个新线程来循环获取数据,获取到数据后通过调用kotlin类成员函数将获取到的数据抛给上层处理。方法如下:
第一步,给传感器设备类Ultrasonic设置回调函数,这里需要用到我们之前new出来并保存的kotlin类实例jobject和临时获取的属于当前子线程的env:
static void setUploadDataClb(pudu::Ultrasonic *ultrasonic) {
nativeClb uploadDataclb = [&](void *object, string msg) //回调函数
{
CHECK_DO_RTN(!object, LOGW(TAG) << "object is NULL")
JNIEnv *env;
bool isAttach = false;
auto ret = vmGlobal->GetEnv((void **) &env, JNI_VERSION_1_6);
if (ret == JNI_EDETACHED) {
ret = vmGlobal->AttachCurrentThread(&env, NULL); //将当前线程被附加到JVM上,并获取一个属于当前线程的JNIEnv指针
isAttach = true;
}
CHECK_DO_RTN(ret != JNI_OK, LOGW(TAG) << "get env failed:")
jobject obj = reinterpret_cast<jobject>(object);
static jclass clazz = (jclass)env->NewGlobalRef(env->GetObjectClass(obj)); //静态局部变量,只初始化一次,不用反复申请释放内存,提升效率
static jmethodID nativeFunction = env->GetMethodID(clazz, "postEventFromNative", "(Ljava/lang/String;)V"); //获取kotlin函数的方法ID
CHECK_DO_RTN(!nativeFunction, LOGW(TAG) << "get method postEventFromNative failed")
env->CallVoidMethod(obj, nativeFunction, env->NewStringUTF(msg.c_str())); //调用kotlin方法
CHECK_DO (isAttach, vmGlobal->DetachCurrentThread())
};
ultrasonic->setUploadDataClb(uploadDataclb); //超声波实例set回调函数
}
第二部,打开设备后开辟子线程循环获取数据,线程函数reading获取数据:
unique_ptr<thread> readThread = make_unique<thread>(bind(&Ultrasonic::reading, this));
readThread->detach();
第三步,获取到数据后调用回调函数:
if(uploadDataClb) {
uploadDataClb(upperObject, oneDataJson.dump()); //调回调函数,执行kotlin函数,发送超声波信息给上层
}
四. JNI相关的软件设计
面向对象的单一职责和开放封闭原则告诉我们要做到高内聚低耦合,对扩展开发,对修改封闭,这对于时常变动,经常需要适配的传感器模块显得尤为重要。以RGBD为例,当前有四款RGBD,我们要实现的是四款RGBD的代码模块的高内聚(内紧),每一个模块都是设备层,传输层,解析层紧密相关,高效合作的整体,也要实现四个模块间的低耦合(外松),模块间几乎没有依赖,不会牵一发动全身。
重构后的RGBD做了两个解耦,一是不同产品类型的RGBD的解耦,如realsense和奥比各自有自己的模块,相互没有任何依赖;二是同一产品类型的多个RGBD间的解耦,如一个机器人上有两个奥比RGBD,这两个RGBD各自有自己的实例,如下图所示,两个RGBD是两个相互独立的整体,各自实例化了kotlin层的实例和C++层的实例, 两个实例如同一个实例,成员变量互通,成员函数互相访问。两个RGBD各自持有自己的变量,各自独立地获取数据,然后调自己的kotlin实例的方法上抛数据。相较于重构前一个类实例管理多个RGBD,结构更清晰,依赖更少,扩展性得到很大提升。
代码
native_interface.cpp:
#include "Ultrasonic.h"
#include "g3log/g3log.hpp"
#include <stdlib.h>
#include <stdint.h>
#include <string>
#include <jni.h>
namespace {
JavaVM* vmGlobal = nullptr;
const char *TAG = "Ultrasonic";
const char *ULTRASONIC_CTX = "mNativeContext";
class UltrasonicNative {
public:
static jboolean openUltrasonic(JNIEnv *env, jobject object, jobjectArray devices) {
vector<string> devicesVector = jstringArray2Vector(env, devices);
pudu::Ultrasonic *ultrasonic = new pudu::Ultrasonic();
setNativeUltrasonic(env, object, ultrasonic);
ultrasonic->setUpperObject(env->NewGlobalRef(object));
setUploadDataClb(ultrasonic);
return ultrasonic->openUltrasonic(devicesVector);
}
static void closeUltrasonic(JNIEnv *env, jobject object) {
pudu::Ultrasonic *ultrasonic = getNativeUltrasonic(env, object);
CHECK_RTN(!ultrasonic)
ultrasonic->closeUltrasonic();
}
static pudu::Ultrasonic *getNativeUltrasonic(JNIEnv *env, jobject object) {
jclass clazz = env->GetObjectClass(object);
jfieldID ultrasonicContext = env->GetFieldID(clazz, ULTRASONIC_CTX, "J");
pudu::Ultrasonic *ultrasonic = reinterpret_cast<pudu::Ultrasonic *>(env->GetLongField(object, ultrasonicContext));
return ultrasonic;
}
static void setNativeUltrasonic(JNIEnv *env, jobject object, pudu::Ultrasonic *ultrasonic) {
jclass clazz = env->GetObjectClass(object);
jfieldID ultrasonicContext = env->GetFieldID(clazz, ULTRASONIC_CTX, "J");
env->SetLongField(object, ultrasonicContext, reinterpret_cast<jlong>(ultrasonic));
}
static void setUploadDataClb(pudu::Ultrasonic *ultrasonic) {
nativeClb uploadDataclb = [&](void *object, string msg)
{
CHECK_DO_RTN(!object, LOGW(TAG) << "object is NULL")
JNIEnv *env;
bool isAttach = false;
auto ret = vmGlobal->GetEnv((void **) &env, JNI_VERSION_1_6);
if (ret == JNI_EDETACHED) {
ret = vmGlobal->AttachCurrentThread(&env, NULL);
isAttach = true;
}
CHECK_DO_RTN(ret != JNI_OK, LOGW(TAG) << "get env failed:")
jobject obj = reinterpret_cast<jobject>(object);
static jclass clazz = (jclass)env->NewGlobalRef(env->GetObjectClass(obj));
static jmethodID nativeFunction = env->GetMethodID(clazz, "postEventFromNative", "(Ljava/lang/String;)V");
CHECK_DO_RTN(!nativeFunction, LOGW(TAG) << "get method postEventFromNative failed")
env->CallVoidMethod(obj, nativeFunction, env->NewStringUTF(msg.c_str()));
CHECK_DO (isAttach, vmGlobal->DetachCurrentThread())
};
ultrasonic->setUploadDataClb(uploadDataclb);
}
static string jstring2string(JNIEnv *env, jstring s) {
const char *jstr = env->GetStringUTFChars(s, NULL);
int length = env->GetStringLength(s);
string str = string((char *) jstr, length);
env->ReleaseStringUTFChars(s, jstr);
return str;
}
static vector<string> jstringArray2Vector(JNIEnv *env, jobjectArray devices) {
vector<string> devicesVector;
jsize stringArrayLength = env->GetArrayLength(devices);
for (int i = 0; i < stringArrayLength; i++) {
jobject jobj = env->GetObjectArrayElement(devices, i);
jstring jstr = static_cast<jstring>(jobj);
string str = jstring2string(env, jstr);
devicesVector.push_back(str);
}
return devicesVector;
}
static char* jbyteArray2String(JNIEnv *env, jbyteArray byteArray, int &arrayLength)
{
jbyte *bytes = env->GetByteArrayElements(byteArray, 0);
arrayLength = env->GetArrayLength(byteArray);
LOGW(TAG) << "byteArray len:" << arrayLength;
char* array = new char[arrayLength + 1];
memset(array, 0, arrayLength + 1);
memcpy(array, bytes, arrayLength);
array[arrayLength] = '\0';
env->ReleaseByteArrayElements(byteArray, bytes, 0);
return array;
}
};
JNINativeMethod methodMapping[] = {
{"openUltrasonic", "([Ljava/lang/String;)Z", (void *) UltrasonicNative::openUltrasonic},
{"closeUltrasonic", "()V", (void *) UltrasonicNative::closeUltrasonic},
};
int registerPuduNativeClass(JNIEnv *env) {
jclass clazz = env->FindClass("com/pudutech/mirsdk/hardware/ultrasonic/Ultrasonic");
CHECK_RTN_VAL(!clazz, JNI_ERR)
CHECK_RTN_VAL(env->RegisterNatives(clazz, methodMapping, ARRAY_SIZE(methodMapping)) < 0, JNI_ERR)
env->DeleteLocalRef(clazz);
return JNI_OK;
}
void unregisterPuduNativeClass(JNIEnv *env) {
jclass clazz = env->FindClass("com/pudutech/mirsdk/hardware/ultrasonic/Ultrasonic");
CHECK_RTN(!clazz)
CHECK_RTN(env->UnregisterNatives(clazz) < 0)
env->DeleteLocalRef(clazz);
}
}
#ifdef __cplusplus
extern "C" {
#endif
JNIEXPORT jint JNICALL JNI_OnLoad(JavaVM* vm, void* reserved)
{
JNIEnv* env;
if (vm->GetEnv((void**)&env, JNI_VERSION_1_6) != JNI_OK) {
return JNI_ERR; // JNI version not supported.
}
vmGlobal = vm;
registerPuduNativeClass(env);
return JNI_VERSION_1_6;
}
JNIEXPORT void JNICALL JNI_OnUnload(JavaVM *vm, void *reserved) {
JNIEnv *env;
CHECK_RTN(vm->GetEnv((void **) &env, JNI_VERSION_1_6) != JNI_OK) // JNI version not supported.
unregisterPuduNativeClass(env);
}
#ifdef __cplusplus
}
#endif
Ultrasonic.h:
#pragma once
#include <memory>
#include <vector>
#include <string>
#include <chrono>
#include <thread>
#include <mutex>
#define CHECK_RTN(condition) if(condition){return;}
#define CHECK_DO_RTN(condition, needDo) if(condition){needDo; return;}
#define CHECK_RTN_VAL(condition, val) if(condition){return val;}
#define CHECK_DO_RTN_VAL(condition, needDo, val) if(condition){needDo; return val;}
#define CHECK_DO(condition, needDo) if(condition){needDo;}
using namespace std;
typedef std::function<void(void* object, string msg)> nativeClb;
template <typename T, size_t N>
char(&ArraySizeHelper(T(&array)[N]))[N];
#define ARRAY_SIZE(array) (sizeof(ArraySizeHelper(array)))
namespace pudu{
class Ultrasonic{
public:
Ultrasonic();
~Ultrasonic();
bool openUltrasonic(vector<string> devicesVector);
void closeUltrasonic();
void setUploadDataClb(const nativeClb &clb);
void setUpperObject(void *object);
private:
int getValidId(const string &device);
int setSerialPort();
bool sendToUltrasonic(unsigned char *buffer, size_t dataLen);
bool receiveFromUltrasonic(unsigned char *buffer, size_t dataLen);
void reading();
int parseData(unsigned char high, unsigned char low);
private:
bool isRunning;
int fd;
int baudRate;
nativeClb uploadDataClb;
void* upperObject;
string data;
};
}
Ultrasonic.cpp
#include "Ultrasonic.h"
#include "g3log/g3log.hpp"
#include "base/json.h"
#include <errno.h>
#include <fcntl.h>
#include <termios.h>
#include <unistd.h>
#include <string>
#include <dirent.h>
#include <linux/usbdevice_fs.h>
#include <chrono>
#include <fstream>
#include <cmath>
#include <time.h>
#include <chrono>
namespace pudu {
using nlohmann::json;
const char *TAG = "Ultrasonic";
const string TTY = "/sys/class/tty/";
const int READ_LEN = 3;
const int WRITE_LEN = 4;
const int ULTRASONIC_SIZE = 8;
const int FLAG_BIT = 165; //0xa5
unsigned char command[][3] = {{0xd8, 0x02, 0xb0}, {0xd2, 0x02, 0xb0}, {0xd6, 0x02, 0xb0}, {0xdc, 0x02, 0xb0}, {0xd0, 0x02, 0xb0}, {0xd4, 0x02, 0xb0}, {0xde, 0x02, 0xb0}, {0xda, 0x02, 0xb0}};
Ultrasonic::Ultrasonic() : isRunning(false), fd(-1), baudRate(B115200), uploadDataClb(nullptr), upperObject(nullptr)
{
}
bool Ultrasonic::openUltrasonic(vector<string> devicesVector) {
CHECK_RTN_VAL(isRunning, true)
isRunning = true;
CHECK_DO (fd != -1,close(fd); fd = -1)
int id = -1;
for (auto device: devicesVector) {
id = getValidId(device);
CHECK_DO (id != -1, break)
}
CHECK_DO_RTN_VAL(id == -1, LOGWF(TAG, "getValid id error"), false)
string ttyDev = "/dev/ttyUSB" + to_string(id);
string cmd = "su -c \"chmod 777 ";
cmd = cmd + ttyDev + "\"";
system(cmd.c_str());
LOGDF(TAG, "start open Ultrasonic: %s", ttyDev.c_str());
fd = open(ttyDev.c_str(), O_RDWR | O_NOCTTY | O_SYNC); //no block: O_NDELAY
CHECK_DO_RTN_VAL(fd < 0, LOGWF(TAG, "open Ultrasonic[%s] failed: %s", ttyDev.c_str(), strerror(errno)), false)
int rlt = setSerialPort();
CHECK_DO_RTN_VAL(rlt, close(fd); fd = -1, false)
unique_ptr<thread> readThread = make_unique<thread>(bind(&Ultrasonic::reading, this));
readThread->detach();
return true;
}
void Ultrasonic::closeUltrasonic() {
isRunning = false;
close(fd);
fd = -1;
}
void Ultrasonic::reading()
{
int index = 0, data = 0;
bool rlt = false;
unsigned char buffer[WRITE_LEN] = {0};
while (isRunning) {
CHECK_DO(index >= ULTRASONIC_SIZE, index = 0)
rlt = sendToUltrasonic(command[index], READ_LEN);
CHECK_DO(!rlt, continue;)
rlt = receiveFromUltrasonic(buffer, WRITE_LEN);
LOGDF(TAG, "ultrasonic data: %d,%d,%d", (int)buffer[0], (int)buffer[1], (int)buffer[2]);
CHECK_DO(!rlt || (int)buffer[0] != FLAG_BIT, continue;)
data = parseData(buffer[1], buffer[2]);
if(uploadDataClb) {
json oneDataJson;
oneDataJson["location"] = (int)command[index][0];
oneDataJson["distance"] = data;
uploadDataClb(upperObject, oneDataJson.dump()); //发送超声波信息给算法
}
index++;
}
LOGDF(TAG, "thread end");
}
bool Ultrasonic::sendToUltrasonic(unsigned char *buffer, size_t dataLen)
{
ssize_t len = write(fd, buffer, dataLen);
CHECK_DO_RTN_VAL(len != dataLen, tcflush(fd, TCOFLUSH), -1)
return len == dataLen;
}
bool Ultrasonic::receiveFromUltrasonic(unsigned char *buffer, size_t dataLen)
{
int len = read(fd, buffer, dataLen);
CHECK_DO_RTN_VAL(len < 0, LOGDF(TAG, "read data failed") , -1)
return len == dataLen;
}
int Ultrasonic::parseData(unsigned char high, unsigned char low)
{
return (int)high * 16 * 16 + (int)low;
}
int Ultrasonic::setSerialPort ()
{
struct termios tty;
memset (&tty, 0, sizeof(tty));
CHECK_DO_RTN_VAL(tcgetattr(fd, &tty) != 0, LOGWF(TAG, "tcgetattr failed, error %d", errno), -1)
cfsetospeed (&tty, baudRate);
cfsetispeed (&tty, baudRate);
tty.c_cflag |= (CLOCAL | CREAD ); //CLOCAL--忽略 modem 控制线,本地连线, 不具数据机控制功能, CREAD--使能接收标志
tty.c_cflag &= ~CSIZE; // 先把数据位清零
tty.c_cflag |= CS8; // 把数据位设置为8位
tty.c_cflag &= ~PARENB; //无奇偶校验
tty.c_cflag &= ~CSTOPB; //设置停止位1
tty.c_lflag &= ~(ICANON | ECHO | ECHOE | ISIG); //c_lflag控制串口驱动如何管理输入的字符,此处关闭数据回传
tty.c_iflag &= ~(IXON | IXOFF | IXANY);
tty.c_cc[VMIN] = 4; // 至少读到4个字符read才会返回
tty.c_cc[VTIME] = 0.1; //0.1/10 s超时
tcflush(fd, TCIOFLUSH);
CHECK_DO_RTN_VAL (tcsetattr (fd, TCSANOW, &tty) != 0, LOGWF(TAG, "tcsetattr failed, error %d", errno), -1)
return 0;
}
void Ultrasonic::setUploadDataClb(const nativeClb &clb)
{
uploadDataClb = clb;
}
void Ultrasonic::setUpperObject(void *object) {
upperObject = object;
}
int Ultrasonic::getValidId(const string &device) {
struct dirent *entry;
int ret = 0;
auto deleter = [](DIR *p) {if(p) closedir(p); };
unique_ptr<DIR, decltype(deleter)> dir(opendir(TTY.c_str()), deleter);
CHECK_DO_RTN_VAL(dir == nullptr, LOGEF(TAG, "open dir error,path:%s", TTY.c_str()), -1)
while ((entry = readdir(dir.get())) != nullptr) {
CHECK_DO (entry->d_type != DT_LNK, continue;)
string path = TTY + entry->d_name;
char linkPath[256] = {0};
ret = readlink(path.c_str(), linkPath, 256);
CHECK_DO (ret == -1, LOGDF(TAG, "readlink error,errno=%d, path=%s", errno, path.c_str()); continue;)
string pidPath = TTY + linkPath + "/../../../../idProduct";
string vidPath = TTY + linkPath + "/../../../../idVendor";
fstream pidStream(pidPath, ios::in);
fstream vidStream(vidPath, ios::in);
CHECK_DO (!pidStream || !vidStream, continue;)
string pidTmp((istreambuf_iterator<char>(pidStream)), istreambuf_iterator<char>());
string vidTmp((istreambuf_iterator<char>(vidStream)), istreambuf_iterator<char>());
CHECK_DO (!pidTmp.empty(), pidTmp.pop_back())
CHECK_DO (!vidTmp.empty(), vidTmp.pop_back())
LOGDF(TAG, "pid:%s, vid:%s", pidTmp.c_str(), vidTmp.c_str());
if(vidTmp + pidTmp == device) {
auto found = path.find("ttyUSB");
CHECK_DO(found == string::npos, continue)
int index = atoi(path.substr(found + 6).c_str());
return index;
}
}
return -1;
}
}
cmake_minimum_required(VERSION 3.6.0)
include(cppbase/CMakeLists.txt)
include_directories(${CMAKE_INCLUDE_CURRENT_DIR})
add_library(ultrasonic_native SHARED Ultrasonic.cpp native_interface.cpp)
target_link_libraries(ultrasonic_native log pudutech_log)
kotlin实例:
package com.pudutech.mirsdk.hardware.ultrasonic
import com.pudutech.base.Pdlog
/**
* Ultrasonic native 声明
*/
class Ultrasonic {
private var internalCallback: IUltrasonicInternalCallback? = null
private val TAG = "Ultrasonic"
private var mNativeContext: Long = 0 //native 与 kotlin的上下文关联,存实例的地址
companion object {
init {
System.loadLibrary("ultrasonic_native")
}
/**
* 对外提供打开Ultrasonic
*/
fun open(): Ultrasonic {
return Ultrasonic()
}
}
init {
var devices = arrayOf("1a867523")
var ret: Boolean = openUltrasonic(devices)
Pdlog.i(TAG, "open ultrasonic rlt: $ret")
}
fun close() {
closeUltrasonic()
}
fun setCallback(callback: IUltrasonicInternalCallback) {
this.internalCallback = callback
}
fun postEventFromNative(str: String) {
Pdlog.i(TAG, "###ultrasonic data: $str")
internalCallback?.onCallback(str)
}
/*******************JNI函数*********************/
external fun openUltrasonic(devices: Array<String>): Boolean
external fun closeUltrasonic()
}