Android app通过JNI获取UART数据

前言

现在一直在弄蓝牙的项目,已经有一年时间没有弄Android的东西了。现在有时间想把以前弄的东西整理一下,方便自己以后需要用时翻出来看看。

这个APP是以前在MT6735平台Android 5.1(L1)调试验证UART外设发送过来的数据是否正确,想着也许后面调试还用的着,就记录一下。

硬件平台相关配置

APP需要操作平台设备硬件时,需要了解以下几点信息(我这里是在MTK平台上,其它Android平台也类似):

  • 需要操作的UART是否有权限?系统默认其它APP没有读写权限,这里需要添加权限,以下有2种方式:
// 方式一:使用adb直接给UART设备添加权限
$ adb shell
$ chmod 0666 /dev/ttyMT*

// 方式二:在init.rc文件里面添加权限,每次开机它都自动添加权限。和方式一相比就不用每次手动添加权限;
chmod 0666 /dev/ttyMT*
chown system system /dev/ttyMT*
  • MTK平台UART硬件物理端口名称软件字符设备名称对应关系:
硬件物理端口名称软件字符设备名称
UART1/dev/ttyMT0
UART2/dev/ttyMT1
UART3/dev/ttyMT2
UART4/dev/ttyMT3
  • 如何关闭SELinux权限? 在Android 5.0以上添加了这个权限,字符设备有read/write权限APP也不能直接访问字符设备。这里有3种处理方式:
  1. 设备是ENG版本(有root权限),可以使用adb将SELinux关闭。

    adb shell setenforce 0

  2. 将APP操作UART需要的权限添加到SELinux。(这里暂不介绍)

    在Kernel LOG / Main Log 中查询关键字 “avc:” 看看是否有SELinux Policy Exception。

  3. 代码中关闭selinux机制。

    文件路径:bootable/bootloader/lk/platform/mt6735/rules.mk
    文件中对应的内容:
    // choose one of following value -> 1: disabled/ 2: permissive /3: enforcing
    SELINUX_STATUS := 3修改为SELINUX_STATUS := 1

APP主要实现的功能

功能很简单,主要是将UART外设发送的数据实时的在APP上面显示出来。
在这里插入图片描述

APP层代码分析

APP层代码主要是2个class:MainActivityUartJniTool

  • MainActivity是主界面窗口类,主要处理步骤如下:
  1. uartJniTool = new UartJniTool(mhandler);实例化UartJniTool类,并且将mhandler传递给UartJniTool对象保存起来,方便后面更新UI。(Android规定只有主线程才能更新UI)
  2. 定义一个readUartBtn,被点击后uartJniTool.uartToolStart();开始获取uart data,再次被点击停止获取数据并且关闭uart。
  3. 定义一个writeUartBtn,发送固定的一段字符串,验证APP是否可以正常的发送数据。
  4. 定义的mhandler主要是将接收到消息的数据,显示在TextView上;
public class MainActivity extends AppCompatActivity {

    private static final String TAG= "MainActivity";

    private static final int UARTDATA= 8001;
    UartJniTool  uartJniTool;
    ToggleButton readUartBtn;
    Button writeUartBtn;
    TextView uartText;
    private int offset;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        uartJniTool = new UartJniTool(mhandler);
        //uartJniTool.uartToolStart();

        uartText = (TextView)findViewById(R.id.uartText);
        uartText.setMovementMethod(ScrollingMovementMethod.getInstance());

        readUartBtn = (ToggleButton)findViewById(R.id.readUartBtn);
        writeUartBtn = (Button)findViewById(R.id.writeUartBtn);

        readUartBtn.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                if (readUartBtn.isChecked()){
                   int result = uartJniTool.uartToolStart();
                    if (result < 0){
                        readUartBtn.setChecked(false);
                    }
                }else {
                    uartJniTool.uartToolStop();
                }
            }
        });

        writeUartBtn.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                uartJniTool.writeUartData();
            }
        });
    }

    Handler mhandler = new Handler(){
        @Override
        public void handleMessage(Message msg) {
            switch (msg.what) {
                case UARTDATA:
                    int uartData = msg.getData().getInt("UARTDATA");
                    Log.i(TAG, "handleMessage---->"+uartData);
                    uartText.append(numToHex8(uartData)+" ");
                    offset=uartText.getLineCount()*uartText.getLineHeight();
                    if(offset>uartText.getHeight()){
                        uartText.scrollTo(0,offset-uartText.getHeight());
                    }
                    break;
            }
            super.handleMessage(msg);
        }
    };

    public static String numToHex8(int b) {
        return String.format("0x%02x", b);//2表示需要两个16进行数
    }
  • UartJniTool是调用JNI native方法接口类,主要处理步骤如下:
  1. 加载本地libUartToothJni.so库,通过方法System.loadLibrary("UartToothJni");来加载。
  2. getUartDataHandler方法是JNI层获取到UART数据的后,主动回调该方法。getUartDataHandler方法拿到数据后,通过Handler发送给MainActivity显示出来。
  3. uartToolStart功能是打开和读取UART的数据,uartToolStop功能是关闭UART,writeUartData功能是发送固定的数据验证APP是否可以操作UART。
public class UartJniTool {

    private static final String TAG= "UartJniTool";
    private static final int UARTDATA= 8001;
    private Handler mhandler;
    static {
        System.loadLibrary("UartToothJni");
    }

    public UartJniTool(Handler mhandler) {
            this.mhandler = mhandler;
    }

    public void getUartDataHandler(int data)
    {
        Log.i(TAG, "getUartDataHandler---->"+data);
        Message msg = new Message();
        msg.what = UARTDATA;
        Bundle bundle = new Bundle();
        bundle.putInt("UARTDATA", data);
        msg.setData(bundle);//mes利用Bundle传递数据
        mhandler.sendMessage(msg);//用activity中的handler发送消息
    }

    public native int uartToolStart();
    public native int uartToolStop();
    //public native String getUartData();
    public native int writeUartData();
}

JNI native层代码分析

我们需要定义JNI的头文件,一般都是通过命令自动生成的,如下(具体操作百度吧,很多的):

javah -jni package name.class name

如果你比较熟悉JNI了,其实也不用javah命令生成头文件也可以。JNI函数的名称定义是有一定的规律:

Java flag + package name + class name + method name
For example: Java_com_lututong_uarttools_UartJniTool_uartToolStart

  • Java_com_lututong_uarttools_UartJniTool_uartToolStart对应app层UartJniTool类的uartToolStart方法,主要功能如下:
  1. 获取并且保存Java虚拟机和调用JNI的对象,为read thread主动调用app层方法做好准备。
  2. uart_tool_start主要open和init uart;
  • Java_com_lututong_uarttools_UartJniTool_uartToolStop主要功能是停止获取数据,并且关闭UART。
  • Java_com_lututong_uarttools_UartJniTool_writeUartData主要功能是发送固定的数据验证APP是否可以操作UART。
/*
 * Class:     com_lututong_uarttools_UartJniTool
 * Method:    uartToolStart
 * Signature: ()I
 */
JNIEXPORT jint JNICALL Java_com_lututong_uarttools_UartJniTool_uartToolStart(JNIEnv *env, jobject obj)
{
    int ret;

    //get java VM and object
    env->GetJavaVM(&gs_jvm);
    gs_object = env->NewGlobalRef(obj);

    // open and init uart
    ret = uart_tool_start();

    return ret;
}

/*
 * Class:     com_lututong_uarttools_UartJniTool
 * Method:    uartToolStop
 * Signature: ()I
 */
JNIEXPORT jint JNICALL Java_com_lututong_uarttools_UartJniTool_uartToolStop(JNIEnv *env, jobject obj)
{
    uart_tool_stop();

    return 0;
}

/*
 * Class:     com_lututong_uarttools_UartJniTool
 * Method:    writeUartData
 * Signature: ()I
 */
JNIEXPORT jint JNICALL Java_com_lututong_uarttools_UartJniTool_writeUartData(JNIEnv *env, jobject obj)
{
    char *buffer = "cai.zhong!!!";
    int ret = -1;
    if(serial_fd >0){
        ret = write(serial_fd, buffer, 9);
        //for (int i = 0; i < 7; i++)
           //ret =  write(serial_fd, &buffer[0], 1);
    }

    if(ret > 0)
        LOGE("[cai.zhong]Write data successfully!\n");
    return 0;
}
  • 打开UART并且设置波特率和其它属性。
int uart_speed(int speed) {
    switch (speed) {
        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;
        default:
            return B57600;
    }
}

int init_serial(int speed) {
    struct termios ti;
    int baudenum;
    int fd_bt;

 //   fd_bt = open(UART_DEVICE, O_RDWR | O_NOCTTY | O_NONBLOCK);
   fd_bt = open(UART_DEVICE, O_RDWR | O_NOCTTY );
    if (fd_bt < 0) {
        LOGE("Can't open serial port %s\n", UART_DEVICE);
        return -1;
    }
    else
        LOGI("open %s successfully!!! fd_bt=%d", UART_DEVICE, fd_bt);

    tcflush(fd_bt, TCIOFLUSH);

    if (tcgetattr(fd_bt, &ti) < 0) {
        LOGE("Can't get serial port setting\n");
        close(fd_bt);
        fd_bt=-1;
        return -1;
    }

    cfmakeraw(&ti);

    ti.c_cflag |= CLOCAL;
    ti.c_lflag = 0;

    ti.c_cflag &= ~CRTSCTS;
    ti.c_iflag &= ~(IXON | IXOFF | IXANY);

    /* Set baudrate */
    baudenum = uart_speed(speed);
  //  if ((baudenum == B115200) && (speed != 115200)) {
    if ((baudenum == B9600) && (speed != 9600)) {
        LOGE("Serial port baudrate not supported!\n");
        close(fd_bt);
        fd_bt=-1;
        return -1;
    }

    cfsetospeed(&ti, baudenum);
    cfsetispeed(&ti, baudenum);

    if (tcsetattr(fd_bt, TCSANOW, &ti) < 0) {
        LOGE("Can't set serial port setting\n");
        close(fd_bt);
        fd_bt=-1;
        return -1;
    }

    tcflush(fd_bt, TCIOFLUSH);

    return fd_bt;
}

  • thread_exit 关闭read线程。
  • bt_rx_monitorread线程将获取到的数据发送给APP层,主要的步骤如下:
  1. 通过gs_jvm获取JNIEnv环境变量,通过gs_object获取jclass对象类;
  2. 调用GetMethodID方法拿到UartJniTool类的getUartDataHandler方法;
  3. 通过read获取UART接收到的数据;
  4. 通过CallVoidMethod方法来将数据传给UartJniTool类的getUartDataHandler方法;
  • uart_tool_start初始化串口、创建和启动read thread;
  • uart_tool_stop杀掉read thread和关闭UART;
static void thread_exit(int signo)
{
    pthread_t tid = pthread_self();
    LOGI("Thread %lu exits\n", tid);
    pthread_exit(0);
}

void *bt_rx_monitor(void *ptr)
{
    char ucRxBuf;
    int ret;
    JNIEnv *env;
    jclass ClassCJM;
    jmethodID MethodGetUartDataHandler;
    jobject getUartDataHandlerDescriptor;

    LOGI("Thread %lu starts\n", rxThread);

#if 1
    if (gs_jvm != NULL) {
        gs_jvm->AttachCurrentThread((JNIEnv **)&env, NULL);
        ClassCJM = env->GetObjectClass(gs_object);

        MethodGetUartDataHandler = env->GetMethodID(ClassCJM, "getUartDataHandler", "(I)V");
        //etUartDataHandlerDescriptor = env->NewObject(ClassCJM, MethodGetUartDataHandler);
    }
#endif
    while (1) {
          if(serial_fd >0)
          {
                ret = read(serial_fd, &ucRxBuf, 1);
                LOGE("[cai.zhong]Receive data= %d ret=%d\n",ucRxBuf,ret);
                if(ret < -1){
                    LOGE("Receive packet from external device fails\n");
                    break;
                }else if(ret >0){
                    if (gs_object) {
                        env->CallVoidMethod(gs_object, MethodGetUartDataHandler, ucRxBuf);
                   }
                }
          }
    }
//#endif
    return 0;
}

int uart_tool_start(void)
{
    serial_fd = init_serial(9600);//115200
    if (serial_fd < 0) {
        LOGE("Initialize serial port to Device fails\n");
        return -1;
    }

    //signal(SIGRTMIN, thread_exit);
    /* Create RX monitor thread */
    pthread_create(&rxThread, NULL, bt_rx_monitor, NULL);

    LOGI("UART TOOL mode start\n");

    return 0;
}

void uart_tool_stop(void)
{
    /* Wait until thread exit */
    pthread_kill(rxThread, 0);
    //pthread_join(rxThread, NULL);
    //signal(SIGRTMIN, SIG_DFL);

    close(serial_fd);
    serial_fd = -1;
}

完整的工程代码

gitee地址: https://gitee.com/dianqi0901zc/UartTools

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值