目录
- 一、前言
- 二、效果图
- 三、源码(带注释)
- 1、布局代码
- 2、Java代码
- 3、权限声明
- 四、注意
- 五、项目源码下载
一、前言
在学校和机电的同学组队准备做一个智能小车去参加比赛,需要我开发一个小车的控制app,所以我开始学习蓝牙串口通信的相关知识。
在看了别人的博客之后,我写了一个蓝牙串口通信的Demo。但由于我没有那些硬件,所以只能在笔记本上下载串口调试工具,然后和手机蓝牙配对来进行通信测试,测试没有问题,用于小车蓝牙模块的通讯估计也是没有问题的。
关于如何设置笔记本电脑的蓝牙 串口并且和手机蓝牙配对,以及pc串口调试工具的下载请看这篇博客:
通过上面这篇博客的操作再进入app就能实现本Demo 和 pc串口调试工具的数据互传了。(如果在app中总是显示连接出错,多半是因为蓝牙没有配对好,关闭蓝牙再多配对几次)
二、效果图
先点击开启蓝牙(即使蓝牙已打开),然后再搜索设备,列表里没有找到继续点击搜索设备
点击要连接的设备跳转到通讯页面
三、源码(带注释)
我就直接放源码吧,里面的注释写得挺详细。
建议先熟悉一下蓝牙开发的相关api再来看。
1、布局代码
activity_main.xml
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity">
<Button
android:id="@+id/open_bluetooth_btn"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="开启蓝牙"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="0.27"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintVertical_bias="0.07" />
<Button
android:id="@+id/fount_device_btn"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="搜索设备"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="0.694"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintVertical_bias="0.07" />
<TextView
android:id="@+id/textView"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="10dp"
android:layout_marginTop="20dp"
android:text="蓝牙设备列表"
android:textColor="@color/black"
android:textSize="16sp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="0.0"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/open_bluetooth_btn"
app:layout_constraintVertical_bias="0.0" />
<ListView
android:id="@+id/bluetooth_device_list"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="10dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/textView"
app:layout_constraintVertical_bias="0.0" />
</androidx.constraintlayout.widget.ConstraintLayout>
communication_layout.xml
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent">
<Button
android:id="@+id/send_text_btn"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginEnd="10dp"
android:layout_marginBottom="10dp"
android:text="发送"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="1.0"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintVertical_bias="1.0" />
<EditText
android:id="@+id/send_edit_text"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="10dp"
android:layout_marginEnd="4dp"
android:layout_marginBottom="10dp"
android:hint="输入要发送的信息"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toStartOf="@+id/send_text_btn"
app:layout_constraintHorizontal_bias="0.0"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintVertical_bias="1.0" />
<TextView
android:id="@+id/bluetooth_name"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="蓝牙设备名称"
android:textColor="@color/black"
android:textSize="18sp"
app:layout_constraintBottom_toTopOf="@+id/send_edit_text"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintVertical_bias="0.0" />
<LinearLayout
android:layout_width="match_parent"
android:layout_height="0dp"
android:orientation="horizontal"
android:padding="10dp"
app:layout_constraintBottom_toTopOf="@+id/send_edit_text"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/bluetooth_name">
<TextView
android:id="@+id/received_text_content"
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_marginRight="5dp"
android:layout_weight="1"
android:background="@drawable/communication_list_shape"
android:padding="4dp"
android:text="我收到的信息:"
android:textColor="@color/black" />
<TextView
android:id="@+id/send_text_content"
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_marginLeft="5dp"
android:layout_weight="1"
android:background="@drawable/communication_list_shape"
android:padding="4dp"
android:text="我发出的信息:"
android:textColor="@color/black" />
</LinearLayout>
<TextView
android:id="@+id/cancel_conn_btn"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="10dp"
android:text="结束"
android:textColor="@color/purple_500"
android:textSize="16sp"
app:layout_constraintBottom_toBottomOf="@+id/bluetooth_name"
app:layout_constraintEnd_toStartOf="@+id/bluetooth_name"
app:layout_constraintHorizontal_bias="0.0"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>
2、Java代码
MainActivity.java
package com.example.bluetoothdemo;
import androidx.appcompat.app.AppCompatActivity;
import androidx.core.app.ActivityCompat;
import android.Manifest;
import android.bluetooth.BluetoothAdapter;
import android.bluetooth.BluetoothDevice;
import android.bluetooth.BluetoothSocket;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.pm.PackageManager;
import android.os.Build;
import android.os.Bundle;
import android.util.Log;
import android.view.View;
import android.widget.AdapterView;
import android.widget.Button;
import android.widget.ListView;
import com.example.bluetoothdemo.adapter.MyArrayAdapter;
import com.example.bluetoothdemo.bean.DeviceInformation;
import com.example.bluetoothdemo.utils.ToastUtil;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.ArrayList;
import java.util.List;
import java.util.UUID;
/**
* 蓝牙串口通信app
* author:CSDN 在下木子李
* create at 2021/1/24
*/
public class MainActivity extends AppCompatActivity implements View.OnClickListener {
private static final int MY_PERMISSION_REQUEST_CONSTANT = 1;
private Button mOpenBluetoothBtn;
private Button mFoundDeviceBtn;
private ListView mDeviceList;
private MyArrayAdapter mAdapter;
private List<DeviceInformation> mDatas = new ArrayList<>();
private BluetoothAdapter mBluetoothAdapter;
private ToastUtil mToast;
private BroadcastReceiver mBluetoothReceiver;//用于接收蓝牙状态改变广播的广播接收者
private String TAG = "MainActivity";
private BroadcastReceiver mBLuetoothStateReceiver;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
initReceiver();
initView();
initListener();
}
/*
注册广播接收者
*/
private void initReceiver() {
//创建用于接收蓝牙状态改变广播的广播接收者
mBLuetoothStateReceiver = new BroadcastReceiver(){
@Override
public void onReceive(Context context, Intent intent) {
int state = intent.getIntExtra(BluetoothAdapter.EXTRA_STATE, -1);
switch (state){
case BluetoothAdapter.STATE_ON:
mToast.showToast("蓝牙已打开");
break;
case BluetoothAdapter.STATE_OFF:
mToast.showToast("蓝牙已关闭");
break;
case BluetoothAdapter.STATE_TURNING_ON:
mToast.showToast("蓝牙正在打开");
break;
case BluetoothAdapter.STATE_TURNING_OFF:
mToast.showToast("蓝牙正在关闭");
break;
}
}
};
//创建设备扫描广播接收者
mBluetoothReceiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
Log.d(TAG,"onReceive");
String action = intent.getAction();
if (BluetoothDevice.ACTION_FOUND.equals(action)) {
boolean isAdded = false;//标记扫描到的设备是否已经在数据列表里了
//获取扫描到的设备
BluetoothDevice device = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);
//保存设备的信息
DeviceInformation deviceInformation = new DeviceInformation(device.getName(),device.getAddress());
for (DeviceInformation data : mDatas) {
//判断已保存的设备信息里是否有一样的
if (data.getDeviceAddress().equals(deviceInformation.getDeviceAddress())) {
isAdded = true;
break;
}
}
if (!isAdded) {
//通知UI更新
mDatas.add(deviceInformation);
mAdapter.notifyDataSetChanged();
}
}
}
};
//注册广播接收者
IntentFilter filter1 = new IntentFilter(BluetoothAdapter.ACTION_STATE_CHANGED);
IntentFilter filter2 = new IntentFilter(BluetoothDevice.ACTION_FOUND);
registerReceiver(mBLuetoothStateReceiver,filter1);
registerReceiver(mBluetoothReceiver,filter2);
}
//权限是否授予,给出提示
public void onRequestPermissionsResult(int requestCode, String permissions[], int[] grantResults) {
switch (requestCode) {
case MY_PERMISSION_REQUEST_CONSTANT: {
if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
mToast.showToast("权限授权成功");
}else{
mToast.showToast("权限授权失败");
}
return;
}
}
}
private void initView() {
//安卓6.0开始需要动态申请权限
if (Build.VERSION.SDK_INT >= 6.0) {
ActivityCompat.requestPermissions(this, new String[]{
Manifest.permission.ACCESS_FINE_LOCATION},
MY_PERMISSION_REQUEST_CONSTANT);
}
mOpenBluetoothBtn = findViewById(R.id.open_bluetooth_btn);
mFoundDeviceBtn = findViewById(R.id.fount_device_btn);
mDeviceList = findViewById(R.id.bluetooth_device_list);
mToast = new ToastUtil(this);
mAdapter = new MyArrayAdapter(mDatas,this);
mDeviceList.setAdapter(mAdapter);
}
//初始化监听
private void initListener() {
mOpenBluetoothBtn.setOnClickListener(this);
mFoundDeviceBtn.setOnClickListener(this);
//设备列表item的点击事件
mDeviceList.setOnItemClickListener(new AdapterView.OnItemClickListener() {
@Override
public void onItemClick(AdapterView<?> adapterView, View view, int position, long id) {
if (mBluetoothAdapter.isDiscovering()) {
//停止搜索设备
mBluetoothAdapter.cancelDiscovery();
}
//获取点击的item的设备信息
DeviceInformation deviceInformation = mDatas.get(position);
//跳转到设备通信页面
Intent intent = new Intent(MainActivity.this,CommunicationActivity.class);
//将设备地址传递过去
intent.putExtra("name",deviceInformation.getDeviceName());
intent.putExtra("address",deviceInformation.getDeviceAddress());
startActivity(intent);
}
});
}
/**
* 检测和开启蓝牙
*/
private void openBluetooth() {
mBluetoothAdapter = BluetoothAdapter.getDefaultAdapter();
if (mBluetoothAdapter != null) {
//判断蓝牙是否打开并可见
if (!mBluetoothAdapter.isEnabled()) {
//请求打开并可见
Intent intent = new Intent(BluetoothAdapter.ACTION_REQUEST_ENABLE);
startActivityForResult(intent,1);
}
}else{
mToast.showToast("设备不支持蓝牙功能");
}
}
/**
* 搜索蓝牙设备
*/
private void discoverBluetooth(){
if (mBluetoothAdapter.isDiscovering()) {
mBluetoothAdapter.cancelDiscovery();
}
//搜索设备
mBluetoothAdapter.startDiscovery();
mToast.showToast("正在搜索设备");
}
/**
点击事件
*/
@Override
public void onClick(View view) {
switch (view.getId()){
case R.id.open_bluetooth_btn:
//开启蓝牙
openBluetooth();
break;
case R.id.fount_device_btn:
//搜索设备
discoverBluetooth();
break;
default:
break;
}
}
}
Communication.java
package com.example.bluetoothdemo;
import android.bluetooth.BluetoothAdapter;
import android.bluetooth.BluetoothDevice;
import android.bluetooth.BluetoothSocket;
import android.content.Intent;
import android.os.Bundle;
import android.text.Editable;
import android.text.TextWatcher;
import android.util.Log;
import android.view.View;
import android.widget.Button;
import android.widget.EditText;
import android.widget.ListView;
import android.widget.TextView;
import androidx.annotation.Nullable;
import androidx.appcompat.app.AppCompatActivity;
import com.example.bluetoothdemo.utils.ToastUtil;
import org.w3c.dom.Text;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.UUID;
/**
* 串口通信页面
* author:CSDN 在下木子李
* create at 2021/1/24
*/
public class CommunicationActivity extends AppCompatActivity {
private EditText mEditText;
private Button mSendBtn;
private String mAddress;
private BluetoothAdapter mBlueToothAdapter;
private BluetoothDevice mDevice;
private BluetoothSocket mBluetoothSocket;
private final UUID mUUID = UUID.fromString("00001101-0000-1000-8000-00805F9B34FB");//蓝牙串口服务的UUID
private ToastUtil mToast;
private TextView mReceiveContent;
private TextView mSendContent;
private TextView mCancelConn;
private String mSendContentStr;
private static OutputStream mOS;
private String TAG = "CommunicationActivity";
private String mName;
private TextView mBtName;
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.communication_layout);
Intent intent = getIntent();
//得到传输过来的设备地址
mAddress = intent.getStringExtra("address");
mName = intent.getStringExtra("name");
initView();
initListener();
//开始连接
connectDevice();
}
private void initView() {
mEditText = findViewById(R.id.send_edit_text);
mSendBtn = findViewById(R.id.send_text_btn);
mToast = new ToastUtil(this);
mReceiveContent = findViewById(R.id.received_text_content);
mSendContent = findViewById(R.id.send_text_content);
mCancelConn = findViewById(R.id.cancel_conn_btn);
mBtName = findViewById(R.id.bluetooth_name);
mBtName.setText(mName);
}
private void initListener() {
mSendBtn.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
mSendContentStr = mEditText.getText().toString();
//发送信息
sendMessage(mSendContentStr);
}
});
mCancelConn.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
finish();
}
});
}
/**
* 发送数据的方法
* @param contentStr
*/
private void sendMessage(String contentStr) {
if (mBluetoothSocket.isConnected()) {
try {
//获取输出流
mOS = mBluetoothSocket.getOutputStream();
if (mOS != null) {
//写数据(参数为byte数组)
mOS.write(contentStr.getBytes("GBK"));
mEditText.getText().clear();
mSendContent.append(contentStr);
mToast.showToast("发送成功");
}
} catch (IOException e) {
e.printStackTrace();
}
}else{
mToast.showToast("没有设备已连接");
}
}
/**
* 与目标设备建立连接
*/
private void connectDevice() {
//获取默认蓝牙设配器
mBlueToothAdapter = BluetoothAdapter.getDefaultAdapter();
//通过地址拿到该蓝牙设备device
mDevice = mBlueToothAdapter.getRemoteDevice(mAddress);
try {
//建立socket通信
mBluetoothSocket = mDevice.createRfcommSocketToServiceRecord(mUUID);
mBluetoothSocket.connect();
if (mBluetoothSocket.isConnected()) {
mToast.showToast("连接成功");
//开启接收数据的线程
ReceiveDataThread thread = new ReceiveDataThread();
thread.start();
}else{
mToast.showToast("连接失败,结束重进");
}
} catch (IOException e) {
e.printStackTrace();
mToast.showToast("连接出错! ");
finish();
try {
mBluetoothSocket.close();
} catch (IOException ioException) {
ioException.printStackTrace();
}
}
}
@Override
protected void onDestroy() {
super.onDestroy();
try {
if (mBluetoothSocket.isConnected()) {
//关闭socket
mBluetoothSocket.close();
mBlueToothAdapter = null;
}
} catch (IOException e) {
e.printStackTrace();
}
}
/**
* 负责接收数据的线程
*/
public class ReceiveDataThread extends Thread{
private InputStream inputStream;
public ReceiveDataThread() {
super();
try {
//获取连接socket的输入流
inputStream = mBluetoothSocket.getInputStream();
} catch (IOException e) {
e.printStackTrace();
}
}
@Override
public void run() {
super.run();
int len = 0;
byte[] buffer = new byte[256];
while (true){
try {
inputStream.read(buffer);
for (byte b : buffer) {
Log.d(TAG,"b:" + b);
}
//设置GBK格式可以获取到中文信息,不会乱码
String a = new String(buffer,0,buffer.length - 3,"GBK");//为什么-3 看文章最后注意部分
Log.d(TAG,"a:" + a);
// byte[] gbks = "你好".getBytes("GBK");
// for (byte gbk : gbks) {
// Log.d(TAG,"gbk:" + gbk);
// }
// String[] chars = a.split(" ");
// String str = "";
// for(int i = 0; i<chars.length;i++){
// str += (char)Integer.parseInt(chars[i]);
// }
//Log.d(TAG,"str:" + str);
runOnUiThread(new Runnable() {
@Override
public void run() {
//将收到的数据显示在TextView上
mReceiveContent.append(a);
}
});
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
}
3、权限声明
<uses-permission android:name="android.permission.BLUETOOTH"/>
<uses-permission android:name="android.permission.BLUETOOTH_ADMIN"/>
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION"/>
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION"/>
四、注意
我使用的这个友善串口调试工具进行通讯测试时,我发现用它发完数据后,在手机端接收到的数据的字节数组会多出3个字节,不知道是干什么用的。字节数组转汉字后会乱码,所以我就减去了最后3个字节。
还有编码格式要用GBK,不然汉字会乱码。不过在智能小车的数据传输中应该用不到汉字。
五、项目源码下载
源码+apk
GitHub:
https://github.com/gitHub-lgh/BlueToothSerialPort