Android开发之串口编程原理和实现方式

提到串口编程,就不得不提到JNI,不得不提到JavaAPI中的文件描述符类:FileDescriptor。下面我分别对JNI、FileDescriptor以及串口的一些知识点和实现的源码进行分析说明。这里主要是参考了开源项目android-serialport-api。

串口编程需要了解的基本知识点:对于串口编程,我们只需对串口进行一系列的设置,然后打开串口,这些操作我们可以参考串口调试助手的源码进行学习。在Java中如果要实现串口的读写功能只需操作文件设备类:FileDescriptor即可,其他的事都由驱动来完成不用多管!当然,你想了解,那就得看驱动代码了。这里并不打算对驱动进行说明,只初略阐述应用层的实现方式。

(一)JNI:

关于JNI的文章网上有很多,不再多做解释,想详细了解的朋友可以查看云中漫步的技术文章,写得很好,分析也很全面,那么在这篇拙文中我强调3点:

1、如何将编译好的SO文件打包到APK中?(方法很简单,直接在工程目录下新建文件夹 libs/armeabi,将SO文件Copy到此目录即可)

2、命名要注意的地方?(在编译好的SO文件中,将文件重命名为:libfilename.so即可。其中filename.so是编译好后生成的文件)

3、MakeFile文件的编写(不用多说,可以直接参考package/apps目录下用到JNI的相关项目写法)

这是关键的代码:

复制代码 代码如下:

<span style="font-size:18px;"> int fd;

speed_t speed;

jobject mFileDescriptor;

/* Check arguments */

{

speed = getBaudrate(baudrate);

if (speed == -1) {

/* TODO: throw an exception */

LOGE("Invalid baudrate");

return NULL;

}

}

/* Opening device */

{

jboolean iscopy;

const char *path_utf = (*env)->GetStringUTFChars(env, path, &iscopy);

LOGD("Opening serial port %s with flags 0x%x", path_utf, O_RDWR | flags);

fd = open(path_utf, O_RDWR | flags);

LOGD("open() fd = %d", fd);

(*env)->ReleaseStringUTFChars(env, path, path_utf);

if (fd == -1)

{

/* Throw an exception */

LOGE("Cannot open port");

/* TODO: throw an exception */

return NULL;

}

}

/* Configure device */

{

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

cfsetospeed(&cfg, speed);

if (tcsetattr(fd, TCSANOW, &cfg))

{

LOGE("tcsetattr() failed");

close(fd);

/* TODO: throw an exception */

return NULL;

}

}

</span>

(二)FileDescritor:

文件描述符类的实例用作与基础机器有关的某种结构的不透明句柄,该结构表示开放文件、开放套接字或者字节的另一个源或接收者。文件描述符的主要实际用途是创建一个包含该结构的FileInputStream 或FileOutputStream。这是API的描述,不太好理解,其实可简单的理解为:FileDescritor就是对一个文件进行读写。

(三)实现串口通信细节

1) 建工程:SerialDemo包名:org.winplus.serial,并在工程目录下新建jni和libs两个文件夹和一个org.winplus.serial.utils,如下图:

2) 新建一个类:SerialPortFinder,添加如下代码:

复制代码 代码如下:

<span style="font-size:18px;">package org.winplus.serial.utils;

import java.io.File;

import java.io.FileReader;

import java.io.IOException;

import java.io.LineNumberReader;

import java.util.Iterator;

import java.util.Vector;

import android.util.Log;

public class SerialPortFinder {

private static final String TAG = "SerialPort";

private Vector<Driver> mDrivers = null;

public class Driver {

public Driver(String name, String root) {

mDriverName = name;

mDeviceRoot = root;

}

private String mDriverName;

private String mDeviceRoot;

Vector<File> mDevices = null;

public Vector<File> getDevices() {

if (mDevices == null) {

mDevices = new Vector<File>();

File dev = new File("/dev");

File[] files = dev.listFiles();

int i;

for (i = 0; i < files.length; i++) {

if (files[i].getAbsolutePath().startsWith(mDeviceRoot)) {

Log.d(TAG, "Found new device: " + files[i]);

mDevices.add(files[i]);

}

}

}

return mDevices;

}

public String getName() {

return mDriverName;

}

}

Vector<Driver> getDrivers() throws IOException {

if (mDrivers == null) {

mDrivers = new Vector<Driver>();

LineNumberReader r = new LineNumberReader(new FileReader(

"/proc/tty/drivers"));

String l;

while ((l = r.readLine()) != null) {

// Issue 3:

// Since driver name may contain spaces, we do not extract

// driver name with split()

String drivername = l.substring(0, 0x15).trim();

String[] w = l.split(" +");

if ((w.length >= 5) && (w[w.length - 1].equals("serial"))) {

Log.d(TAG, "Found new driver " + drivername + " on "

+ w[w.length - 4]);

mDrivers.add(new Driver(drivername, w[w.length - 4]));

}

}

r.close();

}

return mDrivers;

}

public String[] getAllDevices() {

Vector<String> devices = new Vector<String>();

// Parse each driver

Iterator<Driver> itdriv;

try {

itdriv = getDrivers().iterator();

while (itdriv.hasNext()) {

Driver driver = itdriv.next();

Iterator<File> itdev = driver.getDevices().iterator();

while (itdev.hasNext()) {

String device = itdev.next().getName();

String value = String.format("%s (%s)", device,

driver.getName());

devices.add(value);

}

}

} catch (IOException e) {

e.printStackTrace();

}

return devices.toArray(new String[devices.size()]);

}

public String[] getAllDevicesPath() {

Vector<String> devices = new Vector<String>();

// Parse each driver

Iterator<Driver> itdriv;

try {

itdriv = getDrivers().iterator();

while (itdriv.hasNext()) {

Driver driver = itdriv.next();

Iterator<File> itdev = driver.getDevices().iterator();

while (itdev.hasNext()) {

String device = itdev.next().getAbsolutePath();

devices.add(device);

}

}

} catch (IOException e) {

e.printStackTrace();

}

return devices.toArray(new String[devices.size()]);

}

}

</span>

上面这个类在“android-serialport-api串口工具测试随笔”中有详细的说明,我就不多说了。

3)新建SerialPort类,这个类主要用来加载SO文件,通过JNI的方式打开关闭串口

复制代码 代码如下:

<span style="font-size:18px;">package org.winplus.serial.utils;

import java.io.File;

import java.io.FileDescriptor;

import java.io.FileInputStream;

import java.io.FileOutputStream;

import java.io.IOException;

import java.io.InputStream;

import java.io.OutputStream;

import android.util.Log;

public class SerialPort {

private static final String TAG = "SerialPort";

/*

* Do not remove or rename the field mFd: it is used by native method

* close();

*/

private FileDescriptor mFd;

private FileInputStream mFileInputStream;

private FileOutputStream mFileOutputStream;

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");

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();

}

}

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 setters

public InputStream getInputStream() {

return mFileInputStream;

}

public OutputStream getOutputStream() {

return mFileOutputStream;

}

// JNI

private native static FileDescriptor open(String path, int baudrate,

int flags);

public native void close();

static {

System.loadLibrary("serial_port");

}

}

</span>

4) 新建一个MyApplication 继承android.app.Application,用来对串口进行初始化和关闭串口

复制代码 代码如下:

<span style="font-size:18px;">package org.winplus.serial;

import java.io.File;

import java.io.IOException;

import java.security.InvalidParameterException;

import org.winplus.serial.utils.SerialPort;

import org.winplus.serial.utils.SerialPortFinder;

import android.content.SharedPreferences;

public class MyApplication extends android.app.Application {

public SerialPortFinder mSerialPortFinder = new SerialPortFinder();

private SerialPort mSerialPort = null;

public SerialPort getSerialPort() throws SecurityException, IOException, InvalidParameterException {

if (mSerialPort == null) {

/* Read serial port parameters */

SharedPreferences sp = getSharedPreferences("android_serialport_api.sample_preferences", MODE_PRIVATE);

String path = sp.getString("DEVICE", "");

int baudrate = Integer.decode(sp.getString("BAUDRATE", "-1"));

/* Check parameters */

if ( (path.length() == 0) || (baudrate == -1)) {

throw new InvalidParameterException();

}

/* Open the serial port */

mSerialPort = new SerialPort(new File(path), baudrate, 0);

}

return mSerialPort;

}

public void closeSerialPort() {

if (mSerialPort != null) {

mSerialPort.close();

mSerialPort = null;

}

}

}

</span>

5) 新建一个继承抽象的Activity类,主要用于读取串口的信息

复制代码 代码如下:

<span style="font-size:18px;">package org.winplus.serial;

import java.io.IOException;

import java.io.InputStream;

import java.io.OutputStream;

import java.security.InvalidParameterException;

import org.winplus.serial.utils.SerialPort;

import android.app.Activity;

import android.app.AlertDialog;

import android.content.DialogInterface;

import android.content.DialogInterface.OnClickListener;

import android.os.Bundle;

public abstract class SerialPortActivity extends Activity {

protected MyApplication mApplication;

protected SerialPort mSerialPort;

protected OutputStream mOutputStream;

private InputStream mInputStream;

private ReadThread mReadThread;

private class ReadThread extends Thread {

@Override

public void run() {

super.run();

while (!isInterrupted()) {

int size;

try {

byte[] buffer = new byte[64];

if (mInputStream == null)

return;

/**

* 这里的read要尤其注意,它会一直等待数据,等到天荒地老,海枯石烂。如果要判断是否接受完成,只有设置结束标识,或作其他特殊的处理。

*/

size = mInputStream.read(buffer);

if (size > 0) {

onDataReceived(buffer, size);

}

} catch (IOException e) {

e.printStackTrace();

return;

}

}

}

}

private void DisplayError(int resourceId) {

AlertDialog.Builder b = new AlertDialog.Builder(this);

b.setTitle("Error");

b.setMessage(resourceId);

b.setPositiveButton("OK", new OnClickListener() {

public void onClick(DialogInterface dialog, int which) {

SerialPortActivity.this.finish();

}

});

b.show();

}

@Override

protected void onCreate(Bundle savedInstanceState) {

super.onCreate(savedInstanceState);

mApplication = (MyApplication) getApplication();

try {

mSerialPort = mApplication.getSerialPort();

mOutputStream = mSerialPort.getOutputStream();

mInputStream = mSerialPort.getInputStream();

/* Create a receiving thread */

mReadThread = new ReadThread();

mReadThread.start();

} catch (SecurityException e) {

DisplayError(R.string.error_security);

} catch (IOException e) {

DisplayError(R.string.error_unknown);

} catch (InvalidParameterException e) {

DisplayError(R.string.error_configuration);

}

}

protected abstract void onDataReceived(final byte[] buffer, final int size);

@Override

protected void onDestroy() {

if (mReadThread != null)

mReadThread.interrupt();

mApplication.closeSerialPort();

mSerialPort = null;

super.onDestroy();

}

}

</span>

6)编写string.xml 以及baudrates.xml文件

在string.xml文件中添加:

复制代码 代码如下:

<span style="font-size:18px;"> <string name="error_configuration">Please configure your serial port first.</string>

<string name="error_security">You do not have read/write permission to the serial port.</string>

<string name="error_unknown">The serial port can not be opened for an unknown reason.</string>

</span>

在baudrates.xml文件中添加

复制代码 代码如下:

<span style="font-size:18px;"><?xml version="1.0" encoding="utf-8"?>

<resources>

<string-array name="baudrates_name">

<item>50</item>

<item>75</item>

<item>110</item>

<item>134</item>

<item>150</item>

<item>200</item>

<item>300</item>

<item>600</item>

<item>1200</item>

<item>1800</item>

<item>2400</item>

<item>4800</item>

<item>9600</item>

<item>19200</item>

<item>38400</item>

<item>57600</item>

<item>115200</item>

<item>230400</item>

<item>460800</item>

<item>500000</item>

<item>576000</item>

<item>921600</item>

<item>1000000</item>

<item>1152000</item>

<item>1500000</item>

<item>2000000</item>

<item>2500000</item>

<item>3000000</item>

<item>3500000</item>

<item>4000000</item>

</string-array>

<string-array name="baudrates_value">

<item>50</item>

<item>75</item>

<item>110</item>

<item>134</item>

<item>150</item>

<item>200</item>

<item>300</item>

<item>600</item>

<item>1200</item>

<item>1800</item>

<item>2400</item>

<item>4800</item>

<item>9600</item>

<item>19200</item>

<item>38400</item>

<item>57600</item>

<item>115200</item>

<item>230400</item>

<item>460800</item>

<item>500000</item>

<item>576000</item>

<item>921600</item>

<item>1000000</item>

<item>1152000</item>

<item>1500000</item>

<item>2000000</item>

<item>2500000</item>

<item>3000000</item>

<item>3500000</item>

<item>4000000</item>

</string-array>

</resources>

</span>

7)开始编写界面了:在main.xml布局文件中添加两个编辑框,一个用来发送命令,一个用来接收命令:

复制代码 代码如下:

<span style="font-size:18px;"><?xml version="1.0" encoding="utf-8"?>

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"

android:layout_width="fill_parent"

android:layout_height="fill_parent"

android:orientation="vertical" >

<EditText

android:id="@+id/EditTextReception"

android:layout_width="fill_parent"

android:layout_height="fill_parent"

android:layout_weight="1"

android:gravity="top"

android:hint="Reception"

android:isScrollContainer="true"

android:scrollbarStyle="insideOverlay" >

</EditText>

<EditText

android:id="@+id/EditTextEmission"

android:layout_width="fill_parent"

android:layout_height="wrap_content"

android:hint="Emission"

android:lines="1" >

</EditText>

</LinearLayout>

</span>

8) SerialDemoActivity类的实现:

复制代码 代码如下:

<span style="font-size:18px;">package org.winplus.serial;

import java.io.IOException;

import android.os.Bundle;

import android.view.KeyEvent;

import android.widget.EditText;

import android.widget.TextView;

import android.widget.TextView.OnEditorActionListener;

public class SerialDemoActivity extends SerialPortActivity{

EditText mReception;

@Override

protected void onCreate(Bundle savedInstanceState) {

super.onCreate(savedInstanceState);

setContentView(R.layout.main);

// setTitle("Loopback test");

mReception = (EditText) findViewById(R.id.EditTextReception);

EditText Emission = (EditText) findViewById(R.id.EditTextEmission);

Emission.setOnEditorActionListener(new OnEditorActionListener() {

public boolean onEditorAction(TextView v, int actionId, KeyEvent event) {

int i;

CharSequence t = v.getText();

char[] text = new char[t.length()];

for (i=0; i<t.length(); i++) {

text[i] = t.charAt(i);

}

try {

mOutputStream.write(new String(text).getBytes());

mOutputStream.write('\n');

} catch (IOException e) {

e.printStackTrace();

}

return false;

}

});

}

@Override

protected void onDataReceived(final byte[] buffer, final int size) {

runOnUiThread(new Runnable() {

public void run() {

if (mReception != null) {

mReception.append(new String(buffer, 0, size));

}

}

});

}

}

</span>

写到这里,代码基本上写完了。下面就是要实现JNI层的功能了,要实现JNI,必须首先生成头文件,头文件的生成方式也很简单, 我们编译工程,在终端输入 javah org.winplus.serial.utils.SerialPort 则会生成头文件:org_winplus_serial_utils_SerialPort.h,这个头文件的名字可以随意命名。我们将它命名为:SerialPort.h拷贝到新建的目录jni中,新建SerialPort.c 文件,这两个文件的代码就不贴出来了。直接到上传的代码中看吧。

(四)串口的应用,可实现扫描头,指纹识别等外围USB转串口的特色应用

还蛮繁琐的,以上只是对开源项目android-serialport-api 进行精简想了解此项目请点击此处!就这样吧,晚了准备见周公去!

时间: 2025-01-28 09:32:05

Android开发之串口编程原理和实现方式的相关文章

Android开发之串口编程原理和实现方式_Android

提到串口编程,就不得不提到JNI,不得不提到JavaAPI中的文件描述符类:FileDescriptor.下面我分别对JNI.FileDescriptor以及串口的一些知识点和实现的源码进行分析说明.这里主要是参考了开源项目android-serialport-api. 串口编程需要了解的基本知识点:对于串口编程,我们只需对串口进行一系列的设置,然后打开串口,这些操作我们可以参考串口调试助手的源码进行学习.在Java中如果要实现串口的读写功能只需操作文件设备类:FileDescriptor即可,

【Android开发】网路编程及Internet应用-使用WebView显示网页

Android提供了内置的浏览器,该浏览器使用了开源的WebKit引擎.WebKit不仅能够搜索网址.查看电子邮件,而且能够播放视频节目.在Android中,要使用内置的浏览器,需要通过WebView组件来实现.通过WebView组件可以轻松实现显示网页功能. 如何在Android中使用WebView组件: WebView组件使用方法同其它组件一样,既可以使用XML布局文件配置,也可以在java文件中通过new关键字创建.推荐使用XML布局文件配置,配置方法: <WebView android:

Android开发中的几种网络请求方式详解_Android

Android应用经常会和服务器端交互,这就需要手机客户端发送网络请求,下面介绍四种常用网络请求方式,我这边是通过Android单元测试来完成这四种方法的,还不清楚Android的单元测试的同学们请看Android开发技巧总结中的Android单元测试的步骤一文. Java.NET包中的HttpURLConnection类 Get方式: // Get方式请求 public static void requestByGet() throws Exception { String path = "h

Android开发中网络编程与常见功能优化总结

一般的应用都是从服务器获取数据,然后通过极致的界面风格,将数据清晰,明朗的展现给用户. 那么就可以分为这两块: 1.界面UI   追求极致 2.功能 2.1获取数据:主要是与服务器通信,那么就要涉及到网络编程 : 2.1.1  URlConnection  2.1.2  HttpURLConnection(post get) 2.1.3  Socket 2.1.4  HttpClient(post get) 2.1.5  WebService(自己拼装请求xml 数据,采用开源jar包 ksoa

【Android开发】网络编程及Internet应用-通过HTTP访问网络

通有线互联网一样,移动互联网也可以使用HTTP访问网络.在Android中,针对HTTP进行网络通信的方法主要有两种,一种是使用HttpURLConnection实现:另一种是使用HttpClient实现.下面分别进行介绍 1.使用HttpURLConnection访问网络 HttpURLConnection位于java.net包中,用于发送HTTP请求和获取HTTP响应.由于该类是抽象类,不能直接实例化对象,则需要使用URL的openConnection()方法来获得.例如,要创建一个http

【Android开发】网络编程及Internet应用-获取天气预报

在Eclipse中创建Android项目,利用之前学过的WebView控件和中国天气网提供的天气数据接口,实现获取指定城市的天气预报. 布局文件: res/layout/main.xml: <?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:too

【Android开发】网络编程及Internet应用-使用HttpClient访问网络

    前面介绍了使用java.net包中的HttpURLConnection类来访问网络,在一般情况下,如果只需要到某个简单页面提交请求并获取服务器的响应,完全可以使用该技术来实现.不过,对于比较复杂的联网操作,使用HttpURLConnection类就不一定能满足要求,这时,可以使用Apache组织提供的HttpClient项目来实现.在Android中,已经成功的集成了HttpClient,所以可以直接在Android中使用HttpClient来访问网络.     HttpClient实际

【Android开发】网路编程及Internet应用-从指定网站上下载文件

利用前面学过的HttpUrlConnection和文件输入输出流来完成从Android端下载指定站点的文件. 写一个编辑框,用来输入要下载文件的URL路径,下面一个按钮,点击实现文件下载 res/layout/main.xml: <?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/and

《Android开发基础教程》——6.2节Gallery界面组件——画廊展示

6.2 Gallery界面组件--画廊展示 Android开发基础教程 Gallery组件的展示方式是将图片从左到右的方式排列,如同画廊放置作品一样.但是较为特殊的是Gallery组件支持手指左右拖曳滑动的效果,并且可以选择指定的图片,从界面布局来看是相当灵活而有用的组件. 6.2.1 Gallery语法示例与常用的属性 例如:我们要创建一个Gallery组件,名称为"Gallery01",Gallery组件和边界的距离是5dp,图片间的间隔是2dp,宽度填满整个屏幕,高度根据图片高度