Android经典蓝牙开发

前言

此外本文只涉及经典蓝牙(Classic Bluetooth)的点对点通信开发,并不涉及低功耗蓝牙(BLE)的开发。

开发流程

  • 设置蓝牙
  • 搜索附近的蓝牙设备
  • 配对连接
  • 通信

设置蓝牙

1.获取 BluetoothAdapter:

 mBluetoothAdapter = BluetoothAdapter.getDefaultAdapter();

2.开启蓝牙

  • 处理6.0以下版本的蓝牙权限

1.在AndroidManifest中添加权限:





2.开启蓝牙功能:

常量REQUEST_ENABLE_BT是本地定义的整型(需要大于0),当系统通过onActivityResult() 返回至你的应用程序时,将作为requestCode的参数。
如果成功开启了蓝牙,你的Activity将收到RESULT_OK作为resultCode。如果蓝牙不能成功开启(例如用户选择“取消”),则resultCode为RESULT_CANCELED

//1、获取BluetoothAdapter
mBluetoothAdapter = BluetoothAdapter.getDefaultAdapter();
//2、判断是否支持蓝牙,并弹窗要求打开蓝牙
if(mBluetoothAdapter == null ||!mBluetoothAdapter.isEnabled()){
     Intent enableBtIntent = new Intent(BluetoothAdapter.ACTION_REQUEST_ENABLE);
     startActivityForResult(enableBtIntent,REQUEST_ENABLE_BT);
}

3.对返回值进行处理

@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
     super.onActivityResult(requestCode, resultCode, data);
     if(requestCode == REQUEST_ENABLE_BT && resultCode == RESULT_OK){
           //已启用,进行下一步初始化工作
     }else if(requestCode == REQUEST_ENABLE_BT && resultCode == RESULT_CANCELED){
           //未启用,退出应用
           Toast.makeText(MainActivity.this,"请启用蓝牙",Toast.LENGTH_SHORT).show();  
           finish();  
     }
}
  • 处理6.0版本以上的蓝牙权限
    1.在AndroidManifest中添加一个模糊定位的权限:


2.校验蓝牙权限:

if (Build.VERSION.SDK_INT >= 23) {
      //校验是否已具有模糊定位权限
      if (ContextCompat.checkSelfPermission(context,
      Manifest.permission.ACCESS_COARSE_LOCATION)!= PackageManager.PERMISSION_GRANTED) {
            ActivityCompat.requestPermissions(context,
                  new String[]{Manifest.permission.ACCESS_COARSE_LOCATION},
                  REQUEST_ENABLE_BT
                  );
      } else {
            //具有权限
      }
} else {
      //系统不高于6.0执行下一步初始化
}

3.对返回值进行处理,类似于startActivityForResult方法:

@Override
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
      super.onRequestPermissionsResult(requestCode, permissions, grantResults);
      if (requestCode == REQUEST_ENABLE_BT) {
            if(grantResults[0] == PackageManager.PERMISSION_GRANTED) {
            //同意权限
            } else {
            // 权限拒绝
            }
      }
}

搜索附近的蓝牙设备

1.查询已配对的设备并加入列表

//将配过对的设备加入list
Set paireDevices = mBluetoothAdapter.getBondedDevices();
if(paireDevices.size()>0){
      for(BluetoothDevice device: paireDevices){
            adapter.addData(device); //adapter为列表的适配器
      }
}

2.发现设备
调用异步方法startDiscovery() 开始搜索蓝牙设备。

该进程为异步进程,并且该方法会立即返回一个布尔值,指示是否已成功启动发现操作。 发现进程通常包含约 12 秒钟的查询扫描,之后对每台发现的设备进行页面扫描,以检索其蓝牙名称。

当这个方法发现蓝牙设备时,将会广播ACTION_FOUNDIntent ,搜索到的设备信息EXTRA_DEVICE包含在此Intent中,因此注册一个BroadcastReceiver 来处理广播。

//新建一个IntentFilter
private IntentFilter getIntentFilter(){
      IntentFilter intentFilter = new IntentFilter();    
      intentFilter.addAction(BluetoothDevice.ACTION_FOUND);         
      intentFilter.addAction(BluetoothAdapter.ACTION_DISCOVERY_STARTED); 
      intentFilter.addAction(BluetoothAdapter.ACTION_DISCOVERY_FINISHED);
      return intentFilter;
}

//新建BroadcastReceiver
private final BroadcastReceiver receiver = new BroadcastReceiver() {
      @Override
      public void onReceive(Context context, Intent intent) {
            String action = intent.getAction();
            if(BluetoothDevice.ACTION_FOUND.equals(action)){
                  BluetoothDevice device=intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);            
                  if(!list.contains(device)){//去重
                        adapter.addData(device);
                  }      
            }else if(BluetoothAdapter.ACTION_DISCOVERY_FINISHED.equals(action)){                              
                  Toast.makeText(context,"扫描完毕",Toast.LENGTH_SHORT).show();
        }
    }
};

//在onCreate 中注册
@Override
protected void onCreate(Bundle savedInstanceState) {        
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        //注册
        registerReceiver(receiver,getIntentFilter());
}

不要忘记在onDestroy()中进行反注销

unregisterReceiver(receiver);

执行设备搜索的操作是一项很繁重的任务,会消耗大量的资源。一旦你找到了一个设备并要进行连接,请务必确认是否停止搜索设备的操作。如果已经进行了连接,那么搜索操作将会显著地降低连接的速率,因此你应当在连接时停止搜索。可通过cancelDiscovery()方法停止搜索。

配对连接

要在两台设备之间创建连接,其中一台设备要作为服务器端,保持开放的BluetoothServerSocket并在线程中调用 accept()开始侦听连接请求,
而另一台设备必须利用扫描得到的服务端MAC发起连接请求。

:如果两台设备之前尚未配对,则在连接过程中,Android 框架会自动向用户显示配对请求通知或对话框

  • 服务器端:
  1. 通过调用listenUsingRfcommWithServiceRecord(String, UUID)获取BluetoothServerSocket
  2. 通过在run()中调用accept(),开始监听连接请求。
    由于accept()为阻塞调用,所以需要一个专门的线程进行连接的操作。
//用于接收连接请求,并启动ConnectedThread
private class AcceptThread extends Thread {
      private final BluetoothServerSocket mmServerSocket;
      public AcceptThread() {
            BluetoothServerSocket tmp = null;
            try {
                  tmp = mBluetoothAdapter.listenUsingRfcommWithServiceRecord("YourAPPName",                        MY_UUID);
            } catch (IOException e) {
                  e.printStackTrace();
            }
            mmServerSocket = tmp;
    }    
public void run() {
        Log.d(TAG, "BEGIN mAcceptThread" + this);
        BluetoothSocket socket = null;
        // 在没有连接上的时候accept
        while (mState!=3) {
            try {
                socket = mmServerSocket.accept();
            } catch (IOException e) {
                Log.e(TAG, "accept() failed", e);
                break;
            }
            if (socket != null) {
                synchronized (MainActivity.this) {
                    switch (mState) {
                        case STATE_LISTEN:
                        case STATE_CONNECTING:
                            // 准备通信
                            connected(socket);
                            break;
                        case STATE_NONE:
                        case STATE_CONNECTED:
                            // Either not ready or already connected. Terminate new socket.
                            try {
                                socket.close();
                            } catch (IOException e) {
                                Log.e(TAG, "Could not close unwanted socket", e);
                            }
                            break;
                    }
                }
            }
        }
        Log.i(TAG, "END mAcceptThread");
    }
    public void cancel() {
        Log.d(TAG, "Socket cancel " + this);
        try {
            mmServerSocket.close();
        } catch (IOException e) {
            Log.e(TAG, "Socket close() of server failed", e);
        }
    }
}

mState为标记当前状态的变量,规定为

STATE_NONE = 0;       // 初始状态
STATE_LISTEN = 1;     // 等待连接
STATE_CONNECTING = 2; // 正在连接
STATE_CONNECTED = 3;  // 已经连接上设备
  • 客户端:
  1. 利用扫描到的服务器端的MAC地址得到远程设备
    BluetoothDevice btDev = mBluetoothAdapter.getRemoteDevice(macAddress);
  2. 该远程设备调用方法createRfcommSocketToServiceRecord(UUID)建立安全连接。

注:UUID定义为00001101-0000-1000-8000-00805F9B34FB,为手机蓝牙串口的统一UUID。

  1. run()中通过调用conenct建立连接
    由于connect()为阻塞调用,因此该连接过程应始终在主 Activity 线程以外的线程中执行。
//用于蓝牙连接
private class ConnectThread extends Thread {
        private final BluetoothSocket mmSocket;
        private final BluetoothDevice mmDevice;

        public ConnectThread(BluetoothDevice device) {
                    mmDevice = device;
                    BluetoothSocket tmp = null;
                    try {
                        //尝试建立安全的连接
                        tmp = mmDevice.createRfcommSocketToServiceRecord(MY_UUID);
                        //尝试建立不安全的连接
                        //tmp = mmDevice.createInsecureRfcommSocketToServiceRecord(MY_UUID);
                    } catch (IOException e) {
                        Log.i(TAG,"获取 BluetoothSocket失败");
                        e.printStackTrace();
                    }
                    mmSocket = tmp;
     }
    @Override
    public void run() {
        if(mBluetoothAdapter.isDiscovering()){        
            mBluetoothAdapter.cancelDiscovery();
        }
        try {
            mmSocket.connect();
        } catch (IOException e) {
            Log.i(TAG,"socket连接失败");
            //利用Handler传递消息
            Message msg = mHandler.obtainMessage(Constants.MESSAGE_TOAST);
            Bundle bundle = new Bundle();
            bundle.putString(Constants.TOAST,"Socket连接失败");
            msg.setData(bundle);
            mHandler.sendMessage(msg);
            return;
        } 
        synchronized (MainActivity.this){
            mConnectThread = null;
        }
        //调用外部类方法,启动用于通信线程connectedThread
        connected(mmSocket);
    }

    public void cancel(){
        try {
            mmSocket.close();
            setState(false);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

注:connected(mmSocket)是应用中的虚构方法,它将启动用于传输数据的线程。

//连接完成后启动ConnectedThread
public synchronized void connected(BluetoothSocket socket){
    if (mConnectThread != null) {
        mConnectThread.cancel();
        mConnectThread = null;
    }
    if (mConnectedThread != null) {
        mConnectedThread.cancel();
        mConnectedThread = null;
    }
    setState(STATE_CONNECTED);
    mConnectedThread = new ConnectedThread(socket);
    mConnectedThread.start();
}

通信

在成功连接两台设备后,每台设备都会有一个已连接的 BluetoothSocket。利用 BluetoothSocket传输任意数据的一般过程非常简单:

  1. 获取 InputStreamOutputStream,二者分别通过套接字
    以及 getInputStream()getOutputStream()来处理数据传输。
  2. 使用 read(byte[])write(byte[])读取数据并写入到流式传输。

因为read(byte[]) 和 write(byte[])方法都是阻塞调用的,所以需要一个专门的线程进行读写的操作。

//蓝牙连接完成后进行输入输出
private class ConnectedThread extends Thread{
      private final BluetoothSocket mmSocket;
      private final InputStream mmInStream;
      private final OutputStream mmOutStream;
      public ConnectedThread(BluetoothSocket socket) {
            mmSocket = socket;
            InputStream tmpIn = null;
            OutputStream tmpOut = null;
            try {
                  tmpIn = socket.getInputStream();
                  tmpOut = socket.getOutputStream();
            } catch (IOException e) {
                  Log.e(TAG, "temp sockets not created", e);
            }
            mmInStream = tmpIn;
            mmOutStream = tmpOut;
      }    
      public void run() {
            Log.i(TAG, "BEGIN mConnectedThread");
            //当连接状态为连接时,循环读取
            while(mState == STATE_CONNECTED){
                  try {
                        // 从InputStream中读取
                        Scanner in = new Scanner(mmInStream,"UTF-8");
                        String str = in.nextLine();
                        Log.i(TAG,"read: "+str);
                        //利用handle传递数据,此时为Toast模式
                        Message msg = mHandler.obtainMessage(Constants.MESSAGE_TOAST);
                        Bundle bundle = new Bundle();
                        bundle.putString(Constants.TOAST,str);
                        msg.setData(bundle);
                        mHandler.sendMessage(msg);
                  } catch (Exception e) {
                        Log.e(TAG, "disconnected", e);
                  }
           }
      }
      public void write(byte[] buffer) {
            try {
                  mmOutStream.write(buffer);
            } catch (IOException e) {
                  e.printStackTrace();
                  Log.e(TAG, "Exception during write", e);
            }
      }
      public void cancel() {
            try {
                  mmSocket.close();
            } catch (IOException e) {
                  Log.e(TAG, "close() of connect socket failed", e);
            }
      }
}
  • 调用mConnectedThread.write(byte[] buffer)进行输出。
  • 输入流则在run()中被循环读取,这里采用了Handler处理数据传递。
    Constants的常量代表了对Message不同的处理方式,在ConnectedThreadrun()中,使用不同的 Constants值,调整输入流的处理方式。

例子中使用了
mHandler.obtainMessage(Constants.MESSAGE_TOAST);
代表把得到的数据以MESSAGE_TOAST的方式处理。

//利用Handler传递数据
private final Handler mHandler = new Handler() {
    @Override
    public void handleMessage(Message msg) {
          switch(msg.what){
                case: Constants.SomeConfig:
                // do something
                break;
          }
    }
};

Demo的GitHub链接:https://github.com/YangLuYang/android-Demo-ClassicBluetooth

你可能感兴趣的:(Android经典蓝牙开发)