SoFunction
Updated on 2025-03-10

Android development serial programming principle and implementation method

When it comes to serial programming, we have to mention JNI, and we have to mention the file descriptor class in Java API: FileDescriptor. Below I will analyze and explain some knowledge points and implementation source codes of JNI, FileDescriptor and serial port respectively. Here we mainly refer to the open source project android-serialport-api.

Basic knowledge points that need to be understood in serial port programming: For serial port programming, we only need to make a series of settings on the serial port and then open the serial port. We can refer to the source code of the serial port debugging assistant for learning these operations. In Java, if you want to implement the serial port read and write function, you only need to operate the file device class: FileDescriptor, and other things are done by the driver without having to worry too much! Of course, if you want to understand, you have to look at the driver code. The driver is not intended to be explained here, but only the implementation method of the application layer is briefly explained.

(I) JNI
There are many articles about JNI on the Internet, so I won’t explain them more. Friends who want to know more can check out the technical articles of Wandering in the Cloud. They are well written and have a comprehensive analysis. So in this article, I emphasize 3 points:
1. How to package compiled SO files into APK? (The method is very simple. Just create a new folder libs/armeabi in the project directory and copy the SO file to this directory)
2. What should be paid attention to when naming? (In the compiled SO file, rename the file to:. It is the file generated after compilation)
3. Write MakeFile file (needless to say, you can directly refer to the related project writing method of using JNI in the package/apps directory)
Here is the key code:
Copy the codeThe code is as follows:

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

(II) FileDescriber
An instance of a file descriptor class is used as an opaque handle to a structure related to the underlying machine, which represents another source or receiver of an open file, an open socket, or byte. The main practical purpose of a file descriptor is to create a FileInputStream or FileOutputStream containing the structure. This is the description of the API, which is not easy to understand. In fact, it can be simply understood as: FileDescriber is to read and write a file.
(III) Details of implementing serial communication
1) Construction project: SerialDemo package name: and create two folders, jni and libs and one in the project directory, as shown in the figure below:
2) Create a new class: SerialPortFinder, add the following code:
Copy the codeThe code is as follows:

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

import ;
import ;
import ;
import ;
import ;
import ;

import ;

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 = ();
int i;
for (i = 0; i < ; i++) {
if (files[i].getAbsolutePath().startsWith(mDeviceRoot)) {
(TAG, "Found new device: " + files[i]);
(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 = ()) != null) {
// Issue 3:
// Since driver name may contain spaces, we do not extract
// driver name with split()
String drivername = (0, 0x15).trim();
String[] w = (" +");
if (( >= 5) && (w[ - 1].equals("serial"))) {
(TAG, "Found new driver " + drivername + " on "
+ w[ - 4]);
(new Driver(drivername, w[ - 4]));
}
}
();
}
return mDrivers;
}

public String[] getAllDevices() {
Vector<String> devices = new Vector<String>();
// Parse each driver
Iterator<Driver> itdriv;
try {
itdriv = getDrivers().iterator();
while (()) {
Driver driver = ();
Iterator<File> itdev = ().iterator();
while (()) {
String device = ().getName();
String value = ("%s (%s)", device,
());
(value);
}
}
} catch (IOException e) {
();
}
return (new String[()]);
}

public String[] getAllDevicesPath() {
Vector<String> devices = new Vector<String>();
// Parse each driver
Iterator<Driver> itdriv;
try {
itdriv = getDrivers().iterator();
while (()) {
Driver driver = ();
Iterator<File> itdev = ().iterator();
while (()) {
String device = ().getAbsolutePath();
(device);
}
}
} catch (IOException e) {
();
}
return (new String[()]);
}
}
</span>

The above class has detailed explanations in the "Android-serialport-api serial port tool testing essay", so I won't say much.
3) Create a new SerialPort class. This class is mainly used to load SO files and open and close the serial port through JNI.
Copy the codeThe code is as follows:

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

import ;
import ;
import ;
import ;
import ;
import ;
import ;

import ;

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 (!() || !()) {
try {
/* Missing read/write permission, trying to chmod the file */
Process su;
su = ().exec("/system/bin/su");
String cmd = "chmod 666 " + () + "\n"
+ "exit\n";
().write(());
if ((() != 0) || !()
|| !()) {
throw new SecurityException();
}
} catch (Exception e) {
();
throw new SecurityException();
}
}

mFd = open((), baudrate, flags);
if (mFd == null) {
(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 {
("serial_port");
}
}
</span>

4) Create a new MyApplication inheritance to initialize and close the serial port
Copy the codeThe code is as follows:

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

import ;
import ;
import ;

import ;
import ;

import ;

public class MyApplication extends {
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 = ("DEVICE", "");
int baudrate = (("BAUDRATE", "-1"));

/* Check parameters */
if ( (() == 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 = null;
}
}
}
</span>

5) Create a new inherited abstract Activity class, mainly used to read serial port information
Copy the codeThe code is as follows:

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

import ;
import ;
import ;
import ;

import ;

import ;
import ;
import ;
import ;
import ;

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() {
();
while (!isInterrupted()) {
int size;
try {
byte[] buffer = new byte[64];
if (mInputStream == null)
return;

/**
* The read here should be paid special attention to it. It will wait for data until the end of time and the end of the world will be ruined. If you want to determine whether the completion is accepted, you can only set the end flag or do other special treatments.
*/
size = (buffer);
if (size > 0) {
onDataReceived(buffer, size);
}
} catch (IOException e) {
();
return;
}
}
}
}

private void DisplayError(int resourceId) {
b = new (this);
("Error");
(resourceId);
("OK", new OnClickListener() {
public void onClick(DialogInterface dialog, int which) {
();
}
});
();
}

@Override
protected void onCreate(Bundle savedInstanceState) {
(savedInstanceState);
mApplication = (MyApplication) getApplication();
try {
mSerialPort = ();
mOutputStream = ();
mInputStream = ();

/* Create a receiving thread */
mReadThread = new ReadThread();
();
} catch (SecurityException e) {
DisplayError(.error_security);
} catch (IOException e) {
DisplayError(.error_unknown);
} catch (InvalidParameterException e) {
DisplayError(.error_configuration);
}
}

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

@Override
protected void onDestroy() {
if (mReadThread != null)
();
();
mSerialPort = null;
();
}
}
</span>

6) Writing and documentation
Add in the file:
Copy the codeThe code is as follows:

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

Add in file
Copy the codeThe code is as follows:

<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) Start writing the interface: add two edit boxes to the layout file, one is used to send commands and the other is used to receive commands:
Copy the codeThe code is as follows:

<span style="font-size:18px;"><?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:andro
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:orientation="vertical" >

<EditText
android:
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:
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:hint="Emission"
android:lines="1" >
</EditText>

</LinearLayout>
</span>

8) Implementation of SerialDemoActivity class:
Copy the codeThe code is as follows:

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

import ;

import ;
import ;
import ;
import ;
import ;

public class SerialDemoActivity extends SerialPortActivity{
EditText mReception;

@Override
protected void onCreate(Bundle savedInstanceState) {
(savedInstanceState);
setContentView();

// setTitle("Loopback test");
mReception = (EditText) findViewById();

EditText Emission = (EditText) findViewById();
(new OnEditorActionListener() {
public boolean onEditorAction(TextView v, int actionId, KeyEvent event) {
int i;
CharSequence t = ();
char[] text = new char[()];
for (i=0; i<(); i++) {
text[i] = (i);
}
try {
(new String(text).getBytes());
('\n');
} catch (IOException e) {
();
}
return false;
}
});
}

@Override
protected void onDataReceived(final byte[] buffer, final int size) {
runOnUiThread(new Runnable() {
public void run() {
if (mReception != null) {
(new String(buffer, 0, size));
}
}
});
}
}
</span>

Having written this, the code is basically finished. The following is to implement the functions of the JNI layer. To implement JNI, you must first generate the header file. The generation method of the header file is also very simple. When we compile the project and enter javah in the terminal, the header file will be generated: org_winplus_serial_utils_SerialPort.h. The name of this header file can be named at will. We name it: Copy it to the newly created directory jni, create a new file, and the code for these two files will not be posted. Just go to the uploaded code.
(IV) Application of serial port, which can realize the special applications of peripheral USB to serial port such as scanning head and fingerprint recognition, etc.
It's quite cumbersome. The above is just a streamlining of the open source project android-serialport-api. If you want to know about this project, please click here! Let’s do it, I’m going to see Duke Zhou when I’m late!