Android Serial Port Development with Google's Serial Port API
Preparation
- Android Studio
- Google android-serialport-api
Background
There are many approaches to serial port development on Android using C-based methods such as JNI or CMake, but they are often complex. Google's API provides a simpler method for basic read/write operations (default setting: N81, no parity, 8 data bits, 1 stop bit). This is the easiest way to get started.
Java does not directly call C functions for serial communication; instead, it uses.so library files compiled from C with CMake or JNI. By using Google's API, you don't need to deal with the C files—just use the provided.so libraries and the Java classes that call them. The Google demo includes these classes, so you only need to import the necessary components and build upon them.
Getting Started
- Unzip the Google API demo and set it aside for reference.
- Create a new Android Studio project with the default settings.
- Open
app/build.gradleand add the following insideandroidblock to specify the location of.so files. Here we use a customlibsfolder:
android {
compileSdkVersion 28
defaultConfig {
applicationId "com.example.hp.demo"
minSdkVersion 19
targetSdkVersion 28
versionCode 1
versionName "1.0"
testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
}
buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
}
}
sourceSets.main {
jniLibs.srcDirs = ['libs']
}
}
-
Navigate to
android-serialport-api\android-serialport-api\project\libsin the unzipped demo. Copy all the folders (containing.so files for different CPU architectures) into your project'slibsfolder. -
Go to
android-serialport-api\android-serialport-api\project\srcand copy theandroid_serialport_apifolder into your project'sjavadirectory (at the same level ascom). -
Inside
android_serialport_api, you'll find three items:sample: Demo code from Google API. Delete this folder after copying, as it will cause XML-related errors.SerialPort.java: The main class for serial port operations. This communicates with the.so library. You will build your code upon this.SerialPortFinder.java: Usually not needed (I didn't use it). It helps find and list all available serial port names and paths, useful for batch operations.
-
To reiterate: The standard approach is to write C and header files, compile them via JNI or CMake to produce.so files, then write a Java class that calls the.so library, and finally use that class. Our approach skips to the last step—using Google's pre-compiled.so and pre-written Java class, then buliding our own code on top.
-
Now you can start coding. But first, verify:
-
Check that step 3 is correctly set up.
-
In
SerialPort.java, ensure the following static block exists:static { System.loadLibrary("serial_port"); }This loads the.so library. The library name must match: the.so file is
libserial_port.so, so the name isserial_port. If step 3 is misconfigured, the library won't be found.
-
-
Now for the actual work:
a) Create a wrapper class around
SerialPort.java. Although you can useSerialPort.javadirectly, its methods are low-level. When dealing with multiple serial ports needing different operations, a wrapper is essential to avoid code duplication. Your wrapper should provide:- Set serial port name and baud rate from outside
- Pass serial port response data to external listeners
- Open serial port, get input/output streams
- Close serial port, close input/output streams
- Write bytes to serial port
- Write byte arrays
- Write strings
- Real-time listening for complete messages (See the demo for code details.)
b) Use the wrapper: instantiate it, register a data receiver, call the send methods where needed, and handle message validation in the receiver.
Pitfall Warnings
-
Incomplete serial port responses (missing a byte or two): This usually happens because you registered two listeners on the same serial port, and they are competing.
-
Fragmented responses: The sending device may transmit byte by byte or in chunks, all arriving quickly. To safely assemble the complete message, sleep 10–100ms after receiving some data, then read again (reading from the input stream buffer). Concatenate the data and pass it to the external listener.
-
Creating a new serial port is resource-intensive: The reading process runs in a loop. Insert a sleep of 10–100ms between reads to reduce CPU usage.
-
Reading thread continues after closing serial port: Wrap the reading logic in a
whileloop with a boolean flag. Set the flag to false when closing the port, so the thread terminates naturally. -
The
SerialPortconstructor requires anint flagsparameter besides file and baud rate: I haven't fully understood this. My senior always passed0without issues. -
The
inputStream.read()method: It returns anintrepresenting the number of bytes read. Store this value. When the buffer is empty, it returns -1. Always use anifcondition to check if the returned int > 0 before further processing. This method has multiple overloads:read(byte[] buffer): Reads bytes in to the buffer. Ensure no null pointer and that buffer capacity is sufficient.read(byte[] buffer, int offset, int length): Readslengthbytes and stores them starting atbuffer[offset]. Useful for appending data when reading multiple times.