之前因为项目需要,做了一个和设备通信的蓝牙模块,开始只是一个简单的发送接收,最近整理了一下,做了一些改进,也算是一种学习和进步吧!

下面看具体内容:

先看效果

江湖规矩,先贴一个扫描的图再将内容吧

android弹窗搜索蓝牙信息 安卓蓝牙有没有弹窗_android弹窗搜索蓝牙信息

文档准备

工欲善其事必先利其器,想要做好一个功能,我觉得最先要做的就是看官方文档,如果官方文档不够详细,再补充一点别人的博客,最后融合成自己的东西,下面是谷歌官方文档,建议先看看,写的非常不错,还是中文的。

https://developer.android.google.cn/guide/topics/connectivity/bluetooth

下面开始介绍代码吧

添加权限

先在 manifest 里面添加下面几个权限

<uses-permission android:name="android.permission.BLUETOOTH" />
<uses-permission android:name="android.permission.BLUETOOTH_ADMIN" />
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />

因为权限是按注给的(可能高版本不行了),只需要申请下面这个权限就行了

Manifest.permission.ACCESS_COARSE_LOCATION

蓝牙客户端分析

关于客户端我这先整理一下思路,后面再看具体实现。

被连设备状态

首先是我们要知道,当我们想要去连接一个蓝牙设备时,这个蓝牙设备有几种状态?如果看了上面说的官方文档,应该很容易得到答案,状态如下

  • 已配对过
  • 扫描到了
  • 未扫描,且从未连接过

当然这是我的答案,有点牵强,却符合我们设计代码时的业务,下面讲讲这三个状态的一些相关内容。

已配对的设备

我们可以通过下面方法拿到已配对的蓝牙设备。

// 获得和当前Android已经配对的蓝牙设备。
Set<BluetoothDevice> pairedDevices = mBluetoothAdapter.getBondedDevices();
扫描蓝牙设备

上面被连接的设备的第二种及第三种状态都与扫描有关,蓝牙设备的扫描通过 BluetoothAdapter 和广播接收器实现的。下面一个一个介绍,先是 BluetoothAdapter 的扫描:

//蓝牙适配器
BluetoothAdapter mBluetoothAdapter = BluetoothAdapter.getDefaultAdapter();
//开启扫描,会自动停止,好像是120s吧
mBluetoothAdapter.startDiscovery()

再是广播接收器的注册(再扫描前注册吧):

IntentFilter filter = new IntentFilter();

//开始扫描
filter.addAction(BluetoothAdapter.ACTION_DISCOVERY_STARTED);
//结束扫描
filter.addAction(BluetoothAdapter.ACTION_DISCOVERY_FINISHED);
//发现设备
filter.addAction(BluetoothDevice.ACTION_FOUND);
//状态变化
filter.addAction(BluetoothDevice.ACTION_BOND_STATE_CHANGED);

registerReceiver(mBroadcastReceiver, filter);

广播接收器收到的就是我们上次加的 filter 了,很好理解:

private final BroadcastReceiver mBroadcastReceiver = new BroadcastReceiver() {
    @SuppressLint("SetTextI18n")
    @Override
    public void onReceive(Context context, Intent intent) {
        String action = intent.getAction();
        switch (action) {
            case BluetoothAdapter.ACTION_DISCOVERY_STARTED:
                break;
            case BluetoothAdapter.ACTION_DISCOVERY_FINISHED:
                break;
            case BluetoothDevice.ACTION_FOUND:
                //发现一个设备
                BluetoothDevice device = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);
                //设备可能多次被发现
                if (null != device && !deviceList.contains(device)) {
                    ...
                }
                break;
            default:
                break;
        }
    }
};
客户端使用目的

对于一个客户端来说,不就是连接蓝牙设备,发送接收消息么。不管上面被连设备处于什么状态,我们的目的都是去连接它,给它发消息,接收它消息,这就是我们的目的。

也就是说,对于客户端,我们完全将它抽象为三个方法:

  • connectDevice
  • sendData
  • receiveData

明确了我们的目的,后面去细化这三个方法就行了,下面开始实现。

蓝牙客户端实现

连接设备

我们先看连接蓝牙设备的方法:

private BluetoothSocket socket;

try {
    socket = device.createRfcommSocketToServiceRecord(UUID.fromString(MY_UUID));
    socket.connect();
} catch (IOException e) {
    e.printStackTrace();
    runOnUiThread(() -> receiveText.append("创建socket失败!\n"));
}

蓝牙的 socket 和 socket 还是挺类似的,就是这里要注意一个 UUID 的问题,随便网上找个方法生成一个就行(官网这么说的…),我用的下面这个,好像很通用:

private final String MY_UUID = "00001101-0000-1000-8000-00805F9B34FB";

上面提到了,一个被连设备可能有三种状态,连接的时候我们就要分别处理了。其实也简单,这里连接不就是要一个 BluetoothSocket 类型的对象么,我们已配对的设备列表还有扫描结果的列表都是这个类型,直接就能使用,对于未扫描且从未连接过的设备,那就没办法了,只能再扫描试试。

  1. 已配对列表
  2. 已扫描列表
  3. 再扫描列表

也就是说对于一个要连接的蓝牙设备,我们先再已配对的设备里找,再从已扫描到的设备里面找,如果都没找到就再开启扫描找,找到了就有 BluetoothSocket 了,去连接就行!

发送数据

都说蓝牙的 socket 和 socket 类似,那发送肯定也差不多啊,事实也是如此:

//发送数据到另一端
private void sendDataToServer(String data) {
    new Thread(() -> {
        try {
            OutputStream os = socket.getOutputStream();
            os.write(data.getBytes());
            os.flush();
            //os.close();
            runOnUiThread(() -> receiveText.append("发送消息:" + data + "\n"));
        } catch (Exception e) {
            e.printStackTrace();
            runOnUiThread(() -> receiveText.append("发送消息:" + "失败!" + "\n"));
        }
    }).start();
}

这里我们拿到 socket 的输出流,开启个线程往里面写内容就可以发出去了。

接收数据

接收数据也和 socket 的使用类似,直接在创建连接那里写个死循环读取数据就可以了

threadPool.execute(() -> {
    try {
        socket = device.createRfcommSocketToServiceRecord(UUID.fromString(MY_UUID));
        socket.connect();
    } catch (IOException e) {
        e.printStackTrace();
        runOnUiThread(() -> receiveText.append("创建socket失败!\n"));
    }

    if (socket != null && socket.isConnected()) {
        runOnUiThread(() -> receiveText.append("创建socket成功!\n"));
        try {
            InputStream inputStream;
            while(socket != null) {

                //随便写写
                inputStream = socket.getInputStream();
                byte[] buffer = new byte[2560];
                int len = inputStream.read(buffer, 0, 2560);
                if (len <= 0) {
                    //noinspection BusyWait
                    Thread.sleep(1000);
                    continue;
                }

                runOnUiThread(()->receiveText.append("收到消息:" + new String(buffer, 0, len) + "\n"));
            }
        } catch(IOException e) {
            e.printStackTrace();
            Log.e("TAG", "startConnect: " + e.getMessage());
            runOnUiThread(() -> receiveText.append("socket异常!\n"));
        } catch(Exception e) {
            e.printStackTrace();
        }
    }
});

写的很粗糙,但是能用!根据自己要的功能改进吧。

完整代码太长了,我会在文章最后面贴 demo 的 gitee 地址,下面先看服务端的代码分析及实现。

蓝牙服务端

不同与蓝牙客户端的多种逻辑,蓝牙服务端实际就提供连接功能、接收数据和发送数据,下面我们直接讲解代码实现。

提供连接功能
mBluetoothAdapter = BluetoothAdapter.getDefaultAdapter();
receiveText.setText("等待客户端连接...\n");

//启动服务端
if (mBluetoothAdapter != null) {
    threadPool.execute(new Thread(() -> {
        try {
            serverSocket = mBluetoothAdapter
                    .listenUsingRfcommWithServiceRecord(tag, UUID.fromString(MY_UUID));
            while (true) {
                BluetoothSocket remotePeer = serverSocket.accept();

                bluetoothSockets.add(remotePeer);
                adapter.notifyDataSetChanged();

                //获取蓝牙设备
                BluetoothDevice device = remotePeer.getRemoteDevice();
                runOnUiThread(()->receiveText.append("连接客户端成功:" + device.getName() + "\n"));

                //启动线程处理socket连接
                threadPool.execute(new ServerThread(remotePeer));
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }));
}

这里也要用到 BluetoothAdapter 这个对象,大概就是蓝牙通信的核心了。创建蓝牙服务端需要提供一个 name 和 UUID,name可以随便填写,UUID 可以用上面提到的那个。拿到 BluetoothServerSocket 对象,接下来的内容就和 socket 服务端类似了,启动一个线程去处理每个 socket 连接。

发送数据

发送数据和客户端差不多,拿着输出流写内容就可以。

//向指定客户端socket发送消息
private void sendDataToClient(BluetoothSocket socket, String data) {
    String deviceName = null != socket ? socket.getRemoteDevice().getName() : null;
    if (null != socket && socket.isConnected()) {
        //异步线程发送
        threadPool.execute(() -> {
            try {
                socket.getOutputStream().write(data.getBytes());
                runOnUiThread(()->receiveText.append("发送消息->" + deviceName + ":" + data + "\n"));
            } catch (IOException e) {
                runOnUiThread(()->receiveText.append("发送消息->" + deviceName + ":" + "失败!"
                        + e.getMessage() + "\n"));
                e.printStackTrace();
            }
        });
    }else {
        runOnUiThread(()->receiveText.append("发送消息->" + deviceName + ":" + "失败!" + "\n"));
    }
}
接收数据

这里就是单个连接 socket 的线程执行代码,里面就是接收数据的部分。

//服务端线程
private class ServerThread implements Runnable {

    private final BluetoothSocket remotePeer;
    private final String deviceName;

    private ServerThread(BluetoothSocket remotePeer) {
        this.remotePeer = remotePeer;
        deviceName = remotePeer.getRemoteDevice().getName();
    }

    @Override
    public void run() {
        try {
            InputStream inputStream;
            while(remotePeer != null && remotePeer.isConnected()) {

                //简单写写
                inputStream = remotePeer.getInputStream();
                byte[] buffer = new byte[2560];
                int len = inputStream.read(buffer, 0, 2560);
                if (len <= 0) {
                    //noinspection BusyWait
                    Thread.sleep(1000);
                    continue;
                }

                //打印接收消息
                runOnUiThread(()->receiveText.append("收到消息->" + deviceName + ":"
                        + new String(buffer, 0, len) + "\n"));
            }
        } catch(IOException e) {
            e.printStackTrace();
        } catch(Exception e) {
            e.printStackTrace();
        }finally {
            try {

                if (null != remotePeer) {
                    remotePeer.close();
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
}

完整代码

完整代码太长了,正好我也是写了一个 demo,已提交到 gitee 上面,有需要的读者可以看看:


结语

本篇文章我自己觉得写的不是很好,可能我的侧重点在介绍思路和我觉得重要的知识吧,蓝牙部分的 demo 我倒是挺满意,有很多我没写出来的东西,希望对读者有帮助,基础知识的话还是建议看官方文档。

最近也算每天一篇博客了,希望能够把自己的思路和语言精炼起来,和读者一起进步。

end