资源描述
提到串口编程,就不得不提到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的相关项目写法)
这是关键的代码:
[cpp] view plaincopy
1. <span style="font-size:18px;"> int fd;
2. speed_t speed;
3. jobject mFileDescriptor;
4.
5. /* Check arguments */
6. {
7. speed = getBaudrate(baudrate);
8. if (speed == -1) {
9. /* TODO: throw an exception */
10. LOGE("Invalid baudrate");
11. return NULL;
12. }
13. }
14.
15. /* Opening device */
16. {
17. jboolean iscopy;
18. const char *path_utf = (*env)->GetStringUTFChars(env, path, &iscopy);
19. LOGD("Opening serial port %s with flags 0x%x", path_utf, O_RDWR | flags);
20. fd = open(path_utf, O_RDWR | flags);
21. LOGD("open() fd = %d", fd);
22. (*env)->ReleaseStringUTFChars(env, path, path_utf);
23. if (fd == -1)
24. {
25. /* Throw an exception */
26. LOGE("Cannot open port");
27. /* TODO: throw an exception */
28. return NULL;
29. }
30. }
31.
32. /* Configure device */
33. {
34. struct termios cfg;
35. LOGD("Configuring serial port");
36. if (tcgetattr(fd, &cfg))
37. {
38. LOGE("tcgetattr() failed");
39. close(fd);
40. /* TODO: throw an exception */
41. return NULL;
42. }
43.
44. cfmakeraw(&cfg);
45. cfsetispeed(&cfg, speed);
46. cfsetospeed(&cfg, speed);
47.
48. if (tcsetattr(fd, TCSANOW, &cfg))
49. {
50. LOGE("tcsetattr() failed");
51. close(fd);
52. /* TODO: throw an exception */
53. return NULL;
54. }
55. }
56. </span>
(二)FileDescritor:
文件描述符类的实例用作与基础机器有关的某种结构的不透明句柄,该结构表示开放文件、开放套接字或者字节的另一个源或接收者。文件描述符的主要实际用途是创建一个包含该结构的FileInputStream 或FileOutputStream。这是API的描述,不太好理解,其实可简单的理解为:FileDescritor就是对一个文件进行读写。
(三)实现串口通信细节
1) 建工程:SerialDemo包名:org.winplus.serial,并在工程目录下新建jni和libs两个文件夹和一个org.winplus.serial.utils,如下图:
2) 新建一个类:SerialPortFinder,添加如下代码:
[java] view plaincopy
1. <span style="font-size:18px;">package org.winplus.serial.utils;
2.
3. import java.io.File;
4. import java.io.FileReader;
5. import java.io.IOException;
6. import java.io.LineNumberReader;
7. import java.util.Iterator;
8. import java.util.Vector;
9.
10. import android.util.Log;
11.
12. public class SerialPortFinder {
13.
14. private static final String TAG = "SerialPort";
15.
16. private Vector<Driver> mDrivers = null;
17.
18. public class Driver {
19. public Driver(String name, String root) {
20. mDriverName = name;
21. mDeviceRoot = root;
22. }
23.
24. private String mDriverName;
25. private String mDeviceRoot;
26. Vector<File> mDevices = null;
27.
28. public Vector<File> getDevices() {
29. if (mDevices == null) {
30. mDevices = new Vector<File>();
31. File dev = new File("/dev");
32. File[] files = dev.listFiles();
33. int i;
34. for (i = 0; i < files.length; i++) {
35. if (files[i].getAbsolutePath().startsWith(mDeviceRoot)) {
36. Log.d(TAG, "Found new device: " + files[i]);
37. mDevices.add(files[i]);
38. }
39. }
40. }
41. return mDevices;
42. }
43.
44. public String getName() {
45. return mDriverName;
46. }
47. }
48.
49. Vector<Driver> getDrivers() throws IOException {
50. if (mDrivers == null) {
51. mDrivers = new Vector<Driver>();
52. LineNumberReader r = new LineNumberReader(new FileReader(
53. "/proc/tty/drivers"));
54. String l;
55. while ((l = r.readLine()) != null) {
56. // Issue 3:
57. // Since driver name may contain spaces, we do not extract
58. // driver name with split()
59. String drivername = l.substring(0, 0x15).trim();
60. String[] w = l.split(" +");
61. if ((w.length >= 5) && (w[w.length - 1].equals("serial"))) {
62. Log.d(TAG, "Found new driver " + drivername + " on "
63. + w[w.length - 4]);
64. mDrivers.add(new Driver(drivername, w[w.length - 4]));
65. }
66. }
67. r.close();
68. }
69. return mDrivers;
70. }
71.
72. public String[] getAllDevices() {
73. Vector<String> devices = new Vector<String>();
74. // Parse each driver
75. Iterator<Driver> itdriv;
76. try {
77. itdriv = getDrivers().iterator();
78. while (itdriv.hasNext()) {
79. Driver driver = itdriv.next();
80. Iterator<File> itdev = driver.getDevices().iterator();
81. while (itdev.hasNext()) {
82. String device = itdev.next().getName();
83. String value = String.format("%s (%s)", device,
84. driver.getName());
85. devices.add(value);
86. }
87. }
88. } catch (IOException e) {
89. e.printStackTrace();
90. }
91. return devices.toArray(new String[devices.size()]);
92. }
93.
94. public String[] getAllDevicesPath() {
95. Vector<String> devices = new Vector<String>();
96. // Parse each driver
97. Iterator<Driver> itdriv;
98. try {
99. itdriv = getDrivers().iterator();
100. while (itdriv.hasNext()) {
101. Driver driver = itdriv.next();
102. Iterator<File> itdev = driver.getDevices().iterator();
103. while (itdev.hasNext()) {
104. String device = itdev.next().getAbsolutePath();
105. devices.add(device);
106. }
107. }
108. } catch (IOException e) {
109. e.printStackTrace();
110. }
111. return devices.toArray(new String[devices.size()]);
112. }
113. }
114. </span>
上面这个类在“android-serialport-api串口工具测试随笔”中有详细的说明,我就不多说了。
3)新建SerialPort类,这个类主要用来加载SO文件,通过JNI的方式打开关闭串口
[java] view plaincopy
1. <span style="font-size:18px;">package org.winplus.serial.utils;
2.
3. import java.io.File;
4. import java.io.FileDescriptor;
5. import java.io.FileInputStream;
6. import java.io.FileOutputStream;
7. import java.io.IOException;
8. import java.io.InputStream;
9. import java.io.OutputStream;
10.
11. import android.util.Log;
12.
13. public class SerialPort {
14. private static final String TAG = "SerialPort";
15.
16. /*
17. * Do not remove or rename the field mFd: it is used by native method
18. * close();
19. */
20. private FileDescriptor mFd;
21. private FileInputStream mFileInputStream;
22. private FileOutputStream mFileOutputStream;
23.
24. public SerialPort(File device, int baudrate, int flags)
25. throws SecurityException, IOException {
26.
27. /* Check access permission */
28. if (!device.canRead() || !device.canWrite()) {
29. try {
30. /* Missing read/write permission, trying to chmod the file */
31. Process su;
32. su = Runtime.getRuntime().exec("/system/bin/su");
33. String cmd = "chmod 666 " + device.getAbsolutePath() + "\n"
34. + "exit\n";
35. su.getOutputStream().write(cmd.getBytes());
36. if ((su.waitFor() != 0) || !device.canRead()
37. || !device.canWrite()) {
38. throw new SecurityException();
39. }
40. } catch (Exception e) {
41. e.printStackTrace();
42. throw new SecurityException();
43. }
44. }
45.
46. mFd = open(device.getAbsolutePath(), baudrate, flags);
47. if (mFd == null) {
48. Log.e(TAG, "native open returns null");
49. throw new IOException();
50. }
51. mFileInputStream = new FileInputStream(mFd);
52. mFileOutputStream = new FileOutputStream(mFd);
53. }
54.
55. // Getters and setters
56. public InputStream getInputStream() {
57. return mFileInputStream;
58. }
59.
60. public OutputStream getOutputStream() {
61. return mFileOutputStream;
62. }
63.
64. // JNI
65. private native static FileDescriptor open(String path, int baudrate,
66. int flags);
67.
68. public native void close();
69.
70. static {
71. System.loadLibrary("serial_port");
72. }
73. }
74. </span>
4) 新建一个Application 继承android.app.Application,用来对串口进行初始化和关闭串口
[java] view plaincopy
1. <span style="font-size:18px;">package org.winplus.serial;
2.
3. import java.io.File;
4. import java.io.IOException;
5. import java.security.InvalidParameterException;
6.
7. import org.winplus.serial.utils.SerialPort;
8. import org.winplus.serial.utils.SerialPortFinder;
9.
10. import android.content.SharedPreferences;
11.
12. public class Application extends android.app.Application {
13. public SerialPortFinder mSerialPortFinder = new SerialPortFinder();
14. private SerialPort mSerialPort = null;
15.
16. public SerialPort getSerialPort() throws SecurityException, IOException, InvalidParameterException {
17. if (mSerialPort == null) {
18. /* Read serial port parameters */
19. SharedPreferences sp = getSharedPreferences("android_serialport_api.sample_preferences", MODE_PRIVATE);
20. String path = sp.getString("DEVICE", "");
21. int baudrate = Integer.decode(sp.getString("BAUDRATE", "-1"));
22.
23. /* Check parameters */
24. if ( (path.length() == 0) || (baudrate == -1)) {
25. throw new InvalidParameterException();
26. }
27.
28. /* Open the serial port */
29. mSerialPort = new SerialPort(new File(path), baudrate, 0);
30. }
31. return mSerialPort;
32. }
33.
34. public void closeSerialPort() {
35. if (mSerialPort != null) {
36. mSerialPort.close();
37. mSerialPort = null;
38. }
39. }
40. }
41. </span>
5) 新建一个继承抽象的Activity类,主要用于读取串口的信息
[java] view plaincopy
1. <span style="font-size:18px;">package org.winplus.serial;
2.
3. import java.io.IOException;
4. import java.io.InputStream;
5. import java.io.OutputStream;
6. import java.security.InvalidParameterException;
7.
8. import org.winplus.serial.utils.SerialPort;
9.
10. import android.app.Activity;
11. import android.app.AlertDialog;
12. import android.content.DialogInterface;
13. import android.content.DialogInterface.OnClickListener;
14. import android.os.Bundle;
15.
16. public abstract class SerialPortActivity extends Activity {
17. protected Application mApplication;
18. protected SerialPort mSerialPort;
19. protected OutputStream mOutputStream;
20. private InputStream mInputStream;
21. private ReadThread mReadThread;
22.
23. private class ReadThread extends Thread {
24.
25. @Override
26. public void run() {
27. super.run();
28. while (!isInterrupted()) {
29. int size;
30. try {
31. byte[] buffer = new byte[64];
32. if (mInputStream == null)
33. return;
34.
35. /**
36. * 这里的read要尤其注意,它会一直等待数据,等到天荒地老,海枯石烂。如果要判断是否接受完成,只有设置结束标识,或作其他特殊的处理。
37. */
38. size = mInputStream.read(buffer);
39. if (size > 0) {
40. onDataReceived(buffer, size);
41. }
42. } catch (IOException e) {
43. e.printStackTrace();
44. return;
45. }
46. }
47. }
48. }
49.
50. private void DisplayError(int resourceId) {
51. AlertDialog.Builder b = new AlertDialog.Builder(this);
52. b.setTitle("Error");
53. b.setMessage(resourceId);
54. b.setPositiveButton("OK", new OnClickListener() {
55. public void onClick(DialogInterface dialog, int which) {
56. SerialPortActivity.this.finish();
57. }
58. });
59. b.show();
60. }
61.
62. @Override
63. protected void onCreate(Bundle save
展开阅读全文