Android JNI串口开发

最近公司要做一个展示牌形式的打卡器,Android系统且展示牌底部有个串口来接收大家手环打卡出勤的信息,这就需要读取每个手环或者工牌的id信息,因此用到了Android studio自带的jni功能在这里做一下总结笔记,方便以后直接拉下来使用.


  • 项目创建时如图,这样会包含native-lib.cpp文件,此文件是主要串口开发的C++函数,包含打开串口/串口设置/关闭串口等的设置

  •  native-lib.cpp文件
#include <jni.h>
#include <string>
#include <stdio.h>
#include <stdlib.h>
#include <fcntl.h>
#include <errno.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <string.h>
#include <stdint.h>
#include <termios.h>
#include <sys/ioctl.h>
#include <android/log.h>

static const char *TAG="serial_port";
#define LOGD(fmt, args...) __android_log_print(ANDROID_LOG_DEBUG, TAG, fmt, ##args)
#define LOGI(fmt, args...) __android_log_print(ANDROID_LOG_INFO,  TAG, fmt, ##args)
#define LOGE(fmt, args...) __android_log_print(ANDROID_LOG_ERROR, TAG, fmt, ##args)

extern "C" JNIEXPORT jstring

JNICALL
Java_com_jetsen_joy_carddemo_MainActivity_stringFromJNI(
        JNIEnv *env,
        jobject /* this */) {
    std::string hello = "Hello from C++";
    return env->NewStringUTF(hello.c_str());
}

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;
    }
}


extern "C"
JNIEXPORT jobject JNICALL
Java_com_jetsen_joy_carddemo_SerialPort_open(JNIEnv *env, jclass thiz, jstring path,
                                             jint baudrate) {

    int fd;
    speed_t speed;
    jobject mFileDescriptor;

    /* 这里获取设置的波特率 */
    {
        speed = getBaudrate(baudrate);
        if (speed == -1) {
            /* TODO: throw an exception */
            LOGE("Invalid baudrate");
            return NULL;
        }
    }

    /* 打开串口 */
    {
        jboolean iscopy;
        const char *path_utf = env->GetStringUTFChars(path, &iscopy);
//        LOGD("Opening serial port %s with flags 0x%x", path_utf, O_RDWR | flags);
//        fd = open(path_utf, O_RDWR | flags);
        fd = open(path_utf, O_RDWR | O_NOCTTY | O_NONBLOCK | O_NDELAY);
        LOGD("open() fd = %d", fd);
        env->ReleaseStringUTFChars(path, path_utf);
        if (fd == -1)
        {
            /* Throw an exception */
            LOGE("Cannot open port");
            /* TODO: throw an exception */
            return NULL;
        }
    }

    /*配置串口 */
    {
        struct termios cfg;
        LOGD("Configuring serial port");
        if (tcgetattr(fd, &cfg))//初始设置串口的配置
        {
            LOGE("tcgetattr() failed");
            close(fd);
            /* TODO: throw an exception */
            return NULL;
        }

        cfmakeraw(&cfg);
        cfsetispeed(&cfg, speed);// 结构中存储的输入波特率为 speed。如果输入波特率被设为0,实际输入波特率将等于输出波特率
        cfsetospeed(&cfg, speed);//设置 termios_p 指向的 termios 结构中存储的输出波特率为 speed

        if (tcsetattr(fd, TCSANOW, &cfg))//设置串口的配置,TCSANOW立即生效的意思
        {
            LOGE("tcsetattr() failed");
            close(fd);
            /* TODO: throw an exception */
            return NULL;
        }
    }

    /* 生成一个文件描述符FileDescriptor*/
    {
        jclass cFileDescriptor = env->FindClass("java/io/FileDescriptor");
        jmethodID iFileDescriptor = env->GetMethodID(cFileDescriptor, "<init>", "()V");
        jfieldID descriptorID = env->GetFieldID( cFileDescriptor, "descriptor", "I");
        mFileDescriptor = env->NewObject(cFileDescriptor, iFileDescriptor);
        env->SetIntField(mFileDescriptor, descriptorID, (jint)fd);
    }

    return mFileDescriptor;
}



extern "C"
JNIEXPORT void JNICALL
Java_com_jetsen_joy_carddemo_SerialPort_close(JNIEnv *env, jobject thiz) {

    jclass SerialPortClass = env->GetObjectClass( thiz);
    jclass FileDescriptorClass = env->FindClass( "java/io/FileDescriptor");

    jfieldID mFdID = env->GetFieldID( SerialPortClass, "mFd", "Ljava/io/FileDescriptor;");
    jfieldID descriptorID = env->GetFieldID(FileDescriptorClass, "descriptor", "I");

    jobject mFd = env->GetObjectField(thiz, mFdID);
    jint descriptor = env->GetIntField(mFd, descriptorID);

    LOGD("close(fd = %d)", descriptor);
    close(descriptor);

}
  •  新建一个SerialPort类,此类中主要是open close串口方法,创建此两个本地方法,运行时Android会自动调用jni中对应的方法,从而实现对指定串口的打开及关闭;
public class SerialPort {

    private final String TAG=SerialPort.class.getName();

    private FileDescriptor mFd;
    private FileInputStream mFileInputStream;
    private FileOutputStream mFileOutputStream;

    public SerialPort(File device, int baudrate) throws SecurityException, IOException {
        /* 这里是判断有串口的读写权限 */
        if (!device.canRead() || !device.canWrite()) {
            try {
                /*没有就需要申请权限,这个地方需要将Android设备root提供权限 */
                Process su;
                su = Runtime.getRuntime().exec("/system/bin/su");
                String cmd = "chmod 666 " + device.getAbsolutePath() + "\n"
                        + "exit\n";
                su.getOutputStream().write(cmd.getBytes());
                if ((su.waitFor() != 0) || !device.canRead()
                        || !device.canWrite()) {
                    throw new SecurityException();
                }
            } catch (Exception e) {
                e.printStackTrace();
                throw new SecurityException();
            }
        }
        //调用jni,打开串口
        this.mFd = open(device.getAbsolutePath(), baudrate);
        if(this.mFd == null) {
            Log.e(TAG, "native open returns null");
            throw new IOException();
        } else {
            //获取与串口通信的输入输出流
            this.mFileInputStream = new FileInputStream(this.mFd);
            this.mFileOutputStream = new FileOutputStream(this.mFd);
        }
    }

    // Getters and setters
    public InputStream getInputStream() {
        return mFileInputStream;
    }

    public OutputStream getOutputStream() {
        return mFileOutputStream;
    }

    static {
        //这个静态库的名字必须与之前我们在CMakeLists.txt中的命名相同
        System.loadLibrary("native-lib");
    }
    // JNI
    private native static FileDescriptor open(String path, int baudrate);
    public native void close();


}
 
  • SerialPortUtil串口工具类 ,对串口的打开,关闭,接收数据进行处理
public class SerialPortUtil {

    private String TAG = SerialPortUtil.class.getSimpleName();
    private SerialPort mSerialPort;
    private OutputStream mOutputStream;
    private InputStream mInputStream;
    private ReadThread mReadThread;
    private String path = "/dev/ttyS3";//不固定,我这里硬件设备读取手环信息的串口是ttyS3
    private int baudrate = 9600;//不固定,波特率 串口需要同设备提供商要到信息
    private static SerialPortUtil portUtil;
    private OnDataReceiveListener onDataReceiveListener = null;//声明接口变量
    private boolean isStop = false;



    //定义接口
    public interface OnDataReceiveListener {
        public void onDataReceive(byte[] buffer, int size);
    }

    public void setOnDataReceiveListener(OnDataReceiveListener dataReceiveListener) {
        onDataReceiveListener = dataReceiveListener;
    }

    public static SerialPortUtil getInstance() {
        if (null == portUtil) {
            portUtil = new SerialPortUtil();
            portUtil.onCreate();
        }
        return portUtil;
    }

    /**
     * 初始化串口通信
     */
    private void onCreate() {
        try {
            mSerialPort = new SerialPort(new File(path), baudrate);
            mOutputStream = mSerialPort.getOutputStream();
            mInputStream = mSerialPort.getInputStream();

            mReadThread = new ReadThread();
            isStop = false;
            mReadThread.start();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    /**
     * 循环读取手环信息
     */
    private class ReadThread extends Thread {

        @Override
        public void run() {
            super.run();
            while (!isStop && !isInterrupted()) {
                int size;
                try {
                    if (mInputStream == null)
                        return;

                    if (mInputStream.available() > 0){
                        byte[] buffer = new byte[mInputStream.available()];
                        size = mInputStream.read(buffer);
                        if (size > 0) {

                            if (null != onDataReceiveListener) {
                                onDataReceiveListener.onDataReceive(buffer, size);
                            }
                        }

                    }

                    try {
                        Thread.sleep(500);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }

                } catch (Exception e) {
                    e.printStackTrace();
                    return;
                }
            }
        }
    }

    /**
     * 关闭串口
     */
    public void closeSerialPort() {
        isStop = true;
        if (mReadThread != null) {
            mReadThread.interrupt();
        }
        if (mSerialPort != null) {
            mSerialPort.close();
        }
    }


}
  •  MainActivty类中做读取到数据的处理
public class MainActivity extends AppCompatActivity {

    // Used to load the 'native-lib' library on application startup.
    static {
        System.loadLibrary("native-lib");
    }

    TextView tv;
    SerialPortUtil mSerialPortUtil;


    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        // Example of a call to a native method
        tv = (TextView) findViewById(R.id.sample_text);
        tv.setText(stringFromJNI());



        mSerialPortUtil = SerialPortUtil.getInstance();
        
        mSerialPortUtil.setOnDataReceiveListener(new SerialPortUtil.OnDataReceiveListener() {
            @Override
            public void onDataReceive(final byte[] buffer, final int size) {

                runOnUiThread(new Runnable() {
                    @Override
                    public void run() {
                        //将读取到的数据进行处理
                        tv.setText("读到了size:" + size + "--" + new String(buffer) + "end");
                        Toast.makeText(MainActivity.this,
                                "收到了size:" + size + "--" + new String(buffer) + "end",
                                Toast.LENGTH_LONG).show();

                    }
                });

            }
        });

    }


    /**
     * A native method that is implemented by the 'native-lib' native library,
     * which is packaged with this application.
     */
    public native String stringFromJNI();
}
  • 添加权限
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />
<uses-permission android:name="android.permission.READ_PHONE_STATE" />
<uses-permission android:name="android.permission.CALL_PHONE" />

 

  • 0
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
好的,关于Android NDK串口通信的开发,我可以给您提供一些参考。在Android中,串口通信需要使用JNI(Java Native Interface)来调用C/C++代码来实现。下面是一个简单的流程: 1.编写C/C++代码实现串口通信,可以使用Linux系统提供的串口通信函数,如open、read、write等。 2.将C/C++代码编译为动态库(.so文件),可以使用NDK的工具链来进行编译。 3.在Java代码中使用JNI调用动态库中的函数,实现串口通信。 以下是一个简单的示例代码,仅供参考: C/C++代码(SerialPort.cpp): ```c++ #include <stdio.h> #include <stdlib.h> #include <fcntl.h> #include <termios.h> #include <unistd.h> int fd; int openPort(char* portName, int baudRate) { struct termios options; fd = open(portName, O_RDWR | O_NOCTTY | O_NDELAY); if (fd == -1) { perror("openPort: Unable to open serial port - "); return -1; } fcntl(fd, F_SETFL, FNDELAY); tcgetattr(fd, &options); cfsetispeed(&options, baudRate); cfsetospeed(&options, baudRate); options.c_cflag |= (CLOCAL | CREAD); options.c_cflag &= ~PARENB; options.c_cflag &= ~CSTOPB; options.c_cflag &= ~CSIZE; options.c_cflag |= CS8; tcsetattr(fd, TCSANOW, &options); return fd; } void closePort() { close(fd); } int readData(char* buffer, int length) { int n = read(fd, buffer, length); return n; } int writeData(char* buffer, int length) { int n = write(fd, buffer, length); return n; } ``` Java代码: ```java public class SerialPort { static { System.loadLibrary("serial_port"); } public static native int openPort(String portName, int baudRate); public static native void closePort(); public static native int readData(byte[] buffer, int length); public static native int writeData(byte[] buffer, int length); } ``` 调用示例: ```java int fd = SerialPort.openPort("/dev/ttyS1", 9600); byte[] buffer = new byte[1024]; int n = SerialPort.readData(buffer, buffer.length); SerialPort.writeData(buffer, n); SerialPort.closePort(); ``` 需要注意的是,在AndroidManifest.xml文件中,需要添加以下权限: ```xml <uses-permission android:name="android.permission.INTERNET"/> <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/> <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/> <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"/> <uses-permission android:name="android.permission.ACCESS_WIFI_STATE"/> <uses-permission android:name="android.permission.CHANGE_WIFI_STATE"/> <uses-permission android:name="android.permission.CHANGE_NETWORK_STATE"/> <uses-permission android:name="android.permission.WAKE_LOCK"/> <uses-permission android:name="android.permission.READ_PHONE_STATE"/> <uses-permission android:name="android.permission.RECORD_AUDIO"/> <uses-permission android:name="android.permission.MODIFY_AUDIO_SETTINGS"/> <uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW"/> <uses-permission android:name="android.permission.CAMERA"/> <uses-permission android:name="android.permission.FLASHLIGHT"/> <uses-permission android:name="android.permission.VIBRATE"/> <uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION"/> <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION"/> <uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED"/> <uses-permission android:name="android.permission.ACCESS_NOTIFICATION_POLICY"/> <uses-permission android:name="android.permission.REQUEST_IGNORE_BATTERY_OPTIMIZATIONS"/> <uses-permission android:name="android.permission.REQUEST_INSTALL_PACKAGES"/> <uses-permission android:name="android.permission.WRITE_SETTINGS"/> <uses-permission android:name="android.permission.BLUETOOTH"/> <uses-permission android:name="android.permission.BLUETOOTH_ADMIN"/> <uses-permission android:name="android.permission.NFC"/> <uses-permission android:name="android.permission.READ_CONTACTS"/> <uses-permission android:name="android.permission.WRITE_CONTACTS"/> <uses-permission android:name="android.permission.READ_CALENDAR"/> <uses-permission android:name="android.permission.WRITE_CALENDAR"/> <uses-permission android:name="android.permission.RECORD_VIDEO"/> <uses-permission android:name="android.permission.READ_SMS"/> <uses-permission android:name="android.permission.RECEIVE_SMS"/> <uses-permission android:name="android.permission.SEND_SMS"/> <uses-permission android:name="android.permission.CALL_PHONE"/> <uses-permission android:name="android.permission.READ_CALL_LOG"/> <uses-permission android:name="android.permission.WRITE_CALL_LOG"/> <uses-permission android:name="android.permission.USE_SIP"/> <uses-permission android:name="android.permission.PROCESS_OUTGOING_CALLS"/> <uses-permission android:name="android.permission.ADD_VOICEMAIL"/> <uses-permission android:name="android.permission.USE_FINGERPRINT"/> <uses-permission android:name="android.permission.BODY_SENSORS"/> <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/> <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/> ```
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值