概述
前段时间学习了一些蓝牙开发的知识,记录一下Android中蓝牙的简单开发。下面是最重要的两个类。
BluetoothAdapter : 蓝牙适配器,通过getDefaultAdapter ()去获取一个实例,如果设备不支持蓝牙的话,返回的是一个null对象,通过它,可以打开、关闭蓝牙,扫描设备、向指定设备创建socket通道…
BluetoothDevice : 代表一个设备对象,可以通过它获取设备的名字、地址、类型等,也可以创建匹配,建立socket通道等等。
1、权限申请
- <uses-permission android:name="android.permission.BLUETOOTH"/> 使用蓝牙所需要的权限
- <uses-permission android:name="android.permission.BLUETOOTH_ADMIN"/> 使用扫描和设置蓝牙的权限(申明这一个权限必须申明上面一个权限)
Android6以上版本,扫描其他蓝牙还需要位置权限
- // Android 9 以下版本
- <user-permission android:name="android.permission.ACCESS_COARSE_LOCATION"/>
- // Android 9 以上
- <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION"/>
2、打开蓝牙
- mBluetoothAdapter = BluetoothAdapter.getDefaultAdapter();
- // 如果设备不支持蓝牙
- if (mBluetoothAdapter == null){
- return;
- }
- // 设备支持蓝牙功能,调用startActivityForResult去启动蓝牙
- if (!mBluetoothAdapter.isEnabled()){
- startBlueTooth.launch(new Intent(BluetoothAdapter.ACTION_REQUEST_ENABLE));
- }
打开蓝牙功能是通过startActivity去启动的,但是startActivity这个函数已经过期了,所以我使用官方推荐的Activity Result替代它
- ActivityResultLauncher<Intent> startBlueTooth = registerForActivityResult(new ActivityResultContracts.StartActivityForResult(),
- new ActivityResultCallback<ActivityResult>() {
- @Override
- public void onActivityResult(ActivityResult result) {
- if (result==null){
- Toast.makeText(BlueToothActivity.this, "open failed", Toast.LENGTH_SHORT).show();
- }else {
- if (result.getResultCode() == RESULT_CANCELED){
- Toast.makeText(BlueToothActivity.this,"用户取消",Toast.LENGTH_SHORT);
- }
- }
- }
- });
-
3、接收蓝牙状态的改变
通过广播去接收蓝牙状态的改变
- class BluetoothStateChangeReceiver extends BroadcastReceiver{
- public int DEFAULT_VALUE_BLUETOOTH = 1000;
-
- @Override
- public void onReceive(Context context, Intent intent) {
- String action = intent.getAction();
- if (BluetoothAdapter.ACTION_STATE_CHANGED.equals(action)){
- int state = intent.getIntExtra(BluetoothAdapter.EXTRA_STATE,DEFAULT_VALUE_BLUETOOTH);
-
- switch(state){
- case BluetoothAdapter.STATE_ON:
- Log.d(TAG, "onReceive: open");
- break;
- case BluetoothAdapter.STATE_OFF:
- Log.d(TAG, "onReceive: off");
- break;
- case BluetoothAdapter.STATE_TURNING_ON :
- Log.d(TAG, "onReceive: 正在打开");
- break;
- case BluetoothAdapter.STATE_TURNING_OFF:
- Log.d(TAG, "onReceive: 正在关闭");
- break;
- }
- }
- }
- }
别忘了广播的注册和解注册
- IntentFilter filter = new IntentFilter(BluetoothAdapter.ACTION_STATE_CHANGED);
- stateReceiver = new BluetoothStateChangeReceiver() ;
- registerReceiver(stateReceiver,filter);
4、扫描其他的设备
同样通过广播接收,action是BluetoothDevice.ACTION_FOUND
- class MyReceiver extends BroadcastReceiver{
- @Override
- public void onReceive(Context context, Intent intent) {
-
- String action = intent.getAction();
-
- if (BluetoothDevice.ACTION_FOUND.equals(action)) {
- // 从intent对象中获取蓝牙设备的信息
- BluetoothDevice device = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);
- // 当发现新设备不存在于配对列表中时添加
- if (device.getBondState() != BluetoothDevice.BOND_BONDED) {
- blueNames.add(device.getName()+"\t"+device.getAddress());
- }
- blueAdpater.notifyDataSetChanged();
- Log.d(TAG, "onReceive: " + device.getName());
- }
- }
- }
动态注册广播
- IntentFilter filter = new IntentFilter(BluetoothDevice.ACTION_FOUND);
- registerReceiver(mReceiver,filter);
开启扫描
- mBluetoothAdapter.startDiscovery();
5、蓝牙配对
- public class BondReceiver extends BroadcastReceiver{
-
- @Override
- public void onReceive(Context context, Intent intent) {
- if (BluetoothDevice.ACTION_BOND_STATE_CHANGED.equals(intent.getAction())){
- BluetoothDevice device = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);
- switch(device.getBondState()){
- case BluetoothDevice.BOND_BONDED:
- Log.d(TAG, "onReceive: 配对完成");
- break;
- case BluetoothDevice.BOND_BONDING:
- Log.d(TAG, "onReceive: 正在配对");
- break;
- case BluetoothDevice.BOND_NONE:
- Log.d(TAG, "onReceive: 取消配对");
- break;
- }
- }
- }
- }
6、获取已经配对的设备
已经配对的设备会被存储起来,通过BluetoothAdpater直接获取即可
- Set<BluetoothDevice> paireDevices = mBluetoothAdapter.getBondedDevices();
- if (paireDevices.size()>0){
- for (BluetoothDevice pairedDevice : pairedDevices) {
- blueNames.add(pairedDevice.getName()+" "+pairedDevice.getAddress());
- Log.d(TAG, "onClick: "+pairedDevice.getName());
- }
- }
7、连接设备
想要在两台设备之间创建连接,必须实现客户端和服务端机制,他们之间使用套接字机制进行连接,服务端开放服务器套接字,客户端通过MAC地址向服务端发起连接。客户端和服务端以不同的方式获得BluetoothSocket
,当客户端和服务端在同一个RFCOMM通道上分别拥有已连接的BluetoothSocket
时,将他们视为彼此已经连接,于是每台设备都获得输入和输出流式传输,并开始传输数据。
连接技术
一种实现技术是自动将每台设备准备为一个服务器,从而使每台设备开放一个服务套接字并侦听连接,在此情况下,任何一台设备都可以发起与另一台设备的连接并称为客户端。
服务器
设置服务器套接字并接受连接,步骤依次如下
1、调用listenUsingRfcommWithServiceRecord()获取一个BluetoothServerSocket
, 该函数需要两个参数,第一个是服务器的名称,自己取一个即可,第二个是UUID,用来对信息做唯一性标识,我们可以从网上众多UUID生成器中随机的生成一个,然后使用UUID.fromString(String)初始化一个UUID。
2、通过accept()函数开始侦听连接请求
只有远程设备发送的连接请求中UUID与使用此套接字注册的UUID相匹配时服务器才会接受请求,accept函数会返回已连接的BluetoothSocket
3、连接成功后调用close()
关闭BluetoothSocket
- private class AcceptThread extends Thread{
-
- private final BluetoothServerSocket mmServerSocket;
- private String mSocketType;
-
- public AcceptThread(boolean secure){
- BluetoothServerSocket tmp = null;
- mSocketType = secure ? "secure" : "Insercure";
- try{
- if (secure){
- tmp = bluetoothAdapter.listenUsingRfcommWithServiceRecord(NAME_SECURE,MY_UUID_SECURE);
- }else{
- tmp = bluetoothAdapter.listenUsingRfcommWithServiceRecord(NAME_INSECURE,MY_UUID_INSECURE);
- }
- } catch (IOException e) {
- Log.e(TAG,"socket type"+ mSocketType + "listen() failed",e);
- }
-
- mmServerSocket = tmp;
- }
-
- @Override
- public void run() {
- Log.d(TAG, "Socket Type: " + mSocketType +
- "BEGIN mAcceptThread" + this);
- setName("AcceptThread"+ mSocketType);
-
- BluetoothSocket socket = null;
- Log.d(TAG, "run: 开始监听");
- while (true){
- try{
- socket = mmServerSocket.accept();
- Log.d("acceptThread", "run: 连接成功");
- connected(socket,socket.getRemoteDevice(),mSocketType);
- } catch (IOException e) {
- Log.e(TAG, "Socket Type: " + mSocketType + "accept() failed", e);
- break;
- }
-
- }
- Log.i(TAG, "END mAcceptThread, socket Type: " + mSocketType);
- }
-
- public void cancel() {
- Log.d(TAG, "Socket Type" + mSocketType + "cancel " + this);
- try {
- mmServerSocket.close();
- } catch (IOException e) {
- Log.e(TAG, "Socket Type" + mSocketType + "close() of server failed", e);
- }
- }
- }
上面的secure和Insecure只是使用了不同的UUID而已。
客户端
远程设备开启监听后,我们就发起向此设备的连接,首先必须先获得远程设备的BluetoothDevice对象,然后获取BluetoothSocket发起连接。
基本步骤如下
1、使用BluetoothDevice
通过调用createRfcommSocketToServiceRecord(UUID)
获取 BluetoothSocket
。
2、通过connect发起连接
- private class ConnectThread extends Thread{
- private final BluetoothSocket mmSocket;
- private final BluetoothDevice mmDevice;
- private String mSocketType;
-
- public ConnectThread(BluetoothDevice device, boolean secure){
- mmDevice = device;
- BluetoothSocket tmp = null;
- mSocketType = secure ? "Secure" : "Insecure";
- try {
- if (secure){
- tmp = device.createRfcommSocketToServiceRecord(MY_UUID_SECURE);
- }else {
- tmp = device.createRfcommSocketToServiceRecord(MY_UUID_INSECURE);
- }
- } catch (IOException e) {
- Log.e(TAG, "Socket Type: " + mSocketType + "create() failed", e);
- }
- mmSocket = tmp;
- }
-
- @Override
- public void run() {
- Log.i(TAG, "BEGIN mConnectThread SocketType:" + mSocketType);
- setName("ConnectThred"+mSocketType);
-
- // 总是取消发现,因为它会减慢连接
- bluetoothAdapter.cancelDiscovery();
- // connect
- // Make a connection to the BluetoothSocket
- try {
- // This is a blocking call and will only return on a
- // successful connection or an exception
- mmSocket.connect();
- Log.d(TAG, "run: socket连接成功");
- } catch (IOException e) {
- // Close the socket
- Log.d(TAG, "run: 关闭socket");
- try {
- mmSocket.close();
- } catch (IOException e2) {
- Log.e(TAG, "unable to close() " + mSocketType +
- " socket during connection failure", e2);
- }
-
- return;
- }
-
- connected(mmSocket,mmDevice,mSocketType);
- }
-
- public void cancel(){
- try{
- mmSocket.close();
- } catch (IOException e) {
- Log.e(TAG, "close() of connect " + mSocketType + " socket failed", e);
- }
- }
-
- }
发送数据
连接成功后,我们就可以通过socket发送数据了,客户端的Socket对象是BluetoothSocket
, 服务端的socket是BluetoothServerSocket
,特别注意不要混淆了。使用getInputStream
和getOutputStream
分别获取通过套接字处理数据传输的InputStream
和OutputStream
。写数据比较简单,但是读数据就需要一个单独的线程一直监听才行。
- private class ConnectedThread extends Thread{
- private final BluetoothSocket mmSocket;
- private InputStream mmInStream;
- private OutputStream mmOutStream;
-
- public ConnectedThread(BluetoothSocket socket, String socketType) throws IOException {
- Log.d(TAG, "create ConnectedThread: " + socketType);
- mmSocket = socket;
-
- InputStream tmpIn = null;
- OutputStream tmpOut = null;
-
- try{
- tmpIn = socket.getInputStream();
- tmpOut = socket.getOutputStream();
-
- if (socket != null){
- tmpOut.write(new String("hello").getBytes());
- Log.d(TAG, "ConnectedThread: socket不是null");
- }
- } catch (IOException e) {
- Log.e(TAG,"temp socket not created", e);
- }
-
- mmInStream = tmpIn;
- mmOutStream = tmpOut;
-
- // mmOutStream.write(new String("hello").getBytes());
- }
-
- @RequiresApi(api = Build.VERSION_CODES.KITKAT)
- @Override
- public void run() {
- Log.i(TAG, "BEGIN mConnectedThread");
- byte[] buffer = new byte[1024];
- int bytes;
-
- while (true){
- try{
- bytes = mmInStream.read(buffer);
- // send the bytes to the ui Activity
- String text = encodeByteToString(buffer,bytes);
- Log.d(TAG, "run: 收到消息:"+ text);
- chatItems.add(text);
- mHandler.sendMessage(mHandler.obtainMessage());
- } catch (IOException e) {
- Log.d(TAG, "run: 没有收到消息");
- e.printStackTrace();
- break;
- }
- }
- }
-
- public String encodeByteToString(byte[] data,int length) {
- byte[] temp = new byte[length];
- for (int i = 0; i < length; i++) {
- temp[i] = data[i];
- }
- try {
- return new String(temp,"utf-8");
- } catch (UnsupportedEncodingException e) {
- return "";
- }
- }
-
-
- public void write(byte[] buffer){
- try{
- mmOutStream.write(buffer);
-
- // mHandler.obtainMessage(Constants.MESSAGE_WRITE,-1,-1,buffer).sendToTarget();
- } catch (IOException e) {
- e.printStackTrace();
- }
- }
-
- public void cancel(){
- try{
- mmSocket.close();
- Log.d(TAG, "cancel: connectedThread");
- } catch (IOException e) {
- Log.e(TAG, "close() of connect socket failed", e);
- }
- }
- }
上面的例子我主要是学习官网上的蓝牙聊天项目写的代码,大家也可以直接看官网项目。从上面的例子中可知,接受到的数据流都是一些二进制,要用到实际的项目中还需要进行一定的编码和转换。也就是自己编写一些协议,学过socket编程的同学一定都懂,其实蓝牙已经有很多的好用的协议了,就比如AVRCP(Audio Video Remote Control Profile),定义了蓝牙设备和audio/video控制功能通信的特点和过程, 结合MediaSession 可以很容易的实现设备音视频控制。
到此这篇关于android蓝牙简单开发示例教程的文章就介绍到这了,更多相关android蓝牙开发内容请搜索w3xue以前的文章或继续浏览下面的相关文章希望大家以后多多支持w3xue!