欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

Android 蓝牙的一些使用心得

程序员文章站 2022-06-08 17:02:49
...

最近在搞蓝牙串口开发,由于此前对蓝牙这块接触较少,所以在写项目时碰壁不少。在查阅了不少大神写的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();
        }
    }
}

Android 蓝牙的一些使用心得

搜索蓝牙

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

    //取消当前正在搜索设备...
    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软件模拟数据接收和发送
Android 蓝牙的一些使用心得
建立连接

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

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

如果没配对过,会弹出配对请求,配对成功后系统会自动连接,设备连接成功后我们就可以处理数据的收发了
Android 蓝牙的一些使用心得

   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;
        }
    }
}

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

欢迎关注我的个人微信公众号,【优了个秀】和你每天进步一点点
Android 蓝牙的一些使用心得