这部分源码一直没变过,不过现在Android Studio已经支持Cmake方式了,所以不需要单独编译动态链接库了,可以直接修改Cmake文件和C文件
说明
开源库: https://github.com/cepr/android-serialport-api
- 参照AS的带C方式创建cpp文件夹,记得把配置也加上。
- 把开源库中的 SerialPort.c 和 SerialPort.h 拷贝下来,放入cpp文件夹中
- 创建CMake文件,我这里直接拷贝的AS的 CMakeLists.txt
经过以上3步,基本工作就做完了,这时候你的cpp库有3个文件: CMakeLists.txt 文件, c文件和头文件。
然后剩下的就是定义调用串口的文件了
修改代码
修改SerialPort头文件和C代码
以下是 SerialPort.c 文件,除了输出的 JNICALL 基本没啥要改的
- 格式是
Java_包名_类名_方法名
,用下划线隔开- 下面要创建的java类及方法一定要严格对应
- 头文件和C代码的 JNICALL 保持同名
/*
* Copyright 2009-2011 Cedric Priscal
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include
#include
#include
#include
#include
#include
#include
#include "SerialPort.h"
#include "android/log.h"
static const char *TAG="serial_port";
#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 -1;
}
}
/*
* Class: android_serialport_SerialPort
* Method: open
* Signature: (Ljava/lang/String;II)Ljava/io/FileDescriptor;
*/
JNIEXPORT jobject JNICALL Java_com_jiataoyuan_serialport_SerialPort_open
(JNIEnv *env, jclass thiz, jstring path, jint baudrate, jint flags)
{
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;
}
}
/* Create a corresponding file descriptor */
{
jclass cFileDescriptor = (*env)->FindClass(env, "java/io/FileDescriptor");
jmethodID iFileDescriptor = (*env)->GetMethodID(env, cFileDescriptor, "", "()V");
jfieldID descriptorID = (*env)->GetFieldID(env, cFileDescriptor, "descriptor", "I");
mFileDescriptor = (*env)->NewObject(env, cFileDescriptor, iFileDescriptor);
(*env)->SetIntField(env, mFileDescriptor, descriptorID, (jint)fd);
}
return mFileDescriptor;
}
/*
* Class: cedric_serial_SerialPort
* Method: close
* Signature: ()V
*/
JNIEXPORT void JNICALL Java_com_jiataoyuan_serialport_SerialPort_close
(JNIEnv *env, jobject thiz)
{
jclass SerialPortClass = (*env)->GetObjectClass(env, thiz);
jclass FileDescriptorClass = (*env)->FindClass(env, "java/io/FileDescriptor");
jfieldID mFdID = (*env)->GetFieldID(env, SerialPortClass, "mFd", "Ljava/io/FileDescriptor;");
jfieldID descriptorID = (*env)->GetFieldID(env, FileDescriptorClass, "descriptor", "I");
jobject mFd = (*env)->GetObjectField(env, thiz, mFdID);
jint descriptor = (*env)->GetIntField(env, mFd, descriptorID);
LOGD("close(fd = %d)", descriptor);
close(descriptor);
}
修改CMake文件
add_library
- 1参设置库名(你要在java中加载的库名称),
- 2参设置共享(基本都是SHARE),
- 3参设置源文件的相对路径(因为在同一目录下,直接写文件名即可)
find_library
这个东西我也没搞明白,看说明是用来搜索库路径的,但是因为有默认值,所以只需要指定NDK就行了,而且删了也不影响什么
target_link_libraries
- 1参指定目标库,就是上面设置的库
- 2参用来连接到日志库,已经包含在nkd中了,如果你的 find_library 默认的话,这里也默认就行了,如果 find_library 删掉的话,这里也删掉
# For more information about using CMake with Android Studio, read the
# documentation: https://d.android.com/studio/projects/add-native-code.html
# Sets the minimum version of CMake required to build the native library.
cmake_minimum_required(VERSION 3.4.1)
# Creates and names a library, sets it as either STATIC
# or SHARED, and provides the relative paths to its source code.
# You can define multiple libraries, and CMake builds them for you.
# Gradle automatically packages shared libraries with your APK.
add_library( # Sets the name of the library.
SerialPort
# Sets the library as a shared library.
SHARED
# Provides a relative path to your source file(s).
SerialPort.c)
# Searches for a specified prebuilt library and stores the path as a
# variable. Because CMake includes system libraries in the search path by
# default, you only need to specify the name of the public NDK library
# you want to add. CMake verifies that the library exists before
# completing its build.
find_library( # Sets the name of the path variable.
log-lib
# Specifies the name of the NDK library that
# you want CMake to locate.
log)
# Specifies libraries CMake should link to your target library. You
# can link multiple libraries, such as libraries you define in this
# build script, prebuilt third-party libraries, or system libraries.
target_link_libraries( # Specifies the target library.
SerialPort
# Links the target library to the log library
# included in the NDK.
${log-lib})
创建SerialPort
这个文件我改了一点,变化不大,你也可以直接用开源库中的,主要内容如下:
- 获取串口:改动部分为将可读写改为可读写可执行
- 获取读写流
- 设置jni方法 ,打开和关闭,其中打开方法是私有方法
/*
* Copyright 2009 Cedric Priscal
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.jiataoyuan.serialport;
import android.util.Log;
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;
public class SerialPort {
private static final String TAG = "SerialPort";
static {
System.loadLibrary("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; // 输出流
/**
* 获取串口
*
* @param device 设备
* @param baudrate 波特率
* @param flags 标志符
* @throws SecurityException
* @throws IOException
*/
public SerialPort(File device, int baudrate, int flags) throws SecurityException, IOException {
/* 检查访问权限 */
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 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();
}
}
// 如果打不开返回null
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();
}
封装SerialPortActivity
为啥封装成这样子,因为我的串口调试助手,所以最后改成这样子了,通俗易懂
/*
* Copyright 2009 Cedric Priscal
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.jiataoyuan.serialport;
import android.app.Activity;
import android.app.AlertDialog;
import android.content.DialogInterface;
import android.content.DialogInterface.OnClickListener;
import android.os.Bundle;
import com.jiataoyuan.dronetrack.R;
import com.jiataoyuan.dronetrack.utils.MyConstantUtils;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.security.InvalidParameterException;
public abstract class SerialPortActivity extends Activity {
protected SerialPort mSerialPort;
protected OutputStream mOutputStream;
private InputStream mInputStream;
private ReadThread mReadThread;
// 根据需要修改线程
private class ReadThread extends Thread {
@Override
public void run() {
super.run();
int size;
byte[] buffer = new byte[64];
StringBuilder sb = new StringBuilder();
while (!isInterrupted()) {
try {
if ((size = mInputStream.read(buffer)) != -1) {
sb.append(new String(buffer, 0, size));
onDataReceived(sb.toString());
}
} 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);
try {
if (null == mSerialPort) {
// 1参 串口 根据你的串口情况填入,如"/dev/ttyS4" , "/dev/ttyS1"
// 2参 波特率 根据约定填入,如19200 ,115200
// 3参 标志位 根据约定填入,没有填0
mSerialPort = new SerialPort(new File(MyConstantUtils.SerialPortPath), MyConstantUtils.baudrate, 0);
}
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(String buffer);
// 发送消息
public void sendMsg(byte[] msg) {
try {
mOutputStream = mSerialPort.getOutputStream();
if (msg.length > 0) {
mOutputStream.write(msg);
mOutputStream.flush();
}
} catch (IOException e) {
e.printStackTrace();
}
}
// 关闭端口
public void closeSerialPort() {
if (mSerialPort != null) {
mSerialPort.close();
mSerialPort = null;
}
}
@Override
protected void onDestroy() {
if (mReadThread != null)
mReadThread.interrupt();
closeSerialPort();
mSerialPort = null;
super.onDestroy();
}
}
调用
要调用的Activity需要继承 SerialPortActivity ,并实现 onDataReceived 方法
-
收
@Override protected void onDataReceived(String buffer) { mBuffer = buffer; Handler handler = new Handler(Looper.getMainLooper()); handler.postDelayed(runnable, 100); }
-
发
private void send(String msg) { sendMsg(msg.getBytes()); } private void send(byte[] msg) { sendMsg(msg); L.e(new String(msg)); }
- 开关串口都在
SerialPortActivity
的create和destroy方法中调用了,应用逻辑无需管理