Android 蓝牙的一些使用心得

最近在搞蓝牙串口开发,由于此前对蓝牙这块接触较少,所以在写项目时碰壁不少。在查阅了不少大神写的Android蓝牙项目技术文章后,于是写下本篇文章,算是自己这段时间对蓝牙些许了解的一个总结吧。

注:本篇章只介绍经典蓝牙,想了解低功耗蓝牙的请出门左转,哈哈~~~

蓝牙开发的基本流程为:开启-》搜索-》配对-》连接-》数据交换。其中配对成功之后系统会自动执行连接操作。
在这里插入图片描述

权限

为了后面工作的不必要麻烦,首先在AndroidManifest.xml配置蓝牙权限

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

如果是6.0以上的系统,还需要增加定位相关的权限,还会延申到动态申请权限和GPS的检测

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

开启蓝牙

首先获取本地蓝牙适配器BluetoothAdapter 如果为空,说明设备不支持蓝牙。

public BluetoothAdapter getBtAdapter() {
    BluetoothAdapter bluetoothAdapter =  BluetoothAdapter.getDefaultAdapter();
    if (bluetoothAdapter == null){
        Log.i(TAG, "getBtAdapter: 该设备不支持蓝牙");
    }
    return bluetoothAdapter;
}

开启蓝牙的方式有两种,第一种简单粗暴,直接调用enable()方法即可,没有如何提示

bluetoothAdapter.enable();

第二种方式:Intent隐式打开,然后在onActivityResult方法中接收结果

 Intent enableBtIntent = new Intent(BluetoothAdapter.ACTION_REQUEST_ENABLE);
 startActivityForResult(enableBtIntent, REQUEST_ENABLE_BLUETOOTH);

 ... ...

@Override
protected void onActivityResult(int requestCode, int resultCode, @Nullable Intent data) {
    super.onActivityResult(requestCode, resultCode, data);
    if (requestCode == REQUEST_ENABLE_BLUETOOTH) {
        if (resultCode == Activity.RESULT_OK) {
            Toast.makeText(BluetoothActivity.this, "打开蓝牙成功", Toast.LENGTH_SHORT).show();
            tvStatu.setText("蓝牙已开启");
            searchBtDevice();
        } else {
            Toast.makeText(BluetoothActivity.this, "打开蓝牙失败", Toast.LENGTH_SHORT).show();
        }
    }
}

在这里插入图片描述

搜索蓝牙

扫描附近的蓝牙,开始扫描前前取消正在执行的搜索

    //取消当前正在搜索设备...
    if (getBtAdapter().isDiscovering()) {
        getBtAdapter().cancelDiscovery();
    }
    getBtAdapter().startDiscovery();

startDiscovery()是一个一步操作,一般会搜索12秒左右

广播接收扫描结果

注册广播

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); //配对状态监听

接收广播

自定义一个广播接收类,监听设备扫描状态

/**
 * 搜索蓝牙广播接收器
 */
private class SearchReceiver extends BroadcastReceiver {
    @Override
    public void onReceive(Context context, Intent intent) {
        String action = intent.getAction();

        if (BluetoothAdapter.ACTION_DISCOVERY_STARTED.equals(action)) {
            mOnSearchDeviceListener.onStartDiscovery();
            //开始搜索
        } else if (BluetoothDevice.ACTION_FOUND.equals(action)) {
            //发现设备
            BluetoothDevice device = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);
            if (mOnSearchDeviceListener != null) {
                mOnSearchDeviceListener.onNewDeviceFound(device);
            }
            if (device.getBondState() == BluetoothDevice.BOND_NONE) {
                mNewList.add(device);
            } else if (device.getBondState() == BluetoothDevice.BOND_BONDED) {
                mBondedList.add(device);
            }
        } else if (BluetoothAdapter.ACTION_DISCOVERY_FINISHED.equals(action)) {
            if (getBtAdapter() != null) {
                getBtAdapter().cancelDiscovery();
            }
            //停止搜索
            if (mOnSearchDeviceListener != null) {
                mOnSearchDeviceListener.onSearchCompleted(mBondedList, mNewList);
            }
        }
    }
}

连接设备

我手上有个蓝牙转串口USB工具,所以就不写蓝牙服务端了。同时使用了SSCOM软件模拟数据接收和发送
在这里插入图片描述
建立连接

因为设备的连接时耗时的,所以我们需要另起一个线程ConnectThread,通过bluetoothDevice.createRfcommSocketToServiceRecord(UUID.fromString(BLUETOOTH_UUID));拿到BluetoothSocket,然后就可以connect了

需要注意的是 调用connect(),最好先释放BluetoothSocket

如果没配对过,会弹出配对请求,配对成功后系统会自动连接,设备连接成功后我们就可以处理数据的收发了
在这里插入图片描述

   public class ConnectThread extends Thread {
	    private static final String TAG = "ConnectThread";
	    private final BluetoothAdapter mBluetoothAdapter;
	    private BluetoothSocket mmSocket;
	    private final String BLUETOOTH_UUID = "00001101-0000-1000-8000-00805F9B34FB";
	    public ConnectThread(BluetoothAdapter bluetoothAdapter, BluetoothDevice bluetoothDevice) {
	        this.mBluetoothAdapter = bluetoothAdapter;
	        BluetoothSocket tmpBluetoothSocket = null;
	        if (mmSocket != null) {
	            Log.i(TAG, "ConnectThread-->mmSocket != null先去释放");
	            try {
	                mmSocket.close();
	            } catch (IOException e) {
	                e.printStackTrace();
	            }
	        }
	        Log.i(TAG, "ConnectThread-->mmSocket != null已释放");
	        //1、获取BluetoothSocket
	        try {
	            //建立安全的蓝牙连接,会弹出配对框
	            tmpBluetoothSocket = bluetoothDevice.createRfcommSocketToServiceRecord(UUID.fromString(BLUETOOTH_UUID));
	        } catch (IOException e) {
	            Log.i(TAG, "ConnectThread-->获取BluetoothSocket异常!" + e.getMessage());
	        }
	        mmSocket = tmpBluetoothSocket;
	        if (mmSocket != null) {
	            Log.i(TAG, "ConnectThread-->已获取BluetoothSocket");
	        }
	
	    }
	
	    @Override
	    public void run() {
	        //连接之前先取消发现设备,否则会大幅降低连接尝试的速度,并增加连接失败的可能性
	        if (mBluetoothAdapter == null) {
	            Log.i(TAG, "ConnectThread:run-->mBluetoothAdapter == null");
	            return;
	        }
	        //取消发现设备
	        if (mBluetoothAdapter.isDiscovering()) {
	            mBluetoothAdapter.cancelDiscovery();
	        }
	
	        if (mmSocket == null) {
	            Log.i(TAG, "ConnectThread:run-->mmSocket == null");
	            return;
	        }
	
	        //2、通过socket去连接设备
	        try {
	            Log.i(TAG, "ConnectThread:run-->去连接...");
	            if (onBluetoothConnectListener != null) {
	                onBluetoothConnectListener.onStartConn();  //开始去连接回调
	            }
	            mmSocket.connect();  //connect()为阻塞调用,连接失败或 connect() 方法超时(大约 12 秒之后),它将会引发异常
	
	            if (onBluetoothConnectListener != null) {
	                onBluetoothConnectListener.onConnSuccess(mmSocket);  //连接成功回调
	                Log.i(TAG, "ConnectThread:run-->连接成功");
	            }
	
	        } catch (IOException e) {
	            Log.i(TAG, "ConnectThread:run-->连接异常!" + e.getMessage());
	
	            if (onBluetoothConnectListener != null) {
	                onBluetoothConnectListener.onConnFailure("连接异常:" + e.getMessage());
	            }
	            //释放
	            cancel();
	        }
	
	    }
	
	    /**
	     * 释放
	     */
	    public void cancel() {
	        try {
	            if (mmSocket != null && mmSocket.isConnected()) {
	                Log.i(TAG, "ConnectThread:cancel-->mmSocket.isConnected() = " + mmSocket.isConnected());
	                mmSocket.close();
	                mmSocket = null;
	                return;
	            }
	
	            if (mmSocket != null) {
	                mmSocket.close();
	                mmSocket = null;
	            }
	
	            Log.i(TAG, "ConnectThread:cancel-->关闭已连接的套接字释放资源");
	
	        } catch (IOException e) {
	            Log.i(TAG, "ConnectThread:cancel-->关闭已连接的套接字释放资源异常!" + e.getMessage());
	        }
	    }
	
	    private OnBluetoothConnectListener onBluetoothConnectListener;
	
	    public void setOnBluetoothConnectListener(OnBluetoothConnectListener onBluetoothConnectListener) {
	        this.onBluetoothConnectListener = onBluetoothConnectListener;
	    }
	    //连接状态监听者
	    public interface OnBluetoothConnectListener {
	        void onStartConn();  //开始连接
	        void onConnSuccess(BluetoothSocket bluetoothSocket);  //连接成功
	        void onConnFailure(String errorMsg);  //连接失败
	    }
    }
 

数据交换

设备连接成功后就可以进行数据交换了。我们可以写个ConnectedThread线程来处理数据的接收read()和发送write()。

public class ConnectedThread extends Thread {
    private static final String TAG = "ConnectedThread";
    private BluetoothSocket mmSocket;
    private InputStream mmInStream;
    private OutputStream mmOutStream;
    //是否是主动断开
    private boolean isStop = false;
    //发起蓝牙连接的线程
    private ConnectThread connectThread;
    private OnReceiveListener onReceiveListener;
    private Handler handler = new Handler(Looper.getMainLooper());

    public void terminalClose(ConnectThread connectThread) {
        isStop = true;
        this.connectThread = connectThread;
    }

    public ConnectedThread(BluetoothSocket socket) {
        mmSocket = socket;

        InputStream tmpIn = null;
        OutputStream tmpOut = null;
        //使用临时对象获取输入和输出流,因为成员流是静态类型
        try {
            tmpIn = socket.getInputStream();
            tmpOut = socket.getOutputStream();

        } catch (IOException e) {
            Log.i(TAG, "ConnectedThread-->获取InputStream 和 OutputStream异常!");
        }
        mmInStream = tmpIn;
        mmOutStream = tmpOut;

        if (mmInStream != null) {
            Log.i(TAG, "ConnectedThread-->已获取InputStream");
        }

        if (mmOutStream != null) {
            Log.i(TAG, "ConnectedThread-->已获取OutputStream");
        }
    }

    @Override
    public void run() {
        //最大缓存区 存放流
        byte[] buffer = new byte[1024 * 2];
        //持续监听输入流直到发生异常
        while (!isStop) {
            try {
                if (mmInStream == null) {
                    Log.i(TAG, "ConnectedThread:run-->输入流mmInStream == null");
                    break;
                }
                //先判断是否有数据,有数据再读取
                if (mmInStream.available() != 0) {
                    //2、接收数据
                    /*
                    为了demo正常运行,假设接收的数据长度为10;
                    实际上我们可以自定义数据格式,如:STX + LENGTH(2hex bytes)+ CONTENT + ETX + LRC
                     */
                    int msgLen = 10;
                    int readTotal = 0;
                    int readCount;
                    while (readTotal < msgLen) {
                        readCount = mmInStream.read(buffer, readTotal, msgLen - readTotal);
                        readTotal += readCount;
                    }
                    final byte[] respData = new byte[readTotal];
                    System.arraycopy(buffer, 0, respData, 0, readTotal);
                    if (onReceiveListener != null) {
                        handler.post(new Runnable() {
                            @Override
                            public void run() {
                                onReceiveListener.onReceiveDataSuccess(respData);  //成功收到消息
                            }
                        });
                    }
                }
            } catch (final IOException e) {
                Log.i(TAG, "ConnectedThread:run-->接收消息异常!" + e.getMessage());
                if (onReceiveListener != null) {
                    handler.post(new Runnable() {
                        @Override
                        public void run() {
                            onReceiveListener.onReceiveDataError("接收消息异常:" + e.getMessage());  //接收消息异常
                        }
                    });
                }
                //关闭流和socket
                boolean isClose = cancel();
                if (isClose) {
                    Log.i(TAG, "ConnectedThread:run-->接收消息异常,成功断开连接!");
                }
                break;
            }
        }
        //关闭流和socket
        boolean isClose = cancel();
        if (isClose) {
            Log.i(TAG, "ConnectedThread:run-->接收消息结束,断开连接!");
        }
    }

    public void receive(OnReceiveListener onReceiveListener) {
        this.onReceiveListener = onReceiveListener;
    }

    //发送数据
    public void write(final byte[] bytes, final OnSendListener onSendListener) {
        try {
            if (mmOutStream == null) {
                Log.i(TAG, "mmOutStream == null");
                return;
            }
            mmOutStream.write(bytes);
            Log.i(TAG, "写入成功:" + new String(bytes));
            if (onSendListener != null) {
                handler.post(new Runnable() {
                    @Override
                    public void run() {
                        onSendListener.onSendSuccess(bytes);  //发送数据成功回调
                    }
                });
            }

        } catch (IOException e) {
            Log.i(TAG, "写入失败:" + new String(bytes));
            if (onSendListener != null) {
                handler.post(new Runnable() {
                    @Override
                    public void run() {
                        onSendListener.onSendError(bytes, "写入失败");  //发送数据失败回调
                    }
                });
            }
        }
    }

    /**
     * 释放
     *
     * @return true 断开成功  false 断开失败
     */
    public boolean cancel() {
        try {
            if (mmInStream != null) {
                mmInStream.close();  //关闭输入流
            }
            if (mmOutStream != null) {
                mmOutStream.close();  //关闭输出流
            }
            if (mmSocket != null) {
                mmSocket.close();   //关闭socket
            }
            if (connectThread != null) {
                connectThread.cancel();
            }
            connectThread = null;
            mmInStream = null;
            mmOutStream = null;
            mmSocket = null;
            Log.i(TAG, "ConnectedThread:cancel-->成功断开连接");
            return true;
        } catch (IOException e) {
            // 任何一部分报错,都将强制关闭socket连接
            mmInStream = null;
            mmOutStream = null;
            mmSocket = null;
            Log.i(TAG, "ConnectedThread:cancel-->断开连接异常!" + e.getMessage());
            return false;
        }
    }
}

以上就是经典蓝牙的基本操作流程了,剩下的就是需要注意一些细节了,比如设备搜索时长的控制、连接设备超时的处理等等。源码传送门

欢迎关注我的个人微信公众号,【优了个秀】和你每天进步一点点
在这里插入图片描述

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值