美文网首页
Android Android-BluetoothKit笔记

Android Android-BluetoothKit笔记

作者: silencefun | 来源:发表于2020-02-03 17:47 被阅读0次

    主要记录操作蓝牙遇到的问题
    使用框架:
    github:https://github.com/dingjikerbo/Android-BluetoothKit

    蓝牙基本操作什么的都不多说了,该框架优点很多很多人做过功课笔记怎么使用,

    动态代理来实现

    代码解读参见 BluetoothKit源码解读

    这里主要记录一些坑

    前提确认过给过所有权限,比如定位wifi,Android 6.0及以上 蓝牙搜索需要定位权限。

    注意:查证发现,直接down该项目主页library作为module引用和使用gradle引用的某些类的代码不同步。

    0x00 扫不到经典蓝牙

    解决:
    构建SearchRequest 要把查找经典蓝牙条件加进去。


    image.png
    SearchRequest request = new SearchRequest.Builder()
        .searchBluetoothLeDevice(3000, 3)   // 先扫BLE设备3次,每次3s
        .searchBluetoothClassicDevice(5000) // 再扫经典蓝牙5s
        .searchBluetoothLeDevice(2000)      // 再扫BLE设备2s
        .build();
    

    0x01 添加经典蓝牙系统的所有配对过的设备显示出来

    解决办法:自己做过滤处理。

    直接返回的手机配对过的经典蓝牙设备的信号量都为0.

      private List<SearchResult> mDevices = new ArrayList<>(); //搜索到的device list
         
      private final SearchResponse mSearchResponse = new SearchResponse() {
        @Override
        public void onSearchStarted() {
             LogUtil.e("onSearchStarted");
             mDevices.clear();
        }
    
        @Override
        public void onDeviceFounded(SearchResult device) {
            if (device.rssi != 0 && device.getName() != null && !device.getName().toUpperCase().equals("NULL") && !mDevices.contains(device)) {
           mDevices.add(device);
            }
    
    
        }
    
        @Override
        public void onSearchStopped() {
            LogUtil.e("onSearchStopped");
        }
    
        @Override
        public void onSearchCanceled() {
            LogUtil.e("onSearchCanceled");
        }
    };
    

    过滤条件如代码,其中 device.getName().toUpperCase().equals("NULL")
    是因为 后来查看 源码中是强制给name 为null 情况下 赋值NULL。

    参见 {@link com.inuker.bluetooth.library.search.SearchResult#getName() }
        
       public String getName() {
        String name = device.getName();
        return TextUtils.isEmpty(name) ? "NULL" : name;
       }
    

    0x02 BluetoothKit 连接不上经典蓝牙或者一直没反应

    这个问题困扰很久,此框架作者强调了主要给BLE设备用的,多次查找debug最终定位到位置,参见

    * {@link com.inuker.bluetooth.library.connect.BleConnectWorker#openGatt() }
    
       @Override
    public boolean openGatt() {
        checkRuntime();
    
        BluetoothLog.v(String.format("openGatt for %s", getAddress()));
    
        if (mBluetoothGatt != null) {
            BluetoothLog.e(String.format("Previous gatt not closed"));
            return true;
        }
    
        Context context = BluetoothUtils.getContext();
        BluetoothGattCallback callback = new BluetoothGattResponse(mBluetoothGattResponse);
    
        if (Version.isMarshmallow()) {
            mBluetoothGatt = mBluetoothDevice.connectGatt(context, false, callback, BluetoothDevice.TRANSPORT_LE);
        } else {
            mBluetoothGatt = mBluetoothDevice.connectGatt(context, false, callback);
        }
    
        if (mBluetoothGatt == null) {
            BluetoothLog.e(String.format("openGatt failed: connectGatt return null!"));
            return false;
        }
    
        return true;
    }
    

    其中 Version.isMarshmallow()方法是做真值判断:

      public static boolean isMarshmallow() {
        return Build.VERSION.SDK_INT >= Build.VERSION_CODES.M;
    }
    

    现在的Android 设备6.0 Android M之前的 几乎很少不存在了,所以,问题出在

    mBluetoothGatt = mBluetoothDevice.connectGatt(context, false, callback, BluetoothDevice.TRANSPORT_LE);
    

    打开源码(Android 9.0)看,

     /**
     * Connect to GATT Server hosted by this device. Caller acts as GATT client.
     * The callback is used to deliver results to Caller, such as connection status as well
     * as any further GATT client operations.
     * The method returns a BluetoothGatt instance. You can use BluetoothGatt to conduct
     * GATT client operations.
     *
     * @param callback GATT callback handler that will receive asynchronous callbacks.
     * @param autoConnect Whether to directly connect to the remote device (false) or to
     * automatically connect as soon as the remote device becomes available (true).
     * @param transport preferred transport for GATT connections to remote dual-mode devices {@link
     * BluetoothDevice#TRANSPORT_AUTO} or {@link BluetoothDevice#TRANSPORT_BREDR} or {@link
     * BluetoothDevice#TRANSPORT_LE}
     * @throws IllegalArgumentException if callback is null
     */
    public BluetoothGatt connectGatt(Context context, boolean autoConnect,
            BluetoothGattCallback callback, int transport) {
        return (connectGatt(context, autoConnect, callback, transport, PHY_LE_1M_MASK));
    }
    

    transport 的参数可以为:

     /**
     * No preference of physical transport for GATT connections to remote dual-mode devices
     */
    public static final int TRANSPORT_AUTO = 0;
    
    /**
     * Prefer BR/EDR transport for GATT connections to remote dual-mode devices
     */
    public static final int TRANSPORT_BREDR = 1;
    
    /**
     * Prefer LE transport for GATT connections to remote dual-mode devices
     */
    public static final int TRANSPORT_LE = 2;
    

    可以看出来transport作为连接偏好,框架在系统OS版本是6.0及以后为采用TRANSPORT_LE,所以修改源码中连接方式,全部采用BluetoothDevice.TRANSPORT_AUTO 可以连接经典蓝牙 ,
    缺点/不足:实测 连接经典蓝牙相对耗时或者说慢了一点。

    0x03 搜索蓝牙无任何数据,立即结束。

    仔细查看会有错误输出:

    BtGatt.GattService: App 'xxx' is scanning too frequently
    

    排除其他的可能权限设备等,问题原因 搜索太频繁。
    Android 7.0 及以上 底层对搜索做了限制,30秒内最多搜索5次,频繁搜索底层不响应并报Error Log。

    解决办法,控制搜索五次的总时长 大于30秒就好,

    0x04 关于service和character的操作建议

    当连接成功后会拿到所有的service,然后根据厂商给定的可操作service和character去进行读写操作。

    这里建议service不要缓存,UUID不变,但是这些service都会和gatt关联的,如果gatt变了,那service就没有用,直接影响service和character做任何读写操作都会出错。
    所以建议每次连接上时都去discover service,然后重新去比对校验自己需要操作的service和character,

    这里实践出连接同一个设备在不同页面都需要进行操作的思路:

    每次连接都重新保存自己需要操作的service和character,然后根据单例对象需要操作的时候去操作,

     /**
    * 自定义管理对象 继承 BluetoothClient
    */
    public class NewBluetoothClient extends BluetoothClient {
    
    private BleGattProfile bleGattProfile;//已连接的BleGattProfile 信息
    
    public BleGattProfile getBleGattProfile() {
        return bleGattProfile;
    }
    
    public void setBleGattProfile(BleGattProfile bleGattProfile) {
        this.bleGattProfile = bleGattProfile;
    }
    
    public NewBluetoothClient(Context context) {
        super(context);
    }
    

    然后实现一个 单例

    public class ClientManager {
    
    private static NewBluetoothClient mClient;
    
    public static NewBluetoothClient getClient() {
        if (mClient == null) {
            synchronized (ClientManager.class) {
                if (mClient == null) {
                    mClient = new NewBluetoothClient(WexInit.getContext());
                }
            }
        }
        return mClient;
    }
    }
    

    在每次连接成功的时候保存所有的service

      BleConnectOptions options = new BleConnectOptions.Builder()
                .setConnectRetry(0)
                .setConnectTimeout(6000)
                .setServiceDiscoverRetry(2)
                .setServiceDiscoverTimeout(6000)
                .build();
    
        ClientManager.getClient().connect(mac, options, new BleConnectResponse() {
            @Override
            public void onResponse(int code, BleGattProfile profile) {
                showDialog(false);
                if (code == REQUEST_SUCCESS) {
                    //需要保存当前BleGattProfile 信息 获取可操作的 特征
                    ClientManager.getClient().setBleGattProfile(profile);
                    ToastUtil.showMessage("蓝牙连接成功");
                } else {
                    ToastUtil.showMessage("蓝牙连接失败");
                }
    
            }
        });
    

    在需要操作的时候,先判断连接状态,然后拿到保存的所有service ,遍历找到需要操作的,

       if (ClientManager.getClient().getBleGattProfile() == null || ClientManager.getClient().getConnectStatus(macAddreee) != BluetoothProfile.STATE_CONNECTED) {
        //do something
            return;
        }
    
        checkPermission();
        //必須每次查找 可能出現更换设备现象
    
    
        for (BleGattService service : ClientManager.getClient().getBleGattProfile().getServices()) {
            if (!service.getUUID().toString().toLowerCase().contains( SERVICE_CHARACTERISTIC))
                continue;
            for (BleGattCharacter character : service.getCharacters()) {
                if (character.getUuid().toString().toLowerCase().contains( CHARACTER_CHARACTERISTIC)) {
                 
                    mService = service.getUUID();
                    mCharacter = character.getUuid();
                    break;
                }
            }
        }
    

    说明:
    SERVICE_CHARACTERISTIC,
    CHARACTER_CHARACTERISTIC为厂商给定的特征值。
    mService mCharacter 为需要操作的uuid。

    0x05 关于日志太多
    使用gradle 引用无解,使用module 源码引用,自己修改下日志控制类就好了

    com.inuker.bluetooth.library.utils.BluetoothLog 中增加一个开关。

    有需要的拿走不谢。

    public class BluetoothLog {
    private static boolean allow = true;
    private static final String LOG_TAG = "miio-bluetooth";
    
    public static void enableLog(boolean enable) {
        allow = enable;
    }
    
    public static void i(String msg) {
        if (allow) {
            Log.i(LOG_TAG, msg);
        }
    }
    
    public static void e(String msg) {
        if (allow) {
            Log.e(LOG_TAG, msg);
        }
    }
    
    public static void v(String msg) {
        if (allow) {
            Log.v(LOG_TAG, msg);
        }
    }
    
    public static void d(String msg) {
        if (allow) {
            Log.d(LOG_TAG, msg);
        }
    }
    
    public static void w(String msg) {
        if (allow) {
            Log.w(LOG_TAG, msg);
        }
    }
    
    public static void e(Throwable e) {
        if (allow) {
            e(getThrowableString(e));
        }
    }
    
    public static void w(Throwable e) {
        if (allow) {
            w(getThrowableString(e));
        }
    }
    
    private static String getThrowableString(Throwable e) {
        Writer writer = new StringWriter();
        PrintWriter printWriter = new PrintWriter(writer);
    
        while (e != null) {
            e.printStackTrace(printWriter);
            e = e.getCause();
        }
    
        String text = writer.toString();
    
        printWriter.close();
    
        return text;
    }
    }
    

    相关文章

      网友评论

          本文标题:Android Android-BluetoothKit笔记

          本文链接:https://www.haomeiwen.com/subject/xivhxhtx.html