安卓串口开发--jni文件、.so文件环境搭建(详细步骤)

窜口通信需要用到jni,对于jni开发,我上网看了很多教程,五花八门,很多都不成功,所以自己来写一份自己做成功的,详细的,以供以后忘记的我学习和大家一起学习!

1、新建项目MySerialPort,确保自己的NDK已经配置完成(不会的百度教程),这里在gradle.properties里面加一句话

android.useDeprecatedNdk=true

可能有的朋友运行之后有问题,会有错误提示让你换一句话,我这里换成了

android.deprecatedNdkCompileLease=1525617089474
安卓串口开发--jni文件、.so文件环境搭建(详细步骤)_第1张图片

当然如果没有问题,最好了。

2、在java文件夹下面新建android_serialport_api包,在这个包里面建立SerialPort类,内容为:

package android_serialport_api;

import java.io.FileDescriptor;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;

/**
        * 串口类,用于打开,关闭和控制串口的类.会用到JNI层的动态库.
        *
        */
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);
}

这时候,可能在open和close函数是红字,不要管他。

3、在AS下面的terminal端口,进入java目录,输入命令

javah -jni android_serialport_api.SerialPort
运行之后在java文件下就会生成
android_serialport_api_SerialPort.h 文件

4、在java文件下右击新建folder->JNI文件,把之前的.h文件拖进来,在新建Android.mk文件和SerialPort.cpp文件

安卓串口开发--jni文件、.so文件环境搭建(详细步骤)_第2张图片

安卓串口开发--jni文件、.so文件环境搭建(详细步骤)_第3张图片

代码如下:

//
// Created by Administrator on 2018/5/6 0006.
//

#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include "android_serialport_api_SerialPort.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 Java_android_1serialport_1api_SerialPort_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 Java_android_1serialport_1api_SerialPort_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;
    }
}

记得修改include引用的报名,和open、close的方法名和.h文件一致

5、确保ndk设置好了环境变量,在AS中terminal进入我们建立的jni目录,输入ndk-build,回车,在main文件会生成libs和obj文件

安卓串口开发--jni文件、.so文件环境搭建(详细步骤)_第4张图片

6、然后在app的build.gradle里面加上

安卓串口开发--jni文件、.so文件环境搭建(详细步骤)_第5张图片

安卓串口开发--jni文件、.so文件环境搭建(详细步骤)_第6张图片

7、clean、rebuld一下,就大功告成了。

至此,jni、.so文件环境配置完成。

你可能感兴趣的:(Android)