利用串口,可以讓Android主板與各種傳感器和智能設備之間通信。Google自己有一個關於Android串口通信。
集成環境
一般串口通信開發,需要用到JNI和NDK方面的知識。首先需要搭建環境,導入相應的.so文件(.so文件是Unix的動態連接庫,本身是二進制文件,是由C/C++編譯而來的),沒有就自己新建libs,將.so文件復制進去。
之后需要再Gradle文件,將libs中的東西引入編譯,不然訪問不到。如下圖
android {
compileSdkVersion 30
buildToolsVersion "30.0.3"
defaultConfig {
......
/*選擇處理器相應的架構*/
ndk {
abiFilters "armeabi"
}
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
}
.....
/*加載 so庫*/
sourceSets {
main {
jniLibs.srcDirs = ['libs']
}
}
}
Google的demo中的 SerialPort.java (串口代碼)
/*
* 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 android_serialport_api;
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";
/*
* Do not remove or rename the field mFd: it is used by native method close();
*/
private FileDescriptor mFd;
private FileInputStream mFileInputStream;//輸入流
private FileOutputStream mFileOutputStream;//輸出流
// 設備號(串口地址),波特率,flags 默認為0
public SerialPort(File device, int baudrate, int flags) throws SecurityException, IOException {
/* Check access permission 檢查權限 */
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 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();
}
}
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();
static {
System.loadLibrary("serial_port");
}
}
// 如何使用
SerialPort serial = new SerialPort(new File("/dev/goc_serial"),115200,0);
**[SerialPortUtil](https://juejin.cn/post/6844903606982967303)**
public class SerialPortUtil {
private SerialPort serialPort = null;
private InputStream inputStream = null;
private OutputStream outputStream = null;
private ReceiveThread mReceiveThread = null;
private boolean isStart = false;
/**
* 打開串口,接收數據
* 通過串口,接收單片機發送來的數據
*/
public void openSerialPort() {
try {
serialPort = new SerialPort(new File("/dev/ttyS0"), 9600, 0);
//調用對象SerialPort方法,獲取串口中"讀和寫"的數據流
inputStream = serialPort.getInputStream();
outputStream = serialPort.getOutputStream();
isStart = true;
} catch (IOException e) {
e.printStackTrace();
}
getSerialPort();
}
/**
* 關閉串口
* 關閉串口中的輸入輸出流
*/
public void closeSerialPort() {
Log.i("test", "關閉串口");
try {
if (inputStream != null) {
inputStream.close();
}
if (outputStream != null) {
outputStream.close();
}
isStart = false;
} catch (IOException e) {
e.printStackTrace();
}
}
/**
* 發送數據
* 通過串口,發送數據到單片機
*
* @param data 要發送的數據
*/
public void sendSerialPort(String data) {
try {
byte[] sendData = DataUtils.HexToByteArr(data);//字符串轉為字節數組
outputStream.write(sendData);
outputStream.flush();
} catch (IOException e) {
e.printStackTrace();
}
}
private void getSerialPort() {
if (mReceiveThread == null) {
mReceiveThread = new ReceiveThread();
}
mReceiveThread.start();
}
/**
* 接收串口數據的線程
*/
private class ReceiveThread extends Thread {
@Override
public void run() {
super.run();
while (isStart) {
if (inputStream == null) {
return;
}
byte[] readData = new byte[1024];
try {
int size = inputStream.read(readData);
if (size > 0) {
String readString = DataUtils.ByteArrToHex(readData, 0, size);//字節數組轉為字符串
//EventBus.getDefault().post(readString);事件通知,拿到數據
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
}
/**
* 串口數據轉換工具類
*/
public class DataUtils {
//-------------------------------------------------------
// 判斷奇數或偶數,位運算,最后一位是1則為奇數,為0是偶數
public static int isOdd(int num) {
return num & 1;
}
//-------------------------------------------------------
//Hex字符串轉int
public static int HexToInt(String inHex) {
return Integer.parseInt(inHex, 16);
}
public static String IntToHex(int intHex){
return Integer.toHexString(intHex);
}
//-------------------------------------------------------
//Hex字符串轉byte
public static byte HexToByte(String inHex) {
return (byte) Integer.parseInt(inHex, 16);
}
//-------------------------------------------------------
//1字節轉2個Hex字符
public static String Byte2Hex(Byte inByte) {
return String.format("%02x", new Object[]{inByte}).toUpperCase();
}
//-------------------------------------------------------
//字節數組轉轉hex字符串
public static String ByteArrToHex(byte[] inBytArr) {
StringBuilder strBuilder = new StringBuilder();
for (byte valueOf : inBytArr) {
strBuilder.append(Byte2Hex(Byte.valueOf(valueOf)));
strBuilder.append(" ");
}
return strBuilder.toString();
}
//-------------------------------------------------------
//字節數組轉轉hex字符串,可選長度
public static 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(Byte.valueOf(inBytArr[i])));
}
return strBuilder.toString();
}
//-------------------------------------------------------
//轉hex字符串轉字節數組
public static byte[] HexToByteArr(String inHex) {
byte[] result;
int hexlen = inHex.length();
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;
}
/**
* 按照指定長度切割字符串
*
* @param inputString 需要切割的源字符串
* @param length 指定的長度
* @return
*/
public static List<String> getDivLines(String inputString, int length) {
List<String> divList = new ArrayList<>();
int remainder = (inputString.length()) % length;
// 一共要分割成幾段
int number = (int) Math.floor((inputString.length()) / length);
for (int index = 0; index < number; index++) {
String childStr = inputString.substring(index * length, (index + 1) * length);
divList.add(childStr);
}
if (remainder > 0) {
String cStr = inputString.substring(number * length, inputString.length());
divList.add(cStr);
}
return divList;
}
/**
* 計算長度,兩個字節長度
*
* @param val value
* @return 結果
*/
public static String twoByte(String val) {
if (val.length() > 4) {
val = val.substring(0, 4);
} else {
int l = 4 - val.length();
for (int i = 0; i < l; i++) {
val = "0" + val;
}
}
return val;
}
/**
* 校驗和
*
* @param cmd 指令
* @return 結果
*/
public static String sum(String cmd) {
List<String> cmdList = DataUtils.getDivLines(cmd, 2);
int sumInt = 0;
for (String c : cmdList) {
sumInt += DataUtils.HexToInt(c);
}
String sum = DataUtils.IntToHex(sumInt);
sum = DataUtils.twoByte(sum);
cmd += sum;
return cmd.toUpperCase();
}
}
關於USB
public class MainActivity_Toast extends AppCompatActivity {
public static String TAG = "MainActivity_Toast";
private static final int REQUEST_OVERLAY = 5004;
public static boolean CanShowFloat = false;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main__toast);
UsbManager usbManager = (UsbManager) getApplicationContext().getSystemService(Context.USB_SERVICE);
Map<String, UsbDevice> usbList = usbManager.getDeviceList();
IntentFilter usbDeviceStateFilter = new IntentFilter();
usbDeviceStateFilter.addAction(UsbManager.ACTION_USB_DEVICE_ATTACHED);
usbDeviceStateFilter.addAction(UsbManager.ACTION_USB_DEVICE_DETACHED);
registerReceiver(mUsbReceiver, usbDeviceStateFilter);
}
BroadcastReceiver mUsbReceiver = new BroadcastReceiver() {
public void onReceive(Context context, Intent intent) {
String action = intent.getAction();
if (UsbManager.ACTION_USB_DEVICE_DETACHED.equals(action)) {
Log.e(TAG,"拔出usb了");
Toast.makeText(MainActivity_Toast.this,"拔出usb了",Toast.LENGTH_SHORT).show();
}else if(UsbManager.ACTION_USB_DEVICE_ATTACHED.equals(action)){
Log.e(TAG,"插入usb了");
UsbDevice device = (UsbDevice)intent.getParcelableExtra(UsbManager.EXTRA_DEVICE);
if (device != null) {
toastDialog();
Log.e(TAG,"設備的ProductId值為:"+device.getProductId());
Log.e(TAG,"設備的VendorId值為:"+device.getVendorId());
}
}
}
};
/*<uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW" />*/
private void toastDialog() {
WindowManager wm = (WindowManager) getApplicationContext().getSystemService(Context.WINDOW_SERVICE);
RequestOverlayPermission(this);
View view = LayoutInflater.from(this).inflate(R.layout.loading_layout,null);
WindowManager.LayoutParams para = new WindowManager.LayoutParams();
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
para.type = WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY;
} else {
para.type = WindowManager.LayoutParams.TYPE_PHONE;
}
wm.addView(view,para);
}
@Override
protected void onActivityResult(int requestCode, int resultCode, @Nullable Intent data) {
super.onActivityResult(requestCode, resultCode, data);
onActivityResult(requestCode,resultCode,data,this);
}
/**
* 動態請求懸浮窗權限
*/
public void RequestOverlayPermission(AppCompatActivity Instatnce) {
if (Build.VERSION.SDK_INT >= 23) {
if (!Settings.canDrawOverlays(Instatnce)) {
//啟動Activity讓用戶授權
/*Intent intent = new Intent(Settings.ACTION_MANAGE_OVERLAY_PERMISSION, Uri.parse("package:" + getPackageName()));
startActivityForResult(intent, 1010);*/
String ACTION_MANAGE_OVERLAY_PERMISSION = "android.settings.action.MANAGE_OVERLAY_PERMISSION";
Intent intent = new Intent(ACTION_MANAGE_OVERLAY_PERMISSION, Uri.parse("package:" + Instatnce.getPackageName()));
Instatnce.startActivityForResult(intent, REQUEST_OVERLAY);
} else {
CanShowFloat = true;
}
}
}
/** 浮窗權限請求,Activity執行結果,回調函數 */
public void onActivityResult(int requestCode, int resultCode, Intent data, final AppCompatActivity Instatnce)
{
// Toast.makeText(activity, "onActivityResult設置權限!", Toast.LENGTH_SHORT).show();
if (requestCode == REQUEST_OVERLAY) // 從應用權限設置界面返回
{
if(resultCode == AppCompatActivity.RESULT_OK)
{
CanShowFloat = true; // 設置標識為可顯示懸浮窗
}
else
{
CanShowFloat = false;
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
if (!Settings.canDrawOverlays(Instatnce)) // 若當前未允許顯示懸浮窗,則提示授權
{
AlertDialog.Builder builder = new AlertDialog.Builder(Instatnce);
builder.setCancelable(false);
builder.setTitle("懸浮窗權限未授權");
builder.setMessage("應用需要懸浮窗權限,以展示浮標");
builder.setPositiveButton("去添加 權限", new DialogInterface.OnClickListener()
{
@Override
public void onClick(DialogInterface dialog, int which)
{
dialog.dismiss();
RequestOverlayPermission(Instatnce);
}
});
builder.setNegativeButton("拒絕則 退出", new DialogInterface.OnClickListener()
{
@Override
public void onClick(DialogInterface dialog, int which)
{
dialog.dismiss();
// 若拒絕了所需的權限請求,則退出應用
Instatnce.finish();
System.exit(0);
}
});
builder.show();
}
}
}
}
}
}