Android AS下的NDK开发-Java与C混合编程(以硬件串口读写操作为例)

本文主要是介绍Android AS下的NDK开发-Java与C混合编程(以硬件串口读写操作为例),希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

Android Studio下NDK开发-Java与C混合编程(以硬件串口读写操作为例)

  • 让Android Studio支持C++编译
  • 新建支持C++的工程
  • 新建工程分析
    • cpp文件分析
    • 调用cpp文件的MainActivity分析
    • CMakeLists.txt文件分析
  • 串口设备读写
    • 修改一下cpp文件名字
    • 修改CMakeLists.txt
    • 新建SerialPort类
    • 创建对象实现数据读写

写在前面:本文所用的硬件平台是天嵌的E9开发板,烧的是安卓6.0.1系统,E9平台的如何烧录镜像等操作这里不作讲解,单纯当做一个有串口接口的Android设备来使用。当然一般的Android手机的硬件设备都可以用来操作,自己的手机具体有哪些硬件设备可以操作可以在系统的/dev目录下查看,串口、显示屏和触摸屏等都在目录中可以找到(截图只有一部分),这里默认读者已经熟悉Android系统和应用开发、了解C语言基本文件操作。

在这里插入图片描述

让Android Studio支持C++编译

在这里插入图片描述
在这里插入图片描述
选择NDK安装的路径:
在这里插入图片描述

新建支持C++的工程

在这里插入图片描述
然后一直下一步直到Finish:
在这里插入图片描述
工程新建好后在目录结构中比一般的Android工程多几个文件,其中如下是最为重要的:
在这里插入图片描述

新建工程分析

cpp文件分析

如上新建的工程可以直接编译运行到手机里面,只在中间显示了一串符串:
在这里插入图片描述
这一串字符来自native-lib.cpp文件:
在这里插入图片描述
这个文件对函数名做一个简单介绍:

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

对C、C++比较熟悉的应该知道第一行的作用。
第二行说明这个函数的返回值是string。
把函数名字分割成如下4个部分:
在这里插入图片描述
1: 每个函数最前面固定写一个Java
2: 将包名中的“.”换成“_”
在这里插入图片描述
3: 要调用c++文件的类名,c++文件会被编译成.so库,java通过加载库来调用c++函数,这里这个cpp文件会被编译成libnative–lib.so,这个名字也是由3部分组成,lib+native-lib+so,中间那个才是库名。
4: 最后一部分就是函数名字。
这个函数的作用就是返回字符串"Hello from C++"

调用cpp文件的MainActivity分析

在这里插入图片描述
9~11行:加载有cpp文件编译成的native-lib库。
25行:用关键字native修饰,说明库里面有一个名为stringFromJNI的函数。
函数的调用就在19行,把返回值显示在TextView上面。

CMakeLists.txt文件分析

这个文件有英文注释,描述比较清楚也就不再翻译,主要是表述了cpp文件名、编译过后生成的库名和最后需要链接的库名字。

串口设备读写

修改一下cpp文件名字

在这里插入图片描述
文件内容如下:

#include <termios.h>
#include <unistd.h>
#include <fcntl.h>
#include <jni.h>#include <android/log.h>static const char *TAG = "seril";#define LOGI(fmt, args...) __android_log_print(ANDROID_LOG_INFO,  TAG, fmt, ##args)
#define LOGD(fmt, args...) __android_log_print(ANDROID_LOG_DEBUG, TAG, fmt, ##args)
#define LOGE(fmt, args...) __android_log_print(ANDROID_LOG_ERROR, TAG, fmt, ##args)// 获取波特率枚举类型
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 (speed_t) -1;}
}extern "C"
JNIEXPORT jobject JNICALL
Java_com_test_ndk_ndkdemo_SerialPort_open(JNIEnv *env, jclass type, jstring path_, jint baudrate, jint flags) {int fd;speed_t speed;jobject mFileDescriptor;const char *path = env->GetStringUTFChars(path_, 0);// Check arguments{speed = getBaudrate(baudrate);if (speed == -1) {LOGD("Invalid baudrate");return NULL;}}// Opening device{LOGD("Opening serial port %s with flags 0x%x", path, O_RDWR | flags);fd = open(path, O_RDWR | flags);LOGD("open() fd = %d", fd);env->ReleaseStringUTFChars(path_, path);if (fd == -1) {// Throw an exceptionLOGD("Cannot open port");return NULL;}}// Configure device{struct termios cfg;LOGD("Configuring serial port");if (tcgetattr(fd, &cfg)) {LOGD("tcgetattr() failed");close(fd);return NULL;}cfmakeraw(&cfg);cfsetispeed(&cfg, speed);cfsetospeed(&cfg, speed);if (tcsetattr(fd, TCSANOW, &cfg)) {LOGD("tcsetattr() failed");close(fd);return NULL;}//获得串口指向配置结构的指针cfmakeraw(&cfg);// 设置串口数据位-----------------------------------------------//屏蔽其他标志cfg.c_cflag&=~CSIZE;//将数据位修改为8bitcfg.c_cflag |=CS8;//将修改后的termios数据设置到串口中if (tcsetattr(fd, TCSANOW, &cfg)) {close(fd);return env->NewStringUTF("uart data num set error");}//获得串口指向配置结构的指针cfmakeraw(&cfg);// 设置串口校验位-----------------------------------------------cfg.c_cflag &= ~PARENB;//将修改后的termios数据设置到串口中if (tcsetattr(fd, TCSANOW, &cfg)) {close(fd);return env->NewStringUTF("uart cheack set error");}//获得串口指向配置结构的指针cfmakeraw(&cfg);// 设置串口流控-----------------------------------------------cfg.c_cflag &= ~CRTSCTS;//将修改后的termios数据设置到串口中if (tcsetattr(fd, TCSANOW, &cfg)) {close(fd);return env->NewStringUTF("Data bit setting failed");}}// Create a corresponding file descriptor{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_test_ndk_ndkdemo_SerialPort_close(JNIEnv *env, jobject instance) {jclass SerialPortClass = env->GetObjectClass(instance);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(instance, mFdID);jint descriptor = env->GetIntField(mFd, descriptorID);LOGD("close(fd = %d)", descriptor);close(descriptor);
}

上面代码中关于串口的配置参数如下:

  1. 波特率
    波特率的参数格式上面代码中已经列出,这里就不再重复。
  2. 数据位
    CS5、CS6、CS7和CS8分别表示数据位为5、6、7和8。注意,在设置数据位前须先使用CSIZE做位屏蔽。具体设置代码:
	cfmakeraw(&cfg);// 设置串口数据位-----------------------------------------------//屏蔽其他标志cfg.c_cflag&=~CSIZE;//将数据位修改为8bitcfg.c_cflag |=CS8;//将修改后的termios数据设置到串口if (tcsetattr(fd, TCSANOW, &cfg)) {close(fd);//提示设置错误return env->NewStringUTF("Data bit setting failed");}
  1. 奇偶校验位
    可设置参数列表如下,设置方法和数据为设置方法类似:
设 置代 码
无校验cfg.c_cflag &= ~PARENB;
奇校验cfg.c_cflag |= (PARODD | PARENB);
偶校验cfg.c_cflag &= ~ PARENB; cfg.c_cflag &= ~PARODD;
空格cfg.c_cflag &= ~PARENB; cfg.c_cflag &= ~CSTOPB;
  1. 停止位
    里面停止位只允许有如下两种:
设 置代 码
1位cfg.c_cflag &= ~CSTOPB;
2位cfg.c_cflag |= CSTOPB;
  1. 数据流控制
    可设置参数列表如下,设置方法和数据为设置方法类似:
设 置代 码
无流控cfg.c_cflag &= ~CRTSCTS
奇校验cfg.c_cflag |= CRTSCTS
偶校验cfg.c_cflag |= IXON | IXOFF | IXANY

修改CMakeLists.txt

在这里插入图片描述

新建SerialPort类

在这里插入图片描述
SerialPort类内容如下:

package com.test.ndk.ndkdemo;import android.util.Log;
import java.io.File;
import java.io.FileDescriptor;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;public class SerialPort {static {System.loadLibrary("uart");}private static final String TAG = "SerialPort";private FileInputStream mFileInputStream;private FileOutputStream mFileOutputStream;private FileDescriptor mFd;public SerialPort(File device, int baudrate, int flags) throws SecurityException, IOException {/* Check access permission */if (!device.canRead() || !device.canWrite()) {try {/* Missing read/write permission, trying to chmod the file */Process su;su = Runtime.getRuntime().exec("/system/bin/su"); // 切换root用户String cmd = "chmod 777 " + 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();}}mFd = open(device.getAbsolutePath(), baudrate, flags);if (mFd == null) {Log.e(TAG, "native open returns null");throw new IOException();}mFileInputStream = new FileInputStream(mFd); // 获取串口输入流mFileOutputStream = new FileOutputStream(mFd); // 获取串口输出流}// Getters and setterspublic FileInputStream getInputStream() {return mFileInputStream;}public FileOutputStream getOutputStream() {return mFileOutputStream;}// cpp文件中的两个方法private native static FileDescriptor open(String path, int baudrate, int flags);public native void close();
}

顺便说一句,linux(Android也是linux)里面所有设备都是以文件形式呈现,操作文件即操作硬件接口。

创建对象实现数据读写

在MainActivity创建SerialPort对象,并获取SerialPort中的输入输出stream即可实现串口读写。界面中有一个Button,一个TextView。
MainActivity代码如下

package com.test.ndk.ndkdemo;import android.os.SystemClock;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.util.Log;
import android.view.View;
import android.widget.Button;
import android.widget.TextView;import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;public class MainActivity extends AppCompatActivity {private SerialPort mSerialPort;private FileInputStream mInputStream;private FileOutputStream mOutputStream;private Button send;private TextView tv;@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_main);tv = findViewById(R.id.sample_text);send = findViewById(R.id.send);// 点击按钮发送2个字节数据send.setOnClickListener(new View.OnClickListener() {@Overridepublic void onClick(View v) {byte[] data = new byte[2];data[0] = 5;data[1] = 6;try {mOutputStream.write(data);mOutputStream.flush();} catch (IOException e) {e.printStackTrace();}}});// 打开串口设备并获取输入输出流try {// 不同的设备串口的设备名字不一样,比如A8开发板的串口文件名字是s3c2410_serial2...,E9开发板提供了四个串口接口,串口1到串口4,串口1的文件名为ttySAC0,用于debug不开放.// ttySAC1到ttySAC3对应串口2~4,每个串口的硬件位置查看硬件原理图就可以知道,位置都在“/dev”下。mSerialPort = new SerialPort(new File("/dev/ttySAC3"), 115200, 0); // 打开连接协调器串口,波特率115200,无奇偶校验位mInputStream = mSerialPort.getInputStream(); // 获取串口输入流,用于读取串口数据mOutputStream = mSerialPort.getOutputStream(); // 获取串口输出流,用于通过串口发送数据} catch (IOException e) {System.out.println("无法打开串口,ttySAC3");e.printStackTrace();}new Thread(new ReadThread()).start();}/*** 数据读取线程*/private class ReadThread extends Thread {private int size;private byte[] buffer = new byte[512];@Overridepublic void run() {while (true) {if (mInputStream == null) {try {// A8串口:s3c2410_serial2...要用什么对应原理图// E9串口: ttySAC0(调试接口),ttySAC1、ttySAC2、ttySAC3具体位置看原理图mSerialPort = new SerialPort(new File("/dev/ttySAC3"), 115200, 0); // 打开连接协调器串口,波特率115200,无奇偶校验位mInputStream = mSerialPort.getInputStream(); // 获取串口输入流,用于读取串口数据mOutputStream = mSerialPort.getOutputStream(); // 获取串口输出流,用于通过串口发送数据} catch (Exception e) {System.out.println("无法打开串口");SystemClock.sleep(3000);}}else{try {  // buffer数组就是读取到的内容,size就是读取到的数量size = mInputStream.read(buffer, 0, buffer.length); // 从协调器串口读取数据if (size > 0) {Log.d("seril",size+"");}} catch (IOException e) {e.printStackTrace();}}}}}
}

E9开发版拓展接口图。如果读者用的A8或者2440等设备需要查看自己板子的原理图,并在“/dev”路径下找到相应的文件
在这里插入图片描述
最后将demo上传提供参考。

这篇关于Android AS下的NDK开发-Java与C混合编程(以硬件串口读写操作为例)的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/1093204

相关文章

Java编译生成多个.class文件的原理和作用

《Java编译生成多个.class文件的原理和作用》作为一名经验丰富的开发者,在Java项目中执行编译后,可能会发现一个.java源文件有时会产生多个.class文件,从技术实现层面详细剖析这一现象... 目录一、内部类机制与.class文件生成成员内部类(常规内部类)局部内部类(方法内部类)匿名内部类二、

SpringBoot实现数据库读写分离的3种方法小结

《SpringBoot实现数据库读写分离的3种方法小结》为了提高系统的读写性能和可用性,读写分离是一种经典的数据库架构模式,在SpringBoot应用中,有多种方式可以实现数据库读写分离,本文将介绍三... 目录一、数据库读写分离概述二、方案一:基于AbstractRoutingDataSource实现动态

揭秘Python Socket网络编程的7种硬核用法

《揭秘PythonSocket网络编程的7种硬核用法》Socket不仅能做聊天室,还能干一大堆硬核操作,这篇文章就带大家看看Python网络编程的7种超实用玩法,感兴趣的小伙伴可以跟随小编一起... 目录1.端口扫描器:探测开放端口2.简易 HTTP 服务器:10 秒搭个网页3.局域网游戏:多人联机对战4.

Springboot @Autowired和@Resource的区别解析

《Springboot@Autowired和@Resource的区别解析》@Resource是JDK提供的注解,只是Spring在实现上提供了这个注解的功能支持,本文给大家介绍Springboot@... 目录【一】定义【1】@Autowired【2】@Resource【二】区别【1】包含的属性不同【2】@

springboot循环依赖问题案例代码及解决办法

《springboot循环依赖问题案例代码及解决办法》在SpringBoot中,如果两个或多个Bean之间存在循环依赖(即BeanA依赖BeanB,而BeanB又依赖BeanA),会导致Spring的... 目录1. 什么是循环依赖?2. 循环依赖的场景案例3. 解决循环依赖的常见方法方法 1:使用 @La

Java枚举类实现Key-Value映射的多种实现方式

《Java枚举类实现Key-Value映射的多种实现方式》在Java开发中,枚举(Enum)是一种特殊的类,本文将详细介绍Java枚举类实现key-value映射的多种方式,有需要的小伙伴可以根据需要... 目录前言一、基础实现方式1.1 为枚举添加属性和构造方法二、http://www.cppcns.co

Elasticsearch 在 Java 中的使用教程

《Elasticsearch在Java中的使用教程》Elasticsearch是一个分布式搜索和分析引擎,基于ApacheLucene构建,能够实现实时数据的存储、搜索、和分析,它广泛应用于全文... 目录1. Elasticsearch 简介2. 环境准备2.1 安装 Elasticsearch2.2 J

Java中的String.valueOf()和toString()方法区别小结

《Java中的String.valueOf()和toString()方法区别小结》字符串操作是开发者日常编程任务中不可或缺的一部分,转换为字符串是一种常见需求,其中最常见的就是String.value... 目录String.valueOf()方法方法定义方法实现使用示例使用场景toString()方法方法

Java中List的contains()方法的使用小结

《Java中List的contains()方法的使用小结》List的contains()方法用于检查列表中是否包含指定的元素,借助equals()方法进行判断,下面就来介绍Java中List的c... 目录详细展开1. 方法签名2. 工作原理3. 使用示例4. 注意事项总结结论:List 的 contain

Android中Dialog的使用详解

《Android中Dialog的使用详解》Dialog(对话框)是Android中常用的UI组件,用于临时显示重要信息或获取用户输入,本文给大家介绍Android中Dialog的使用,感兴趣的朋友一起... 目录android中Dialog的使用详解1. 基本Dialog类型1.1 AlertDialog(