Android 蓝牙串口通信Demo

一、前言

在学校和机电的同学组队准备做一个智能小车去参加比赛,需要我开发一个小车的控制app,所以我开始学习蓝牙串口通信的相关知识。
在看了别人的博客之后,我写了一个蓝牙串口通信的Demo。但由于我没有那些硬件,所以只能在笔记本上下载串口调试工具,然后和手机蓝牙配对来进行通信测试,测试没有问题,用于小车蓝牙模块的通讯估计也是没有问题的。

关于如何设置笔记本电脑的蓝牙 串口并且和手机蓝牙配对,以及pc串口调试工具的下载请看这篇博客:
https://blog.csdn.net/weixin_44902943/article/details/113114481
通过上面这篇博客的操作再进入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
https://download.csdn.net/download/weixin_44902943/14929360

GitHub:
https://github.com/gitHub-lgh/BlueToothSerialPort

  • 17
    点赞
  • 128
    收藏
    觉得还不错? 一键收藏
  • 22
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 22
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值