Android Studio JNI开发(三)——实现通过IIC协议控制硬件设备
- 新建Java JNI 类 MyJni.java,并编写JNI native方法。
public class MyJNI {
static {
System.loadLibrary("jni_native");
}
public static native String getStringFromNative();
public static native int OpenDeviceNode();
public static native int CloseDeviceNode();
public static native int IICWriteRead(byte[] wBuf, int wLen, byte[] rBuf, int rLen);
}
- 生成头文件,cmd进入到工程路径 NdkDemo\app\src\main\java,执行命令生成.h头文件。
javah -classpath . -jni com.csu.jni.MyJNI
在MyJNI.java文件的同级目录(main\java\com.csu.jni)生成一个.h的头文件com_csu_jni_MyJNI.h。
/* DO NOT EDIT THIS FILE - it is machine generated */
#include <errno.h>
#include <fcntl.h>
#include <malloc.h>
#include <math.h>
#include <stdbool.h>
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <jni.h>
#include <android/log.h>
/* Header for class com_csu_jni_MyJNI */
#ifndef _Included_com_csu_jni_MyJNI
#define _Included_com_csu_jni_MyJNI
#ifdef __cplusplus
extern "C" {
#endif
/*
* Class: com_csu_jni_MyJNI
* Method: getStringFromNative
* Signature: ()Ljava/lang/String;
*/
JNIEXPORT jstring JNICALL Java_com_csu_jni_MyJNI_getStringFromNative
(JNIEnv *, jclass);
/*
* Class: com_csu_jni_MyJNI
* Method: OpenDeviceNode
* Signature: ()I
*/
JNIEXPORT jint JNICALL Java_com_csu_jni_MyJNI_OpenDeviceNode
(JNIEnv *, jclass);
/*
* Class: com_csu_jni_MyJNI
* Method: CloseDeviceNode
* Signature: ()I
*/
JNIEXPORT jint JNICALL Java_com_csu_jni_MyJNI_CloseDeviceNode
(JNIEnv *, jclass);
/*
* Class: com_csu_jni_MyJNI
* Method: IICWriteRead
* Signature: ([BI[BI)I
*/
JNIEXPORT jint JNICALL Java_com_csu_jni_MyJNI_IICWriteRead
(JNIEnv *, jclass, jbyteArray, jint, jbyteArray, jint);
#ifdef __cplusplus
}
#endif
#endif
- main下java同级目录新建jni目录,将step6生成的.h文件拷贝到jni目录,然后新建com_csu_jni_MyJNI.c文件,实现.h文件中的方法。
//
// Created by Admin on 2021/11/17.
//
#include "com_csu_jni_MyJNI.h"
#define LOG_TAG "MyJNI"
#define LOGI(...) __android_log_print(ANDROID_LOG_INFO,LOG_TAG,__VA_ARGS__)
#define LOGE(...) __android_log_print(ANDROID_LOG_ERROR,LOG_TAG,__VA_ARGS__)
/**
* My device name
*/
#define DEVICE_NAME "/dev/myjni"
/**
* File descriptor
*/
static int fd = -1;
int file_read(jbyte *rbuf, jint rlen) {
int ret;
if (fd < 0) {
LOGE("file_read(), JNI IS NOT OPENED\n");
}
LOGI("JNI file_read(), rlen=%d, rbuf=%d\n", rlen, rbuf[0]);
ret = read(fd, rbuf, rlen);
if (ret < 0) {
LOGE("JNI read failed, ret=%d\n", ret);
}
LOGI("JNI read success, ret=%d\n", ret);
return ret;
}
int file_write(jbyte *wbuf, jint wlen) {
int ret;
if (fd < 0) {
LOGE("file_write(), JNI IS NOT OPENED\n");
}
LOGI("JNI file_write(), wlen=%d, wbuf=%d\n", wlen, wbuf[0]);
ret = write(fd, wbuf, wlen);
if (ret < 0) {
LOGE("JNI write failed, ret=%d\n", ret);
}
LOGI("JNI write success, ret=%d\n", ret);
return ret;
}
/*
* Class: com_csu_jni_MyJNI
* Method: getStringFromNative
* Signature: ()Ljava/lang/String;
*/
JNIEXPORT jstring JNICALL Java_com_csu_jni_MyJNI_getStringFromNative(JNIEnv *env, jclass clazz) {
return (*env)->NewStringUTF(env, "My JNI string");
}
/*
* Class: com_csu_jni_MyJNI
* Method: OpenDeviceNode
* Signature: ()I
*/
JNIEXPORT jint JNICALL Java_com_csu_jni_MyJNI_OpenDeviceNode(JNIEnv *env, jclass clazz) {
fd = open(DEVICE_NAME, O_RDWR|O_NONBLOCK);
if (fd < 0) {
LOGE("MyJNI open device failed, fd =%d\n", fd);
}
LOGI("MyJNI open device Success, fd =%d\n", fd);
return fd;
}
/*
* Class: com_csu_jni_MyJNI
* Method: CloseDeviceNode
* Signature: ()I
*/
JNIEXPORT jint JNICALL Java_com_csu_jni_MyJNI_CloseDeviceNode(JNIEnv *env, jclass clazz) {
close(fd);
return 0;
}
/*
* Class: com_csu_jni_MyJNI
* Method: IICWriteRead
* Signature: ([BI[BI)I
*/
JNIEXPORT jint JNICALL Java_com_csu_jni_MyJNI_IICWriteRead(JNIEnv *env, jclass clazz, jbyteArray wBuf, jint wLen, jbyteArray rBuf, jint rLen) {
int ret = 0;
// 将Java层的数据拷到缓冲区
jbyte* bufw = (*env)->GetByteArrayElements(env, wBuf, NULL);
//jbyte* bufr = (*env)->GetByteArrayElements(env, rBuf, NULL);
//jint wlen = (*env)->GetArrayLength(env, wBuf);
jint len = (*env)->GetArrayLength(env, rBuf);
// 驱动会把要读取数据的寄存器放在首位返回,所以从驱动获取的数据buf长度要+1
len = len + 1;
// 新建一个数组,用于将驱动数据拷贝到缓冲区
jbyteArray jarr = (*env)->NewByteArray(env, len);
jbyte* jbuf = (*env)->GetByteArrayElements(env, jarr, NULL);
LOGI("JNI IICWriteRead(), wlen=%d, rlen=%d\n", wLen, rLen);
if (wLen > 1) {
ret = file_write(bufw, wLen);
LOGI("JNI IICWriteRead(), wlen=%d, ret=%d\n", wLen, ret);
}
// get register addr
jbuf[0] = bufw[0];
if (rLen > 0) {
ret = file_read(jbuf, rLen);
LOGI("JNI IICWriteRead(), rlen=%d, ret=%d, rbuf=%d.%d.%d.%d\n", rLen, ret, jbuf[1], jbuf[2], jbuf[3], jbuf[4]);
// 驱动返回缓存区的数据,首位为寄存器地址,真正获取的数据从下标1开始
memcpy(jbuf, jbuf + 1, len - 1);
// 将缓冲区的数据返回给Java形参
(*env)->SetByteArrayRegion(env, rBuf, 0, rLen, jbuf);
}
// 释放资源,避免内存泄漏
(*env)->ReleaseByteArrayElements(env, rBuf, jbuf, 0);
(*env)->DeleteLocalRef(env, jarr);
LOGI("JNI IICWriteRead(), ret=%d\n", ret);
return ret;
}
- jni目录下新建Android.mk文件。
LOCAL_PATH := $(call my-dir)
include $(CLEAR_VARS)
LOCAL_MODULE := jni_native
LOCAL_SRC_FILES := com_csu_jni_MyJNI.c
LOCAL_LDLIBS :=-llog
APP_ABI := all
include $(BUILD_SHARED_LIBRARY)
- cmd进入到工程路径 NdkDemo\app\src\main\java,执行命令编译生成.so文件。
ndk-build
在jni同级目录下会生成两个文件夹libs和obj。
-
将libs文件夹下的内容拷贝到module项目名下的libs目录。
-
module的build.gradle 文件的android{}节点中添加对libs的引用:
sourceSets {
main {
jniLibs.srcDirs = ['libs']
}
}
- Activity中调用JNI方法
MainActivity.java
public class MainActivity extends AppCompatActivity {
private static final String TAG = "lxy";
private TextView tvInfo;
private int id = 0;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
String str = MyJNI.getStringFromNative();
Log.i(TAG, "onCreate: rs = " + str);
Button btnOpen = findViewById(R.id.btn_open);
Button btnClose = findViewById(R.id.btn_close);
Button btnFwVersion = findViewById(R.id.btn_read_fw_version);
Button btnWrite = findViewById(R.id.btn_write);
Button btnRead = findViewById(R.id.btn_read);
tvInfo = findViewById(R.id.tv_info);
btnOpen.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
if (id > 0) {
Log.i(TAG, "onClick: device is opened!!!");
updateTextInfo("device is opened!!!");
} else {
id = MyJNI.OpenDeviceNode();
updateTextInfo("device open, id = " + id);
Log.i(TAG, "onClick: device open id = " + id);
}
}
});
btnClose.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
if (id > 0) {
int rs = MyJNI.CloseDeviceNode();
Log.i(TAG, "onClick: close device rs = " + rs);
updateTextInfo("close device, rs = " + rs);
id = 0;
} else {
Log.i(TAG, "onClick: device is not open");
updateTextInfo("device is not open");
}
}
});
btnFwVersion.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
if (id > 0) {
byte[] wBuf = {
0x10};
byte[] rBuf = new byte[256];
int rs = MyJNI.IICWriteRead(wBuf, 1, rBuf, 4);
Log.i(TAG, "onClick: ret = " + rs + ",rBuf= "
+ rBuf[0] + "." + rBuf[1] + "." + rBuf[2] + "." + rBuf[3]);
updateTextInfo("ret = " + rs + ",rBuf= "
+ rBuf[0] + "." + rBuf[1] + "." + rBuf[2] + "." + rBuf[3]);
}
}
});
btnWrite.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
if (id > 0) {
write();
}
}
});
btnRead.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
if (id > 0) {
read();
}
}
});
}
@Override
protected void onDestroy() {
super.onDestroy();
int rs = MyJNI.CloseDeviceNode();
Log.i(TAG, "onClick: close device rs = " + rs);
}
private void write() {
byte[] wbuf = {
0x00, 0x00, 0x01, 0x02, 0x03};
byte[] rbuf = new byte[10];
int rs = MyJNI.IICWriteRead(wbuf, 5, rbuf, 0);
Log.i(TAG, "write(): ret=" + rs);
updateTextInfo("write to fw : " + Arrays.toString(wbuf));
}
private void read() {
byte[] wbuf = {
0x00};
byte[] rbuf = new byte[256];
int rs = MyJNI.IICWriteRead(wbuf, 1, rbuf, 6);
Log.i(TAG, "read(): ret=" + rs + ", rbuf = " + Arrays.toString(rbuf));
updateTextInfo("read from fw : " + Arrays.toString(Arrays.copyOf(rbuf, 6)));
}
private void updateTextInfo(final String s) {
runOnUiThread(new Runnable() {
@Override
public void run() {
tvInfo.setText(s);
}
});
}
}
布局xml文件:
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Hello World!"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="My IIC JNI Test"
android:textSize="24sp"
android:textColor="#353535"
android:layout_marginTop="20dp"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"/>
<Button
android:id="@+id/btn_open"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="open"
android:layout_marginTop="80dp"
android:layout_marginStart="60dp"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintStart_toStartOf="parent"/>
<Button
android:id="@+id/btn_close"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="close"
android:layout_marginStart="20dp"
app:layout_constraintTop_toTopOf="@id/btn_open"
app:layout_constraintStart_toEndOf="@id/btn_open"/>
<Button
android:id="@+id/btn_read_fw_version"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="fw_ver"
android:layout_marginStart="20dp"
app:layout_constraintTop_toTopOf="@id/btn_open"
app:layout_constraintStart_toEndOf="@id/btn_close"/>
<Button
android:id="@+id/btn_write"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="w"
app:layout_constraintTop_toBottomOf="@id/btn_open"
app:layout_constraintStart_toStartOf="@id/btn_open"/>
<Button
android:id="@+id/btn_read"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="r"
android:layout_marginStart="20dp"
app:layout_constraintTop_toTopOf="@id/btn_write"
app:layout_constraintStart_toEndOf="@id/btn_write"/>
<TextView
android:id="@+id/tv_info"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="40dp"
app:layout_constraintTop_toBottomOf="@id/btn_write"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"/>
</androidx.constraintlayout.widget.ConstraintLayout>