飞道的博客

Android蓝牙开发

315人阅读  评论(0)

前言

这是我大二做的一个智能小车配套使用的APP,用Android的蓝牙接口实现,当时有些os相关的内容Thread之类还有一些Android接口、java语法,我其实不是很理解。学了操作系统,再来回顾一下,并整理项目代码,项目具有很高的复用性,特别是蓝牙部分。

reference
项目参考了稚晖君的开源项目 https://github.com/peng-zhihui/BluetoothTouch
Android蓝牙开发官方文档 https://developer.android.google.cn/guide/topics/connectivity/bluetooth

开启蓝牙权限

为APP开启蓝牙权限,将以下config添加到项目配置文件AndroidManifest.xml中:

<manifest>
    <!-- Request legacy Bluetooth permissions on older devices. -->
    <uses-permission android:name="android.permission.BLUETOOTH"
                     android:maxSdkVersion="30" />
    <uses-permission android:name="android.permission.BLUETOOTH_ADMIN"
                     android:maxSdkVersion="30" />

    <!-- Needed only if your app looks for Bluetooth devices.
         If your app doesn't use Bluetooth scan results to derive physical
         location information, you can strongly assert that your app
         doesn't derive physical location. -->
    <uses-permission android:name="android.permission.BLUETOOTH_SCAN" />

    <!-- Needed only if your app makes the device discoverable to Bluetooth
         devices. -->
    <uses-permission android:name="android.permission.BLUETOOTH_ADVERTISE" />

    <!-- Needed only if your app communicates with already-paired Bluetooth
         devices. -->
    <uses-permission android:name="android.permission.BLUETOOTH_CONNECT" />

    <!-- Needed only if your app uses Bluetooth scan results to derive physical location. -->
    <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
    ...
</manifest>

 

设置蓝牙

这个在MainActivity中设置,分为两步:

  1. 获取BluetoothAdapter,这是所有蓝牙活动的基础;
protected void onCreate(Bundle savedInstanceState)
{
   
    super.onCreate(savedInstanceState);
    // Get local Bluetooth adapter
    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR2)
        mBluetoothAdapter = ((BluetoothManager) getSystemService(Context.BLUETOOTH_SERVICE)).getAdapter();
    else
        mBluetoothAdapter = BluetoothAdapter.getDefaultAdapter();

    // If the adapter is null, then Bluetooth is not supported
    if (mBluetoothAdapter == null) {
   
        showToast("Bluetooth is not available", Toast.LENGTH_LONG);
        finish();
        return;
    }
}

 
  1. 使能蓝牙,首先确定蓝牙是否启用,若未启用,调用startActivityForResult,传递ACTION_REQUEST_ENABLE目标动作,这些 大写变量 实际是Android定义好的字符串;若以启用,传入BluetoothHandlerBluetoothAdapter,实例化我们自定义的蓝牙服务类BluetoothChatService(后面慢慢介绍,实际就是蓝牙接口封装起来的一个类);
public void onStart()
{
   
	super.onStart();
    // If BT is not on, request that it be enabled.
    // setupChat() will then be called during onActivityResult
    if (!mBluetoothAdapter.isEnabled()) {
   
        Intent enableIntent = new Intent(BluetoothAdapter.ACTION_REQUEST_ENABLE);
        startActivityForResult(enableIntent, REQUEST_ENABLE_BT);
    } else
        setupBTService(); // Otherwise, setup the chat session
}

private void setupBTService()
{
   
    if (mChatService != null)
        return;
    if (mBluetoothHandler == null)
        mBluetoothHandler = new BluetoothHandler(this);
    mChatService = new BluetoothChatService(mBluetoothHandler, mBluetoothAdapter); // Initialize the BluetoothChatService to perform Bluetooth connections
}

 

连接设备

通过retry判断是否断线重连,否则创建新连接,像http协议有 ipv4ipv6 地址,以太网协议有 MAC 地址,蓝牙也有地址,在蓝牙构建的网络中标识设备或者蓝牙对象。

private void connectDevice(Intent data, boolean retry)
{
   
    if (retry) {
   
        if (btDevice != null && !stopRetrying) {
   
            mChatService.start(); // This will stop all the running threads
            mChatService.connect(btDevice, btSecure); // Attempt to connect to the device
        }
    } else {
    // It's a new connection
        stopRetrying = false;
        mChatService.newConnection = true;
        mChatService.start(); // This will stop all the running threads
        if (data.getExtras() == null)
            return;

        // 获取设备蓝牙地址并连接设备
        String address = data.getExtras().getString(DeviceListActivity.EXTRA_DEVICE_ADDRESS); // Get the device Bluetooth address
        btSecure = data.getExtras().getBoolean(DeviceListActivity.EXTRA_NEW_DEVICE); // If it's a new device we will pair with the device
        btDevice = mBluetoothAdapter.getRemoteDevice(address); // Get the BluetoothDevice object

        mChatService.nRetries = 0; // Reset retry counter
        mChatService.connect(btDevice, btSecure); // Attempt to connect to the device
        showToast(getString(R.string.connecting), Toast.LENGTH_SHORT);
    }
}

 

BluetoothChatService.connect

connect方法的定义如下,这里有mConnectThreadmConnectedThread两个用于连接和保持连接的线程,先cancel之前的线程,再重新连接start,这里synchronized关键字是声明了线程的同步锁(坑,详细解释)。

public synchronized void connect(BluetoothDevice device, boolean secure)
{
   
    stopReading = true;
    
    // Cancel any thread attempting to make a connection
    if (mConnectThread != null) {
   
        mConnectThread.cancel();
        mConnectThread = null;
    }

    // Cancel any thread currently running a connection
    if (mConnectedThread != null) {
   
        mConnectedThread.cancel();
        mConnectedThread = null;
    }

    // Start the thread to connect with the given device
    mConnectThread = new ConnectThread(device, secure);
    mConnectThread.start();
    setState(STATE_CONNECTING);
}

 

BluetoothChatService.ConnectThread

这里final关键字修饰变量在初始化后不可更改。这里BluetoothSocket是用于收发数据的工具,在网络通信的编程开发中很常见。这里仅实现了作为客户端进行连接(坑,补充作为服务端等待连接的线程实现)。基本连接流程如下:

  1. 使用BluetoothDevice调用createRfcommSocketToServiceRecord(UUID)获取蓝牙套接字BluetoothSocket ,这个UUID是由服务端和客户端共同协商的,RFCOMM就是蓝牙通信所使用的的信道;
  2. 阻塞调用socketconnect()方法,系统执行SDP查找具有指定UUID的设备,若查找成功并且对方设备接受连接,开始蓝牙通信,共享RFCOMM信道;若超时则抛出一个IOException
  3. 蓝牙连接完毕,之后调用BluetoothChatService.connected(类似BluetoothChatService.connect),用于启动保持连接线程(或者说通信线程)BluetoothChatService.ConnectedThread.
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";

        // Get a BluetoothSocket for a connection with the
        // given BluetoothDevice
        try {
   
            if (secure)
                tmp = mmDevice.createRfcommSocketToServiceRecord(UUID_RFCOMM_GENERIC);
            else
                tmp = mmDevice.createInsecureRfcommSocketToServiceRecord(UUID_RFCOMM_GENERIC);
        } catch (IOException e) {
   
            if (D)
                Log.e(TAG, "Socket Type: " + mSocketType + "create() failed", e);
        }
        mmSocket = tmp;
    }

    public void run()
    {
   
        // Always cancel discovery because it will slow down a connection
        mAdapter.cancelDiscovery();
        newConnection = false;

        // 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();
        } catch (IOException e) {
   
            // Close the socket
            try {
   
                mmSocket.close();
            } catch (IOException e2) {
   
                if (D)
                    Log.e(TAG, "unable to close() " + mSocketType
                            + " socket during connection failure", e2);
            }
            if (!newConnection)
                connectionFailed();
            return;
        }

        // Reset the ConnectThread because we're done
        synchronized (BluetoothChatService.this) {
   
            mConnectThread = null;
        }

        // Start the connected thread
        connected(mmSocket, mmDevice, mSocketType);
    }

    public void cancel()
    {
   
        try {
   
            mmSocket.close();
        } catch (IOException e) {
   
            if (D)
                Log.e(TAG, "close() of connect " + mSocketType
                        + " socket failed", e);
        }
    }
}

 

传递数据

BluetoothChatService.ConnectedThread

成功连接蓝牙设备后,双方均有一个BluetoothSocket,这时可以分享信息,数据传递流程如下:

  1. 通过BluetoothSocket获取I/O对象InputStreamOutputStream;
  2. 通过read(byte[])write(byte[])进行以字节为单位数据读写,实际上读要不断进行,因此在线程的run()方法中实现,这里读到后直接解析了,更好地做法应该定义一个parser类负责数据的解析(坑)。
private class ConnectedThread extends Thread
{
   
    private final BluetoothSocket mmSocket;
    private final InputStream mmInStream;
    private final OutputStream mmOutStream;

    public ConnectedThread(BluetoothSocket socket, String socketType)
    {
   
        if (D)
            Log.d(TAG, "create ConnectedThread: " + socketType);
        mmSocket = socket;
        InputStream tmpIn = null;
        OutputStream tmpOut = null;

        // Get the BluetoothSocket input and output streams
        try {
   
            tmpIn = socket.getInputStream();
            tmpOut = socket.getOutputStream();
        } catch (IOException e) {
   
            if (D)
                Log.e(TAG, "temp sockets not created", e);
        }

        mmInStream = tmpIn;
        mmOutStream = tmpOut;
        stopReading = false;
    }

    public void run()
    {
   
        if (D)
            Log.i(TAG, "BEGIN mConnectedThread");
        byte[] buffer = new byte[1024];
        int bytes;

        // Keep listening to the InputStream while connected
        while (!stopReading) {
   
            try {
   
                if (mmInStream.available() > 0) {
    // Check if new data is available
                    bytes = mmInStream.read(buffer); // Read from the InputStream

                    /***************  解释器  **************/

                    String readMessage = new String(buffer, 0, bytes);
                    String[] splitMessage = readMessage.split(",");

                    if (D) {
   
                        Log.i(TAG, "Received string: " + readMessage);
                        for (int i = 0; i < splitMessage.length; i++)
                            Log.i(TAG, "splitMessage[" + i + "]: " + splitMessage[i]);
                    }

                   // 命令解析...
                }
            } catch (IOException e) {
   
                if (D)
                    Log.e(TAG, "disconnected", e);
                if (!stopReading) {
   
                    cancel();
                    connectionLost();
                }
                return;
            }
        }
    }

    /**
     * Write to the connected OutStream.
     *
     * @param buffer The bytes to write
     */
    public void write(byte[] buffer)
    {
   
        try {
   
            mmOutStream.write(buffer);
        } catch (IOException e) {
   
            if (D)
                Log.e(TAG, "Exception during write", e);
        }
    }

    public void cancel()
    {
   
        stopReading = true;

        if (mmInStream != null) {
   
            try {
   
                mmInStream.close();
            } catch (Exception ignored) {
   
            }
        }
        if (mmOutStream != null) {
   
            try {
   
                mmOutStream.close();
            } catch (Exception ignored) {
   
            }
        }
        if (mmSocket != null) {
   
            try {
   
                mmSocket.close();
            } catch (Exception ignored) {
   
            }
        }
    }
}

 

查找设备

最后补充一下,设备之间是如何相互发现的,这个就是我们使用蓝牙耳机,通过功能键打开耳机的可发现模式,通过系统设置去匹配、连接耳机的过程。如果设备已经匹配可以在“我的设备”(苹果手机)中看见设备信息,但显示“未连接”意味着没有建立RFCOMM共享信道,不可通信,而“匹配”意味着已经交换了建立连接所必要的信息。

通过BluetoothAdapter可以搜索蓝牙设备或者查询已配对设备列表,已匹配,就是双方协商好了连接使用的UUID,蓝牙MAC地址等等信息。

通过getBondedDevices()查询已匹配设备。

Set<BluetoothDevice> pairedDevices = bluetoothAdapter.getBondedDevices();

if (pairedDevices.size() > 0) {
   
   // There are paired devices. Get the name and address of each paired device.
   for (BluetoothDevice device : pairedDevices) {
   
       String deviceName = device.getName();
       String deviceHardwareAddress = device.getAddress(); // MAC address
   }
}

通过startDiscovery()开始搜索蓝牙设备,为了接收对方设备的反馈,必须基于ACTION_FOUNDintent注册一个BroadcastReceiver

@Override
protected void onCreate(Bundle savedInstanceState) {
   
   ...

   // Register for broadcasts when a device is discovered.
   IntentFilter filter = new IntentFilter(BluetoothDevice.ACTION_FOUND);
   registerReceiver(receiver, filter);
}

// Create a BroadcastReceiver for ACTION_FOUND.
private final BroadcastReceiver receiver = new BroadcastReceiver() {
   
   public void onReceive(Context context, Intent intent) {
   
       String action = intent.getAction();
       if (BluetoothDevice.ACTION_FOUND.equals(action)) {
   
           // Discovery has found a device. Get the BluetoothDevice
           // object and its info from the Intent.
           BluetoothDevice device = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);
           String deviceName = device.getName();
           String deviceHardwareAddress = device.getAddress(); // MAC address
       }
   }
};

@Override
protected void onDestroy() {
   
   super.onDestroy();
   ...

   // Don't forget to unregister the ACTION_FOUND receiver.
   unregisterReceiver(receiver);
}

 

调用startActivityForResult(Intent,int)使设备处于可发现状态,持续两分钟,用于服务端,这个隐含了搜索要求服务客户双方你情我愿的。

int requestCode = 1;
Intent discoverableIntent =
       new Intent(BluetoothAdapter.ACTION_REQUEST_DISCOVERABLE);
discoverableIntent.putExtra(BluetoothAdapter.EXTRA_DISCOVERABLE_DURATION, 300);
startActivityForResult(discoverableIntent, requestCode);

转载:https://blog.csdn.net/qq_23096319/article/details/128741736
查看评论
* 以上用户言论只代表其个人观点,不代表本网站的观点或立场