android framework层本来提供了SerialPort和SerialManager两个类来操作串口,本文提供的是一种利用jni操作串口的方法,app层也可以使用。言归正传,下面来详细说下过程:
一:在frameworks/base/serivces/core/java/com/android/server 新建文件夹tnport,里面分别有
1:ComBean
package com.android.server.tnport; import java.text.SimpleDateFormat; /** * Author: ZHY * Email: [email protected] * Time: 2017/10/12. 13:49 * Package: com.android.server.tnport * FileName: ComBean */ public class ComBean { public byte[] bRec = null; public String sRecTime=""; public String sComPort=""; public ComBean(String sPort,byte[] buffer,int size) { sComPort = sPort; bRec = new byte[size]; for (int i = 0; i < size; i++) { bRec[i] = buffer[i]; } SimpleDateFormat sDateFormat = new SimpleDateFormat("hh:mm:ss"); sRecTime = sDateFormat.format(new java.util.Date()); } }
2:MyFunc
package com.android.server.tnport; public class MyFunc { //------------------------------------------------------- static public int isOdd(int num) { return num & 0x1; } //------------------------------------------------------- static public int HexToInt(String inHex)// { return Integer.parseInt(inHex, 16); } //------------------------------------------------------- static public byte HexToByte(String inHex)// { return (byte)Integer.parseInt(inHex,16); } //------------------------------------------------------- static public String Byte2Hex(Byte inByte) { return String.format("%02x", inByte).toUpperCase(); } //------------------------------------------------------- static public String ByteArrToHex(byte[] inBytArr) { StringBuilder strBuilder=new StringBuilder(); int j=inBytArr.length; for (int i = 0; i < j; i++) { strBuilder.append(Byte2Hex(inBytArr[i])); strBuilder.append(" "); } return strBuilder.toString(); } //------------------------------------------------------- static public String ByteArrToHex(byte[] inBytArr,int offset,int byteCount) { StringBuilder strBuilder=new StringBuilder(); int j=byteCount; for (int i = offset; i < j; i++) { strBuilder.append(Byte2Hex(inBytArr[i])); } return strBuilder.toString(); } static public byte[] HexToByteArr(String inHex) { int hexlen = inHex.length(); byte[] result; if (isOdd(hexlen)==1) { hexlen++; result = new byte[(hexlen/2)]; inHex="0"+inHex; }else { result = new byte[(hexlen/2)]; } int j=0; for (int i = 0; i < hexlen; i+=2) { result[j]=HexToByte(inHex.substring(i,i+2)); j++; } return result; } }
3:SerialHelper
package com.android.server.tnport; import android.util.Slog; import java.io.File; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.security.InvalidParameterException; public abstract class SerialHelper { private static final String TAG = "SerialHelper"; private SerialPort mSerialPort; private OutputStream mOutputStream; private InputStream mInputStream; private ReadThread mReadThread; private SendThread mSendThread; private String sPort = "/dev/ttyS4"; private int iBaudRate = 9600; private boolean _isOpen = false; private byte[] _bLoopData = new byte[]{0x30}; private int iDelay = 500; //---------------------------------------------------- public SerialHelper(String sPort, int iBaudRate) { this.sPort = sPort; this.iBaudRate = iBaudRate; } public SerialHelper() { this("/dev/ttyS4", 9600); } public SerialHelper(String sPort) { this(sPort, 9600); } public SerialHelper(String sPort, String sBaudRate) { this(sPort, Integer.parseInt(sBaudRate)); } //---------------------------------------------------- public void open() throws SecurityException, IOException, InvalidParameterException { Slog.e(TAG, "open port"); mSerialPort = new SerialPort(new File(sPort), iBaudRate, 0); mOutputStream = mSerialPort.getOutputStream(); mInputStream = mSerialPort.getInputStream(); mReadThread = new ReadThread(); mReadThread.start(); mSendThread = new SendThread(); mSendThread.setSuspendFlag(); mSendThread.start(); _isOpen = true; } //---------------------------------------------------- public void close() { if (mReadThread != null) mReadThread.interrupt(); if (mSerialPort != null) { mSerialPort.close(); mSerialPort = null; } _isOpen = false; } //---------------------------------------------------- public void send(byte[] bOutArray) { try { mOutputStream.write(bOutArray); } catch (IOException e) { e.printStackTrace(); } } //---------------------------------------------------- public void sendHex(String sHex) { byte[] bOutArray = MyFunc.HexToByteArr(sHex); send(bOutArray); } //---------------------------------------------------- public void sendTxt(String sTxt) { byte[] bOutArray = sTxt.getBytes(); send(bOutArray); } //---------------------------------------------------- private class ReadThread extends Thread { @Override public void run() { super.run(); Slog.e(TAG, "read thrad start run"); while (!isInterrupted()) { try { try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } if (mInputStream == null) return; byte[] buffer = new byte[512]; int size = mInputStream.read(buffer); Slog.e(TAG, "size=" + size); if (size > 0) { ComBean ComRecData = new ComBean(sPort, buffer, size); onDataReceived(ComRecData); } } catch (Throwable e) { e.printStackTrace(); Slog.e(TAG, "ReadThread Throwable"); return; } } } } //---------------------------------------------------- private class SendThread extends Thread { public boolean suspendFlag = true; @Override public void run() { super.run(); Slog.e(TAG, "send thread run"); while (!isInterrupted()) { synchronized (this) { while (suspendFlag) { try { wait(); } catch (InterruptedException e) { e.printStackTrace(); } } } send(getbLoopData()); try { Thread.sleep(iDelay); } catch (InterruptedException e) { e.printStackTrace(); Slog.e(TAG, "SendThread InterruptedException"); } } } public void setSuspendFlag() { this.suspendFlag = true; } public synchronized void setResume() { this.suspendFlag = false; notify(); } } //---------------------------------------------------- public int getBaudRate() { return iBaudRate; } public boolean setBaudRate(int iBaud) { if (_isOpen) { return false; } else { iBaudRate = iBaud; return true; } } public boolean setBaudRate(String sBaud) { int iBaud = Integer.parseInt(sBaud); return setBaudRate(iBaud); } //---------------------------------------------------- public String getPort() { return sPort; } public boolean setPort(String sPort) { if (_isOpen) { return false; } else { this.sPort = sPort; return true; } } public boolean isOpen() { return _isOpen; } public byte[] getbLoopData() { return _bLoopData; } public void setbLoopData(byte[] bLoopData) { this._bLoopData = bLoopData; } public void setTxtLoopData(String sTxt) { this._bLoopData = sTxt.getBytes(); } public void setHexLoopData(String sHex) { this._bLoopData = MyFunc.HexToByteArr(sHex); } public int getiDelay() { return iDelay; } public void setiDelay(int iDelay) { this.iDelay = iDelay; } public void startSend() { if (mSendThread != null) { mSendThread.setResume(); } } public void stopSend() { if (mSendThread != null) { mSendThread.setSuspendFlag(); } } protected abstract void onDataReceived(ComBean ComRecData); }
4:SerialPort
package com.android.server.tnport; 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; import android.util.Log; public class SerialPort { private static final String TAG = "SerialPort"; private FileDescriptor mFd; private FileInputStream mFileInputStream; private FileOutputStream mFileOutputStream; public SerialPort(File device, int baudrate, int flags) throws SecurityException, IOException { Log.i(TAG, "----SerialPort--file="+device.getName()+"--baudrate="+baudrate+"--flags="+flags); if (!device.canRead() || !device.canWrite()) { try { /* Missing read/write permission, trying to chmod the file */ Process su = Runtime.getRuntime().exec("/system/bin/su"); String cmd = "chmod 666 " + 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(); } }else{ Log.i(TAG, "---!device.canRead() || !device.canWrite()"); } mFd = open(baudrate, flags, device.getAbsolutePath()); if (mFd == null) { Log.e(TAG, "----null---native static FileDescriptor open(String path, int baudrate, int flags)"); 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(int baudrate, int flags, String path); public native void close(); // static { // System.loadLibrary("serial_port"); // } }
二:在自己的UsbAndSerialPortService中调用
private void openPort(SerialHelper ComPort) { try { ComPort.open(); } catch (SecurityException e) { Slog.e(TAG, "open port failed: no permission"); } catch (IOException e) { Slog.e(TAG, "open port failed: unknow error"); } catch (InvalidParameterException e) { Slog.e(TAG, "open port failed: permeter error"); } }
private void closePort(SerialHelper ComPort) { if (ComPort != null) { ComPort.stopSend(); ComPort.close(); } }
private class SerialControl extends SerialHelper { public SerialControl() { } @Override protected void onDataReceived(final ComBean ComRecData) { Slog.e(TAG, "onDataReceived"); DispQueue.AddQueue(ComRecData); } }
private void sendProtData(SerialHelper ComPort, String sOut){ if (ComPort != null && ComPort.isOpen()){ ComPort.sendTxt(sOut); } }
private class DispQueueThread extends Thread { private QueueQueueList = new LinkedList (); @Override public void run() { super.run(); while (!isInterrupted()) { final ComBean ComData; while ((ComData = QueueList.poll()) != null) { DispRecData(ComData); try { Thread.sleep(1000); } catch (Exception e) { e.printStackTrace(); } break; } } } public synchronized void AddQueue(ComBean ComData) { QueueList.add(ComData); } }
private void DispRecData(ComBean ComRecData) { StringBuilder sMsg = new StringBuilder(); sMsg.append(ComRecData.sRecTime); sMsg.append("["); sMsg.append(ComRecData.sComPort); sMsg.append("]"); sMsg.append("[Txt] "); sMsg.append(new String(ComRecData.bRec)); sMsg.append("\r\n"); Slog.e(TAG, "receiver msg=" + sMsg); }
然后新建端口,并打开
SerialControl com = new SerialControl(); com.setBaudRate(baudrate); com.setPort("/dev/ttyS4"); openPort(com); DispQueue = new DispQueueThread(); DispQueue.start();
三:在framework/base/service/core/jni 目录创建com_android_server_tnport_SerialPort.cpp
/* * 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 #include #include #include #include "android/log.h" #include "JNIHelp.h" #include "android_runtime/AndroidRuntime.h" #include #include #include #include #include #include #include #include #include #include #include #include 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) namespace android { static int 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; } } static jobject android_server_tnport_SerialPort_open (JNIEnv *env, jclass thiz, jint baudrate, jint flags, jstring path) { int fd; int speed; jobject mFileDescriptor; { 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(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(path, path_utf); if (fd == -1) { LOGE("Cannot open port"); return NULL; } } { 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; } } { jclass cFileDescriptor = env->FindClass("java/io/FileDescriptor"); jmethodID iFileDescriptor = env->GetMethodID(cFileDescriptor, " " , "()V"); jfieldID descriptorID = env->GetFieldID(cFileDescriptor, "descriptor", "I"); mFileDescriptor = env->NewObject(cFileDescriptor, iFileDescriptor); env->SetIntField(mFileDescriptor, descriptorID, (jint)fd); } return mFileDescriptor; } static void android_server_tnport_SerialPort_close (JNIEnv *env, jobject thiz) { jclass SerialPortClass = env->GetObjectClass(thiz); jclass FileDescriptorClass = env->FindClass("java/io/FileDescriptor"); jfieldID mFdID = env->GetFieldID(SerialPortClass, "mFd", "Ljava/io/FileDescriptor;"); jfieldID descriptorID = env->GetFieldID(FileDescriptorClass, "descriptor", "I"); jobject mFd = env->GetObjectField(thiz, mFdID); jint descriptor = env->GetIntField(mFd, descriptorID); LOGD("close(fd = %d)", descriptor); close(descriptor); } static const JNINativeMethod method_table[] = { { "close", "()V", (void*)android_server_tnport_SerialPort_close}, { "open", "(IILjava/lang/String;)Ljava/io/FileDescriptor;", (void*)android_server_tnport_SerialPort_open}, }; int register_android_server_tnport_SerialPort(JNIEnv *env) { return jniRegisterNativeMethods(env, "com/android/server/tnport/SerialPort", method_table, NELEM(method_table)); } }
并在当前目录下的Android.mk和onload.cpp文件中注册这个jni:
Android.mk
$(LOCAL_REL_DIR)/com_android_server_tnport_SerialPort.cpp \
onload.cpp
int register_android_server_tnport_SerialPort(JNIEnv* env);
register_android_server_tnport_SerialPort(env);
二:还有一种方法就是利用framework层本来提供了SerialPort和SerialManager两个类来操作串口:
安卓原始代码中的串口SerialPort SerialManger SerialService ISerialService.aidl 等都是@hide隐藏的,外部应用无法调用,定制需要,所以将该Manager启用。下面记录下过程
1.启用SerialService
其掉这三个文件中的@hide标识:
frameworks/base/core/java/android/hardware/SerialManager.java
frameworks/base/core/java/android/hardware/SerialPort.java
frameworks/base/core/java/android/hardware/ISerialManager.aidl
SerialService文件位置:
frameworks/base/services/java/com/android/server/SerialService.java
该服务会在SystemSever.java中进行初始化,这里还需要将Context.java中的SERIAL_SERVICE的@hide去掉:
frameworks/base/core/java/android/content/Context.java
- /*
- * @hide
- */
public static final String SERIAL_SERVICE = "serial";
还有, SerialService是通过读取R.array.config_serialPorts这个String array来加载的/dev/设备节点:
public SerialService(Context context) {
mContext = context;
mSerialPorts = context.getResources().getStringArray(
com.android.internal.R.array.config_serialPorts);
}
所以还需要添加下,文件位置:
framework/base/core/res/res/values/config.xml
在config_serialPorts中添加相应设备节点
在使用时应用需要添加uses-permission权限 android.permission.SERIAL_PORT ;
2.安卓提供了个测试工具,在framework/base/tests/SerialChat, 可以进入该目录, 输入mm 进行编译,输出在 out/target/product/xxx/data/app/中,可以push到手机里验证。
3.权限授权问题, 我在测试这个SerialChat程序时发现android.permission.SERIAL_PORT并未被授权, 通过pm.checkPermission("android.permission.SERIAL_PORT", pinfo.packageName) 检查可以看到。
查看frameworks/base/core/res/AndroidManifest.xml中发现,该permission定义如下:
android:description="@string/permdesc_serialPort"
android:protectionLevel="signature|system" />
可以看到protectionLevel是signature|systeml;
signature表示当申请此权限的应用程序的签名与声明此权限的应用的签名相同时才会授权, 该应用是framwork-res.apk,使用的签名是platform,所以需要应用也要使用platform签名。
system表示是系统应用;
所以这里修改Android.mk,添加LOCAL_CERTIFICATE := platform 再重新编译
LOCAL_PATH:= $(call my-dir)
include $(CLEAR_VARS)
LOCAL_MODULE_TAGS := tests
LOCAL_CERTIFICATE := platform
LOCAL_SRC_FILES := $(call all-subdir-java-files)
LOCAL_PACKAGE_NAME := SerialChat
include $(BUILD_PACKAGE)