美文网首页
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笔记

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

  • [笔记]Android性能优化 中

    [笔记]Android性能优化 上[笔记]Android性能优化 中[笔记]Android性能优化 下 7.And...

  • [笔记]Android性能优化 下

    [笔记]Android性能优化 上[笔记]Android性能优化 中[笔记]Android性能优化 下 8.And...

  • [笔记]Android性能优化 上

    [笔记]Android性能优化 上[笔记]Android性能优化 中[笔记]Android性能优化 下 说明 这篇...

  • Gradle For Android笔记

    Gradle For Android笔记 目录索引: Gradle For Android笔记1.Gradle入门...

  • 指纹识别-Android

    指纹识别-Android @(Android进阶资料)[Android, 学习, 读书笔记, Markdown]指...

  • IPC笔记

    一、说明 笔记主要是《Android开发艺术探索》的阅读笔记和自己的理解,笔记中部分内容引自《Android...

  • View事件体系笔记

    一、说明 笔记主要是《Android开发艺术探索》的阅读笔记和自己的理解,笔记中部分内容引自《Android...

  • Activity笔记

    一、说明 笔记主要是《Android开发艺术探索》的阅读笔记和自己的理解,笔记中部分内容引自《Android...

  • 常见问题和性能优化

    一、说明 笔记主要是《Android开发艺术探索》的阅读笔记和自己的理解,笔记中部分内容引自《Android...

网友评论

      本文标题:Android Android-BluetoothKit笔记

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