android studio 串口通信JNI、NDK配置

android studio 串口通信JNI、NDK配置

最近刚好要做关于串口通信的项目,需要用到JNI,于是就去百度关于android这一块串口通信怎么使用,但是发现很多的配置都是eclipse的而关于studio的jni配置就很零散,另外,也是第一次弄,所以遇到了很多的问题,现在就把配置的流程写出来,方便以后如果不记得了,可以查看,当然,如果有遇到同样问题的人也可以通过这篇博客能够得到帮助。

1、先确认android studio中是否有NDK,如果没有可以通过studio下载相应的ndk版本,如下图所示:


点击之后,如果有ndk,则选择ndk路径,如果没有ndk,则直接点击下载ndk。


还有另外一种就是点击SDK manager,选择ndk下载即可


在local.properties文件中添加ndk的路径,不过这个一般studio更新操作的时候会自动加上去,如果不行,那就手动添加ndk的路径

		ndk.dir=D\:\\Java\\sdk\\sdk\\android-ndk-r13-windows-x86_64\\android-ndk-r13
     在gradle.properties文件中添加这句话
		android.useDeprecatedNdk=true
 2、在你的项目中新建文件夹放入SerialPort.java文件


**
 * 串口类,用于打开,关闭和控制串口的类.会用到JNI层的动态库.
 *
 * @author Hong
 */
public class SerialPort {

	/**
	 * 连接本地JNI动态库.
	 */
	static {
		System.loadLibrary("SerialPort");
	}

	private static final String TAG = "SerialPort";

	private String deviceName;
	private int baudRate;

	private FileDescriptor fd;
	private FileInputStream fis;
	private FileOutputStream fos;

	/**
	 * 构造函数
	 *
	 * @param deviceName 串口设备名
	 * @param baudRate 串口波特率
	 */
	public SerialPort(String deviceName, int baudRate) {
		this.deviceName = deviceName;
		this.baudRate = baudRate;
	}

	/**
	 * 打开串口设备.由于android没有串口相关的API,所以串口操作会调用本地
	 * JNI层的函数.
	 *
	 * @return 打开成功返回true,否则返回false.
	 */
	public synchronized boolean openDevice() {
		if(deviceName == null || baudRate < 0)
			return false;

		fd = open(deviceName, baudRate);
		if(fd != null) {
			fis = new FileInputStream(fd);
			fos = new FileOutputStream(fd);
			return true;
		}
		return false;
	}

	/**
	 * 关闭串口设备.会调用本地JNI层函数.
	 * 同时关闭输入输出流
	 */
	public synchronized void closeDevice() {
		if(fd != null) {
			try {
				fos.close();
				fis.close();
				fos = null;
				fis = null;
			} catch (IOException e) {
				e.printStackTrace();
			}
			close(fd);
			fd = null;
		}
	}

	/**
	 * 读串口数据
	 *
	 * @param data 数据缓存
	 * @return 读取到的数据的实际大小
	 * @throws IOException
	 */
	public int read(byte[] data) throws IOException {
		if(isSerialOpened()) {
			return fis.read(data);
		}
		return 0;
	}

	/**
	 * 写入串口数据
	 *
	 * @param data 要写入的数据
	 * @param offset 数据偏移量
	 * @param count 数据大小
	 * @throws IOException
	 */
	public void write(byte[] data, int offset, int count) throws IOException {
		if(isSerialOpened()) {
			fos.write(data, offset, count);
			fos.flush();
		}
	}

	/**
	 * 判断串口是否已经打开.
	 *
	 * @return 打开返回true,否则返回false.
	 */
	public boolean isSerialOpened() {
		return fd != null;
	}

	/**
	 * 获取串口设备名
	 *
	 * @return 串口设备名
	 */
	public String getDeviceName() {
		return deviceName;
	}

	/**
	 * 设置串口设备名.只有在串口还没被打开的情况下才能设置成功.
	 *
	 * @param deviceName 串口设备名
	 */
	public void setDeviceName(String deviceName) {
		if(!isSerialOpened())
			this.deviceName = deviceName;
	}

	/**
	 * 获取串口波特率
	 *
	 * @return 波特率
	 */
	public int getBaudRate() {
		return baudRate;
	}

	/**
	 * 设置串口波特率.中有在串口还没被打开的情况下才能设置成功.
	 *
	 * @param baudRate
	 */
	public void setBaudRate(int baudRate) {
		if(!isSerialOpened())
			this.baudRate = baudRate;
	}

	/**
	 * 本地JNI层函数.打开串口设备.该函数的定义主体在 jni/serial_port.c文件中.
	 *
	 * @param path 要打开的串口设备的绝对路径.
	 * @param baudrate 串口设备的波特率.
	 * @return 成功打开返回串口设备的{@link FileDescriptor}实例.否则返回null.
	 */
	private native FileDescriptor open(String path, int baudrate);

	/**
	 * 本地JNI层函数.关闭串口设备.该函数的定义主体在 jni/serial_port.c文件中.
	 *
	 * @param fd 需要关闭的串口设备的{@link FileDescriptor}实例.
	 * @return 成功关闭返回true,否则返回false.
	 */
	private native boolean close(FileDescriptor fd);

}

接下来,我们要根据这个文件生成.h文件,操作如下
(1)右击View->tool windows->terminal


在该terminal中,使用cd命令进入到项目的java目录下,输入javah -jni 包名.SerialPort,回车即可。在java的目录下就会生成项
目相应的.h文件,将该.h文件拷贝到jni文件夹下,jni文件目录下还有一个Android.mk和SerialPort.cpp文件,
Android.mk

LOCAL_PATH:= $(call my-dir)

include $(CLEAR_VARS)

LOCAL_MODULE := SerialPort

LOCAL_C_INCLUDES := $(LOCAL_PATH)/include

LOCAL_SRC_FILES := SerialPort.cpp
	
LOCAL_LDLIBS += -llog

include $(BUILD_SHARED_LIBRARY)


SerialPort.cpp

#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include "你生成的.h文件,如xxx.h"


#define LOGI(...)  __android_log_print(ANDROID_LOG_INFO, "SerialPort_JNI", "[JiuGui] "__VA_ARGS__)
#define LOGE(...)  __android_log_print(ANDROID_LOG_ERROR, "SerialPort_JNI", "[JiuGui] "__VA_ARGS__)

static speed_t getBaudRate(jint baudRate);

/**
 * 与SerialPort.java中的open(String path, int baudRate)函数相关联。
 * 作用是打开串口设备
 */
JNIEXPORT jobject JNICALL (这里填写与你生成的.h文件中的open方法名一致,括号不用)
(JNIEnv *env, jobject object, jstring deviceName, jint baudRate)
{
	// 获取String的字符。不能直接的用device = deviceName;最后还要释放device指针。
	const char *device = env->GetStringUTFChars(deviceName, 0);
	LOGI("Device=%s, BaudRate= %d", device, baudRate);

	int fd = -1;
	do {
		// 打开设备
		fd = open(device, O_RDWR);
		if (-1 == fd) {
			LOGE("Can't Open %s[%d]: %s", device, errno, strerror(errno));
			break;
		}

		speed_t speed = getBaudRate(baudRate);
		if (speed == -1) {
			LOGE("Invalid baudRate");
			break;
		}

		//设置串口参数
		struct termios Opt;
		tcgetattr(fd, &Opt);
		cfmakeraw(&Opt);
		tcflush(fd, TCIFLUSH);
		cfsetispeed(&Opt, speed);
		cfsetospeed(&Opt, speed);

		if (tcsetattr(fd, TCSANOW, &Opt) != 0) {
			LOGE("SetupSerial![%d]: %s", errno, strerror(errno));
			break;
		}

		// 用JNI函数创建一个FileDescriptor类的实例
		jclass cFileDescriptor = env->FindClass("java/io/FileDescriptor");
		jmethodID iFileDescriptor = env->GetMethodID(cFileDescriptor, "", "()V");
		jfieldID descriptorID = env->GetFieldID(cFileDescriptor, "descriptor", "I");
		jobject fileDescriptor = env->NewObject(cFileDescriptor, iFileDescriptor);
		env->SetIntField(fileDescriptor, descriptorID, (jint)fd);

		// 用完这个device指针后要释放。
		env->ReleaseStringUTFChars(deviceName, device);
		return fileDescriptor;
	} while(0);

	if(fd != -1)
		close(fd); //关闭设备
	// 用完这个device指针后要释放。
	env->ReleaseStringUTFChars(deviceName, device);
	return NULL;
}

/**
 * 与SerialPort.java中的close(FileDescriptor fd);函数相关联。
 * 作用是关闭串口设备。
 */
JNIEXPORT jboolean JNICALL (这里填写与你生成的.h文件中的close方法名一致,括号不用)

(JNIEnv *env, jobject object, jobject descriptor)
{
	if(descriptor != NULL) {
		// 用JNI函数来得到参数descriptor里的“descriptor”属性值。
		// “descriptor”属性是在FileDescriptor类里的。
		jclass cFileDescriptor = env->FindClass("java/io/FileDescriptor");
		jfieldID descriptorID = env->GetFieldID(cFileDescriptor, "descriptor", "I");
		jint fd = env->GetIntField(descriptor, descriptorID);
		// 关闭设备。
		close(fd);
	}
	return 1;
}

/**
 * 获取波特率
 */
static speed_t getBaudRate(jint baudRate)
{
	switch(baudRate) {
	case 0: return B0;
	case 50: return B50;
	case 75: return B75;
	case 110: return B110;
	case 134: return B134;
	case 150: return B150;
	case 200: return B200;
	case 300: return B300;
	case 600: return B600;
	case 1200: return B1200;
	case 1800: return B1800;
	case 2400: return B2400;
	case 4800: return B4800;
	case 9600: return B9600;
	case 19200: return B19200;
	case 38400: return B38400;
	case 57600: return B57600;
	case 115200: return B115200;
	case 230400: return B230400;
	case 460800: return B460800;
	case 500000: return B500000;
	case 576000: return B576000;
	case 921600: return B921600;
	case 1000000: return B1000000;
	case 1152000: return B1152000;
	case 1500000: return B1500000;
	case 2000000: return B2000000;
	case 2500000: return B2500000;
	case 3000000: return B3000000;
	case 3500000: return B3500000;
	case 4000000: return B4000000;
	default: return -1;
	}
}

将SerialPort.cpp中的添加头文件部分,以及相应的open、close方法对应于头文件中的open、close方法。
3、接下来就是要编译jni,生成相应的.so文件,在此过程中,我们需要配置一下ndk的环境,进入到ndk的文件目录下,拷贝ndk的
目录,打开电脑的系统设置-》高级系统设置-》环境变量,编辑path,将该路径粘贴到path中
点击确认。接着,打开命令行,使用cd命令进入到你的项目的jni目录下,输入ndk-build命令,点击回车,就会在项目的main文件
目录下生成libs和obj文件,libs中就会有.so文件,然后在项目的app的build.gradle文件中添加


 defaultConfig {

        ndk {
            abiFilter "armeabi"
            moduleName "SerialPort"//你生成的.so文件的文件名
            ldLibs "log", "z", "m", "jnigraphics", "android"
        }
    }
还需要添加

android {
    sourceSets{ //设置.so文件路径
        main{
            jniLibs.srcDirs = ['libs']//里面添加你的libs文件路径,如果是src/main/libs,则里面填写['src/main/libs']
        }
    }
   
}

至此,配置应该是算成功了。




你可能感兴趣的:(Android)