Android 蓝牙开发——蓝牙连接(六)

阿里云国内75折 回扣 微信号:monov8
阿里云国际,腾讯云国际,低至75折。AWS 93折 免费开户实名账号 代冲值 优惠多多 微信号:monov8 飞机:@monov6

一、APP端调用

//Android的SSP协议栈默认的UUID
private String SPP_UUID = "00001101-0000-1000-8000-00805F9B34FB";
 
private BluetoothSocket btSocket;
 
public void btConnect(BluetoothDevice device){
    // 耗时操作放到子线程
    new Thread(new Runnable() {
        @Override
        public void run() {
            try {
                btSocket = device.createRfcommSocketToServiceRecord(UUID.fromString(SPP_UUID));
                // 停止扫描
                if (btAdapter.isDiscovering()) {
                    btAdapter.cancelDiscovery();
                }
                if (!btSocket.isConnected()) {
                    btSocket.connect();
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }).start();
}

        目前上面的代码在执行连接时会出现 “java.io.IOException: read failed, socket might closed or timeout, read ret: -1” 异常网上有的说在嵌套一层子线程还有说异常就重连三次就会成功。但都不好用也还不清楚是什么原因。

二、连接源码解析

1、连接方法

        Android官方API给出的经典蓝牙连接方法有2个

createRfcommSocketToServiceRecord
        安全连接。建立连接时如果没有建立过配对关系那么连接时会先去建立配对关系然后再执行连接如果与蓝牙设备已建立了配对关系那么就会直接执行连接。

createInsecureRfcommSocketToServiceRecord
        非安全连接。建立连接时如果没有建立过配对关系就会跳过配对过程直接执行连接。

public BluetoothSocket createRfcommSocketToServiceRecord(UUID uuid) throws IOException {
    if (!isBluetoothEnabled()) {
        Log.e(TAG, "Bluetooth is not enabled");
        throw new IOException();
    }
 
    return new BluetoothSocket(BluetoothSocket.TYPE_RFCOMM, -1, true, true, this, -1, new ParcelUuid(uuid));
}
public BluetoothSocket createInsecureRfcommSocketToServiceRecord(UUID uuid) throws IOException {
    if (!isBluetoothEnabled()) {
        Log.e(TAG, "Bluetooth is not enabled");
        throw new IOException();
    }
    return new BluetoothSocket(BluetoothSocket.TYPE_RFCOMM, -1, false, false, this, -1, new ParcelUuid(uuid));
}

        两个函数最后都是创建 BluetoothSocket只是传入参数不同。

2、BluetoothSocket.connect()

源码位置/frameworks/base/core/java/android/bluetooth/BluetoothSocket.java

        执行这个方法的时候, 不能与蓝牙扫描同时进行, 在执行该方法前, 要先停止蓝牙扫描, 调用BluetoothAdapter 的 cancelDiscovery()方法。

public void connect() throws IOException {
    if (mDevice == null) throw new IOException("Connect is called on null device");
 
    try {
        if (mSocketState == SocketState.CLOSED) throw new IOException("socket closed");
        IBluetooth bluetoothProxy = BluetoothAdapter.getDefaultAdapter().getBluetoothService();
        if (bluetoothProxy == null) throw new IOException("Bluetooth is off");
        //获取ParcelFileDescriptor,即FileDescriptor的Android包装类
        mPfd = bluetoothProxy.getSocketManager().connectSocket(mDevice, mType, mUuid, mPort, getSecurityFlags());
        synchronized (this) {
            if (DBG) Log.d(TAG, "connect(), SocketState: " + mSocketState + ", mPfd: " + mPfd);
            if (mSocketState == SocketState.CLOSED) throw new IOException("socket closed");
            if (mPfd == null) throw new IOException("bt socket connect failed");
            //把ParcelFileDescriptor转化为fd
            FileDescriptor fd = mPfd.getFileDescriptor();
            //建立Socket连接准备
            mSocket = LocalSocket.createConnectedLocalSocket(fd);
            mSocketIS = mSocket.getInputStream();
            mSocketOS = mSocket.getOutputStream();
        }
        int channel = readInt(mSocketIS);
        if (channel <= 0) {
            throw new IOException("bt socket connect failed");
        }
        mPort = channel;
        //等待连接。这里代表connect是堵塞的方法
        waitSocketSignal(mSocketIS);
        synchronized (this) {
            if (mSocketState == SocketState.CLOSED) {
                throw new IOException("bt socket closed");
            }
            mSocketState = SocketState.CONNECTED;
        }
    } catch (RemoteException e) {
        Log.e(TAG, Log.getStackTraceString(new Throwable()));
        throw new IOException("unable to send RPC: " + e.getMessage());
    }
}
 
/**
 * 等待连接
 */
private String waitSocketSignal(InputStream is) throws IOException {
    byte [] sig = new byte[SOCK_SIGNAL_SIZE];
    int ret = readAll(is, sig);//等待连接的关键方法
    ······
    return RemoteAddr;
}
 
private int readAll(InputStream is, byte[] b) throws IOException {
    int left = b.length;
    while(left > 0) {
        int ret = is.read(b, b.length - left, left);//inputstream.read就会阻塞此方法为常见方法
        if(ret <= 0) {
            throw new IOException("read failed, socket might closed or timeout, read ret: " + ret);
        }
        left -= ret;
        if(left != 0) {
            Log.w(TAG, "readAll() looping, read partial size: " + (b.length - left) + ", expect size: " + b.length);
        }
    }
    return b.length;
}

        查看上面代码 mPfd 通过 IBluetooth 获取的所以这里有是调用了 AdapterService 里的方法。

3、AdapterService.getSocketManager()

public IBluetoothSocketManager getSocketManager() {
    AdapterService service = getService();
    if (service == null) {
        return null;
    }
 
    return IBluetoothSocketManager.Stub.asInterface(service.mBluetoothSocketManagerBinder);
}

4、BluetoothSocketManagerBinder.connectSocket()

BluetoothDevice device, int type, ParcelUuid uuid, int port, int flag) {
    enforceActiveUser();
    if (!Utils.checkConnectPermissionForPreflight(mService)) {
        return null;
    }
    return marshalFd(mService.connectSocketNative(Utils.getBytesFromAddress(device.getAddress()), type, Utils.uuidToByteArray(uuid), port, flag, Binder.getCallingUid()));
}

        最后又回到  AdapterService.connectSocketNative()这里开始调用 Native 层代码。 

5、connectSocketNative()

源码位置/packages/apps/Bluetooth/jni/com_android_bluetooth_btservice_AdapterService.cpp

static jint connectSocketNative(JNIEnv* env, jobject obj, jbyteArray address, jint type, jbyteArray uuid, jint port, jint flag, jint callingUid) {
    ......
    btUuid = Uuid::From128BitBE((uint8_t*)uuidBytes);
    if (sBluetoothSocketInterface->connect((RawAddress*)addr, (btsock_type_t)type, &btUuid, port, &socket_fd, flag, callingUid) != BT_STATUS_SUCCESS) {
          socket_fd = INVALID_FD;
    }
    done:
        if (addr) env->ReleaseByteArrayElements(address, addr, 0);
        if (uuidBytes) env->ReleaseByteArrayElements(uuid, uuidBytes, 0);
    return socket_fd; //connect函数将socket_fd传进去并被赋值
}

        这里 sBluetoothSocketInterface -> connect 其实就是进入了 HAL 层。

6、connect() 分析

源码位置/system/bt/btif/src/btif_sock.cc

        经过 JNI 调用 sBluetoothSocketInterface -> sBluetoothInterface -> get_profile_interface() -> btif_sock_get_interface()

const btsock_interface_t* btif_sock_get_interface(void) {
    static btsock_interface_t interface = {
        sizeof(interface), btsock_listen, /* listen */
        btsock_connect,                   /* connect */
        btsock_request_max_tx_data_length /* request_max_tx_data_length */
    };
 
    return &interface;
}

        这里将 connect 对应到 btsock_connect。

static bt_status_t btsock_connect(const RawAddress* bd_addr, btsock_type_t type, const Uuid* uuid, int channel, int* sock_fd, int flags, int app_uid) {
  ···
  bt_status_t status = BT_STATUS_FAIL;
  switch (type) {
    case BTSOCK_RFCOMM://选择此通道
      status = btsock_rfc_connect(bd_addr, uuid, channel, sock_fd, flags, app_uid);
      break;
    ···
  }
  return status;
}

7、btsock_rfc_connect()

源码位置/system/bt/btif/src/btif_sock_rfc.cc

bt_status_t btsock_rfc_connect(const RawAddress* bd_addr, const Uuid* service_uuid, int channel, int* sock_fd, int flags, int app_uid) {
    ······
    *sock_fd = slot->app_fd;    // Transfer ownership of fd to caller.
    slot->app_fd = INVALID_FD;  // Drop our reference to the fd.
    slot->app_uid = app_uid;
    //发数据发送出去
    btsock_thread_add_fd(pth, slot->fd, BTSOCK_RFCOMM, SOCK_THREAD_FD_RD, slot->id);
 
    return BT_STATUS_SUCCESS;
}

8、btsock_thread_add_fd()

源码位置/system/bt/btif/src/btif_sock_thread.cc

btif_sock_thread.c 发送数据
int btsock_thread_add_fd(int h, int fd, int type, int flags, uint32_t user_id) {
    ···
    OSI_NO_INTR(ret = send(ts[h].cmd_fdw, &cmd, sizeof(cmd), 0));//socket发送数据
 
    return ret == sizeof(cmd);
}

阿里云国内75折 回扣 微信号:monov8
阿里云国际,腾讯云国际,低至75折。AWS 93折 免费开户实名账号 代冲值 优惠多多 微信号:monov8 飞机:@monov6
标签: android