正常Android设备的串口一般是用作debug调试使用,随着Android设备使用越来越广,比如智能pos、智能扫码机都会用到Android主板和单片机进行通信,如果Android主板和单片机通信数据量大可以使用USB,若是通信数据量小可以使用串口进行通信,因为串口通信简单并且稳定性高,最近做的一个项目就是Android使用串口和加密芯片进行通信,Android主板使用的是rk3288和rk3368. 在rk3288源码中集成了一个串口demo,目录在

rk3288/frameworks/base/tests/SerialChat

网上存在一个demo是通过 java->jni->Serial驱动,虽然也可以正常读写,这样做缺点是
1、如果突然串口不能正常通信可能会导致app崩溃,
2、这个串口只能被当前的app使用,其他app 不能使用。
3、违背正常Android设计初衷,Android标准流程是
java->service->jni->hal->serial驱动。
所以我在项目中使用了Android源码中的demo

public class SerialChat extends Activity implements Runnable, TextView.OnEditorActionListener {private static final String TAG = "SerialChat";private TextView mLog;private EditText mEditText;private ByteBuffer mInputBuffer;private ByteBuffer mOutputBuffer;private SerialManager mSerialManager;private SerialPort mSerialPort;private boolean mPermissionRequestPending;private static final int MESSAGE_LOG = 1;@Overridepublic void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);mSerialManager = (SerialManager)getSystemService(Context.SERIAL_SERVICE);setContentView(R.layout.serial_chat);mLog = (TextView)findViewById(R.id.log);mEditText = (EditText)findViewById(R.id.message);mEditText.setOnEditorActionListener(this);if (false) {mInputBuffer = ByteBuffer.allocateDirect(1024);mOutputBuffer = ByteBuffer.allocateDirect(1024);} else {mInputBuffer = ByteBuffer.allocate(1024);mOutputBuffer = ByteBuffer.allocate(1024);}}@Overridepublic void onResume() {super.onResume();String[] ports = mSerialManager.getSerialPorts();for(int 1 = 0;i < ports.length;i++ ){Log.e("PPTV", "ports is ====" +  ports[i] );}if (ports != null && ports.length > 0) {try {mSerialPort = mSerialManager.openSerialPort(ports[1], 115200);if (mSerialPort != null) {new Thread(this).start();}} catch (IOException e) {}}}@Overridepublic void onPause() {super.onPause();}@Overridepublic void onDestroy() {if (mSerialPort != null) {try {mSerialPort.close();} catch (IOException e) {}mSerialPort = null;}super.onDestroy();}public boolean onEditorAction(TextView v, int actionId, KeyEvent event) {if (/* actionId == EditorInfo.IME_ACTION_DONE && */ mSerialPort != null) {try {String text = v.getText().toString();Log.d(TAG, "write: " + text);byte[] bytes = text.getBytes();mOutputBuffer.clear();mOutputBuffer.put(bytes);mSerialPort.write(mOutputBuffer, bytes.length);} catch (IOException e) {Log.e(TAG, "write failed", e);}v.setText("");return true;}Log.d(TAG, "onEditorAction " + actionId + " event: " + event);return false;}public void run() {Log.d(TAG, "run");int ret = 0;byte[] buffer = new byte[1024];while (ret >= 0) {try {Log.d(TAG, "calling read");mInputBuffer.clear();ret = mSerialPort.read(mInputBuffer);Log.d(TAG, "read returned " + ret);mInputBuffer.get(buffer, 0, ret);} catch (IOException e) {Log.e(TAG, "read failed", e);break;}if (ret > 0) {Message m = Message.obtain(mHandler, MESSAGE_LOG);String text = new String(buffer, 0, ret);Log.d(TAG, "chat: " + text);m.obj = text;mHandler.sendMessage(m);}}Log.d(TAG, "thread out");}Handler mHandler = new Handler() {@Overridepublic void handleMessage(Message msg) {switch (msg.what) {case MESSAGE_LOG:mLog.setText(mLog.getText() + (String)msg.obj);break;}}};
}

首先是获取串口服务

 mSerialManager = (SerialManager)getSystemService(Context.SERIAL_SERVICE);

如果要想通过getSystemService获取serial服务,首先要在
core/java/android/content/Context.java:
添加

public static final String SERIAL_SERVICE = "serial";

在ContextImpl.java 中注册服务

    registerService(SERIAL_SERVICE, new ServiceFetcher() {public Object createService(ContextImpl ctx) {IBinder b = ServiceManager.getService(SERIAL_SERVICE);return new SerialManager(ctx, ISerialManager.Stub.asInterface(b));}});

每次开机会自动启动。可以通过命令查看当前服务是否启动

adb shell service list


可以看到服务已经启动。
目前启动的服务Java层面的服务,只是对我们C语言串口服务做了一层封装,真正工作的是我们native serial(SerialService).
所以registerService 函数中会获取SerialService的代理端,

 IBinder b = ServiceManager.getService(SERIAL_SERVICE);return new SerialManager(ctx, ISerialManager.Stub.asInterface(b));

SerialService 注册在SystemServer.java 中

try {Slog.i(TAG, "Serial Service");// Serial port supportserial = new SerialService(context);ServiceManager.addService(Context.SERIAL_SERVICE, serial);} catch (Throwable e) {Slog.e(TAG, "Failure starting SerialService", e);}}

SerialService.java 源码

public class SerialService extends ISerialManager.Stub {private final Context mContext;private final String[] mSerialPorts;public SerialService(Context context) {mContext = context;mSerialPorts = context.getResources().getStringArray(com.android.internal.R.array.config_serialPorts);}public String[] getSerialPorts() {mContext.enforceCallingOrSelfPermission(android.Manifest.permission.SERIAL_PORT, null);ArrayList<String> ports = new ArrayList<String>();for (int i = 0; i < mSerialPorts.length; i++) {String path = mSerialPorts[i];if (new File(path).exists()) {ports.add(path);}}String[] result = new String[ports.size()];ports.toArray(result);return result;}public ParcelFileDescriptor openSerialPort(String path) {mContext.enforceCallingOrSelfPermission(android.Manifest.permission.SERIAL_PORT, null);for (int i = 0; i < mSerialPorts.length; i++) {if (mSerialPorts[i].equals(path)) {return native_open(path);}}throw new IllegalArgumentException("Invalid serial port " + path);}private native ParcelFileDescriptor native_open(String path);
}
 mContext = context;mSerialPorts = context.getResources().getStringArray(com.android.internal.R.array.config_serialPorts);

会获取目前存在可以使用串口列表。这个根据自己需要添加。
我的就使用一个串口3

  <string-array translatable="false" name="config_serialPorts"><item>"/dev/ttyS3"</item></string-array>

回到SerialChat.java

String[] ports = mSerialManager.getSerialPorts();

结果就是/dev/ttyS3

mSerialPort = mSerialManager.openSerialPort(ports[1], 115200);

打开串口3

public SerialPort openSerialPort(String name, int speed) throws IOException {try {ParcelFileDescriptor pfd = mService.openSerialPort(name);//打开串口if (pfd != null) {SerialPort port = new SerialPort(name);port.open(pfd, speed);//设置波特率及其他属性return port;} else {throw new IOException("Could not open serial port " + name);}} catch (RemoteException e) {Log.e(TAG, "exception in UsbManager.openDevice", e);}return null;}

结果会调用SerialService.openSerialPort(name)

 public ParcelFileDescriptor openSerialPort(String path) {mContext.enforceCallingOrSelfPermission(android.Manifest.permission.SERIAL_PORT, null);for (int i = 0; i < mSerialPorts.length; i++) {if (mSerialPorts[i].equals(path)) {return native_open(path);}}throw new IllegalArgumentException("Invalid serial port " + path);}

最终打开调用native_open 通过jni方式打开串口3。

static jobject android_server_SerialService_open(JNIEnv *env, jobject thiz, jstring path)
{const char *pathStr = env->GetStringUTFChars(path, NULL);int fd = open(pathStr, O_RDWR | O_NOCTTY);if (fd < 0) {ALOGE("could not open %s", pathStr);env->ReleaseStringUTFChars(path, pathStr);return NULL;}env->ReleaseStringUTFChars(path, pathStr);jobject fileDescriptor = jniCreateFileDescriptor(env, fd);if (fileDescriptor == NULL) {return NULL;}return env->NewObject(gParcelFileDescriptorOffsets.mClass,gParcelFileDescriptorOffsets.mConstructor, fileDescriptor);
}

阻塞的方式打开串口

设置串口波特率已经属性

android_hardware_SerialPort.cpp

static void
android_hardware_SerialPort_open(JNIEnv *env, jobject thiz, jobject fileDescriptor, jint speed)
{switch (speed) {case 50:speed = B50;break;case 75:speed = B75;break;case 110:speed = B110;break;case 134:speed = B134;break;case 150:speed = B150;break;case 200:speed = B200;break;case 300:speed = B300;break;case 600:speed = B600;break;case 1200:speed = B1200;break;case 1800:speed = B1800;break;case 2400:speed = B2400;break;case 4800:speed = B4800;break;case 9600:speed = B9600;break;case 19200:speed = B19200;break;case 38400:speed = B38400;break;case 57600:speed = B57600;break;case 115200:speed = B115200;break;case 230400:speed = B230400;break;case 460800:speed = B460800;break;case 500000:speed = B500000;break;case 576000:speed = B576000;break;case 921600:speed = B921600;break;case 1000000:speed = B1000000;break;case 1152000:speed = B1152000;break;case 1500000:speed = B1500000;break;case 2000000:speed = B2000000;break;case 2500000:speed = B2500000;break;case 3000000:speed = B3000000;break;case 3500000:speed = B3500000;break;case 4000000:speed = B4000000;break;default:jniThrowException(env, "java/lang/IllegalArgumentException","Unsupported serial port speed");return;}int fd = jniGetFDFromFileDescriptor(env, fileDescriptor);// duplicate the file descriptor, since ParcelFileDescriptor will eventually close its copyfd = dup(fd);if (fd < 0) {jniThrowException(env, "java/io/IOException", "Could not open serial port");return;}env->SetIntField(thiz, field_context, fd);struct termios tio;if (tcgetattr(fd, &tio))memset(&tio, 0, sizeof(tio));tio.c_cflag =  speed | CS8 | CLOCAL | CREAD;// Disable output processing, including messing with end-of-line characters.tio.c_oflag &= ~OPOST;tio.c_iflag = IGNPAR;// 忽略奇偶校验错误tio.c_lflag = 0; /* turn of CANON, ECHO*, etc *//* no timeout but request at least one character per read */tio.c_cc[VTIME] = 0;//如果读取不到就一直等待tio.c_cc[VMIN] = 1;//读取一个byte就返回tcsetattr(fd, TCSANOW, &tio);tcflush(fd, TCIOFLUSH);
}

到这里open 串口完成
串口写很简单。就不解析了。重点解析read ,由于我们是block 方式打开的串口,所以如果没有数据流返回,串口会一直阻塞

 public void run() {Log.d(TAG, "run");int ret = 0;byte[] buffer = new byte[1024];while (ret >= 0) {try {Log.d(TAG, "calling read");mInputBuffer.clear();ret = mSerialPort.read(mInputBuffer);Log.d(TAG, "read returned " + ret);mInputBuffer.get(buffer, 0, ret);} catch (IOException e) {Log.e(TAG, "read failed", e);break;}if (ret > 0) {Message m = Message.obtain(mHandler, MESSAGE_LOG);String text = new String(buffer, 0, ret);Log.d(TAG, "chat: " + text);m.obj = text;mHandler.sendMessage(m);}}Log.d(TAG, "thread out");}Handler mHandler = new Handler() {@Overridepublic void handleMessage(Message msg) {switch (msg.what) {case MESSAGE_LOG:mLog.setText(mLog.getText() + (String)msg.obj);break;}}};
}

开启一个thread方式read 串口数据,
无数据会一直阻塞在ret = mSerialPort.read(mInputBuffer);
另外注意 的是读写buffer 要是用

  private ByteBuffer mInputBuffer;private ByteBuffer mOutputBuffer;

收到数据可以保存到普通的 byte[]中。

public int read(ByteBuffer buffer) throws IOException {if (buffer.isDirect()) {return native_read_direct(buffer, buffer.remaining());} else if (buffer.hasArray()) {return native_read_array(buffer.array(), buffer.remaining());} else {throw new IllegalArgumentException("buffer is not direct and has no array");}}

调用底层的c语言的read函数

static jint
android_hardware_SerialPort_read_direct(JNIEnv *env, jobject thiz, jobject buffer, jint length)
{int fd = env->GetIntField(thiz, field_context);jbyte* buf = (jbyte *)env->GetDirectBufferAddress(buffer);if (!buf) {jniThrowException(env, "java/lang/IllegalArgumentException", "ByteBuffer not direct");return -1;}int ret = read(fd, buf, length);if (ret < 0)jniThrowException(env, "java/io/IOException", NULL);return ret;
}

Android串口Serial服务解析相关推荐

  1. Android 通过串口获取设备号 android串口测试工具 完整解析

    前言 android 工业平板RK3399-all 调试有关串口的设备 如何获取对应的串口设备及串口名字 先来一张图说明 获取串口 SerialPortFinder mSerialPortFinder ...

  2. android串口通信——身份证识别器

    android串口通信身份证识别器 一身份证识别器基础 调用身份证识别器的步骤 波特率 基本指令 身份证信息结构 文字结构说明 民族代码对照表 性别代码对照表 二身份证的读取 读取的方法调用 身份证的 ...

  3. android之json解析优化,Android开发之json解析

    目前正在尝试着写app,发现看懂代码和能写出来差距很大,最关键的是java基础比较的差,因为只会python,java基础只学习了一个礼拜就过了.感觉java写出来的代码不如python简单明了. 上 ...

  4. Android网络之数据解析----SAX方式解析XML数据

    ​[声明] 欢迎转载,但请保留文章原始出处→_→ 生命壹号:http://www.cnblogs.com/smyhvae/ 文章来源:http://www.cnblogs.com/smyhvae/p/ ...

  5. Android init.rc文件解析过程详解(三)

    Android init.rc文件解析过程详解(三) 三.相关结构体 1.listnode listnode结构体用于建立双向链表,这种结构广泛用于kernel代码中, android源代码中定义了l ...

  6. Android 插件化原理解析——Activity生命周期管理

    之前的 Android插件化原理解析 系列文章揭开了Hook机制的神秘面纱,现在我们手握倚天屠龙,那么如何通过这种技术完成插件化方案呢?具体来说,插件中的Activity,Service等组件如何在A ...

  7. Android 插件化原理解析——Hook机制之AMSPMS

    在前面的文章中我们介绍了DroidPlugin的Hook机制,也就是代理方式和Binder Hook:插件框架通过AOP实现了插件使用和开发的透明性.在讲述DroidPlugin如何实现四大组件的插件 ...

  8. adb shell 调试 Android 串口

    Android手机上很多外设是串口连接到AP的,如modem,gps.为了调试这些串口,通常需要将它们飞线接出来,用pc的串口连接调试.这样比较麻烦. 在adb  shell里是可以直接调试串口的,就 ...

  9. Android串口通信实例分析【附源码】

    Android 串口通信实例分析,用的时开源的android-serialport-api 这个是用android ndk实现的串口通信,我把他做了一个简化,适合于一般的程序的串口通信移植,欢迎拍砖- ...

最新文章

  1. shell脚本中常见的一些特殊符号和作用详解
  2. 气候变迁给社会带来什么变化?
  3. 通过一个具体的例子学习Threadlocal Test
  4. python数据可视化代码_python数据可视化
  5. 【动态规划】 多米诺骨牌 (ssl 1632/luogu 1282)
  6. mysql查看索引创建进度_SQL Server查看索引重建、重组索引进度
  7. socket 端口和地址复用
  8. 32销售是合理的引导用户购买
  9. linux内核根据skb获取目的mac地址
  10. outlook邮件中样式错乱问题
  11. IE浏览器老是假死怎么办 IE假死的解决办法
  12. 像素三国志在线html5小游戏,激萌三国志H5
  13. 阿里云有奖调查结果公布,赠送10个阿里巴巴logo胸针...
  14. 缩放图片至固定大小,尺寸不足以0填充
  15. 十二星座物语,女生最喜欢的星座性格【10】
  16. 数据驱动运营,为门店开拓第二增长曲线。
  17. 先学微机原理还是计算机组成原理,计算机组成原理学习指导
  18. 高效能人士的执行四原则(四)——原则3:坚持激励性记分原则
  19. java开发环境搭建——mysql、navicat、powerDesigner下载安装
  20. 智慧灯杆的单灯控制器安装使用说明

热门文章

  1. Huffman编码MATLAB实现
  2. php获取26个字母,php 根据26个字母分类列出用户名 代码
  3. 职场马斯洛幸福模型的5大层次
  4. OpenCV中如何提取不规则ROI区域, 手眼标定hand_eye_calibration
  5. 卫生部:先看病后付费制度全面推行无时间表
  6. 敏捷到底是个什么鬼?
  7. 模式识别学习笔记(一)模式识别初认识
  8. 数字后端面试问答No.19-21(每日三问)
  9. Python爬虫-爬取新闻网站,将数据对比去重插入数据库
  10. 乐高加快中国授权专卖店开店步伐,相继落户烟台、昆明等地 | 知消图集