工作问题记录

工作笔记

1. 蓝牙的启动与关闭

1. 使用Service

MainActivity启动时,检查数据库中是否已经存在登录后的账户数据。如果有,就获取这些账户的uuid,开启service

1
2
3
if(SERVICE_UUIDs.size!=0){
    openService()   // 开启服务
}

2. 开启蓝牙

服务用于实现低功耗蓝牙单独的控制,在服务的onCreate中开启蓝牙

1
2
3
4
5
// 开启蓝牙
override fun onCreate() {
    super.onCreate()
    ChatServer.startServer(application)
}

3. 在BluetoothServiceonDestory中关闭蓝牙。

1
2
3
4
5
6
// 关闭蓝牙
 override fun onDestroy() {
     super.onDestroy()
     Log.d("BluetoothService", "stop service and bluetooth")
     ChatServer.stopServer()
 }

2. 登录模块

1. 开启蓝牙

在启动MainActivity时,检查是否存在账户信息,若存在,直接开启蓝牙。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
private fun openService()
{
    val activityManager = getSystemService(ACTIVITY_SERVICE) as ActivityManager
    // 若当前没有运行的服务,就启动一个新的
    if (activityManager.getRunningServices(30).size == 0) {
        Log.d(tag, "==================== open Service!")
        messageIntent = Intent(this, BluetoothService::class.java)
        startService(messageIntent)
    }
}

2. 连接状态改变

当设备被连接后会触发回调中的onConnectionStateChange方法。

3. 接收消息

当设备被成功连接后,会收到客户端发来的用户名。在onCharacteristicWriteRequest中接收该用户名,如果该用户名匹配成功,就打开DialogActivity让用户确认是否登录。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
// ChatServer.kt中定义一个被观察的数据
val login = MutableLiveData<Boolean>()

// 在onCharacteristicWriteRequest中修改该参数的状态
login = true;

// MainActivity.kt中观察登录情况
val loginObserver = Observer<Boolean>{
    if(it == true){
        val loginIntent = Intent(this, DialogActivity::class.java)
        startActivity(loginIntent)
        overridePendingTransition(R.anim.bottom_in,R.anim.bottom_silent)
        ChatServer.login.value = false
    }
}
ChatServer.login.observe(this, loginObserver)

用户点击确认登录后,调用onResponseToClient()方法向客户端发送密码。

1
2
3
4
5
6
7
8
9
// 将密码保存到characteristic中
messageCharacteristic?.setValue(key.toByteArray(Charsets.UTF_8))

// 发送信息
gattServer!!.notifyCharacteristicChanged(currentDevice, messageCharacteristic, false)

// 关闭Dialog
if(activity?.localClassName == "DialogActivity")
 activity.finish()

4. 记录登录信息

1
AppDatabase.getInstance(mApp?.applicationContext!!).authLogDao().addAuthLog(authLog)

3. 注册模块

通过点击屏幕右下角的按钮,申请摄像头权限,打开扫码页面。通过扫描客户端生成的二维码,获取账户信息。保存这些信息,数据库插入成功后开启蓝牙进行广播。

1. 保存扫码信息

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
val id = AppDatabase
 .getInstance(this.applicationContext!!)
 .accountDao()
 .addAccount(accountData)

if(!id.equals(0))    // 数据插入成功
{
    val str:String = accountData.serviceUUID.substring(1,37) // 发过来的uuid串外有括号
    SERVICE_UUIDs.add(UUID.fromString(str))
}

2. 开启蓝牙

同登录模块的openService()方法。由于扫码结束后会重新启动MainActivity,因此和登录模块一起在Activity启动时开启服务即可。

3. 接收消息

同登录模块的onCharacteristicWriteRequest(),接收到消息:listening

4. 发送消息

onCharacteristicWriteRequest()中,返回消息:done!

1
2
3
val response = "done!"
characteristic.setValue(response.toByteArray(Charsets.UTF_8))
gattServer?.notifyCharacteristicChanged(device, characteristic, false);

4. 问题与解决

1. 扫码后向客户端发送数据

流程

  1. 扫码注册后会获取到用户的注册信息,将该信息通过子线程插入到room数据库中
  2. 如果插入成功,就向客户端发送消息:done!

问题

  1. 通过writeCharacteristic发送数据不成功,获取到的数据都是空的,猜测可能的问题是在子线程里发送蓝牙消息不正确
  2. 后改到主线程,客户端依旧闪退。于是检查蓝牙类的单例是否出了问题,但是单例没写错。
  3. 又想或者是通过writeCharacteristic发送数据的方式不对,另一种看到的发送方法为notifyCharacteristicChanged
  4. 尝试通过点击按钮发送数据,这样是成功的,这可以确定不是参数、回调中发送数据导致的问题。
  5. 调试后发现原项目将蓝牙的启动和关闭绑定在了MainActivity的启动和销毁中,在MainActivity生命周期结束后会导致蓝牙断开,从而导致发送消息闪退。

解决方法

  1. 将数据库插入的返回值作为LiveData监控,当它变化且不为0时,进行数据的发送
  2. 创建一个Service类,将蓝牙的开启与关闭和它绑定在后台运行,这样Activity不管怎么切换,都不会影响蓝牙的开关了。
  3. Service的启动放在Activity启动和开启相机的时候,在成功发送完数据后关闭Service,从而避免一直开启蓝牙而占用手机资源、消耗性能。

2. 多设备蓝牙配对问题1

问题

有好几个手机、电脑同事扫码注册/登录时,如何保证准确连接上自己的设备?

解决方法

  1. 由电脑端在生成的二维码中放入一个随机uuid,在手机端扫码后保存好这个uuid

  2. 手机端使用该uuid开启蓝牙服务,再由电脑端去连接。

这样就能保证电脑和手机蓝牙连接的时候,uuid是一一对应的了。

3. 多设备蓝牙配对问题2

问题

由问题1又产生了新的问题,如果一个人同时有好几个设备,每个设备在注册前都随机生成了一个uuid。那么在登录时,手机端应当使用哪个uuid进行蓝牙广播?

解决思路

  • 可以全部进行广播,然后让客户端查询它需要的uuid主动连接。

  • 让用户点击账号,通过该账号的点击来发布对应的uuid服务。但是用户得知道登录前要点击登录的设备。

  • 两层嵌套的方式,客户端先用默认的uuid发送一个登录请求,这个请求中包含它自己的uuid,服务端查询表项中有没有这个uuid,有的话就直接连接,没有就丢弃。

这个想法不行,客户端只有建立了连接才能发送uuid,使用默认uuid建立连接的话又会产生多设备配对问题。

4. 蓝牙主设备无法被扫描到

问题

在某一次代码更新后,用同事的手机广播蓝牙,其他设备搜索不到它。但是我的手机可以正常广播并连接,这导致了我们猜测是手机系统问题,而没有第一时间查看日志有没有输出报错。

一开始以为是uuid生成不正确的问题,但是网上都是随机生成的uuid

随后发现我的手机是鸿蒙系统,同事的是Android系统,所以猜测是不是系统的问题,但依旧不是这个问题。

经过排查,发现是服务端广播失败了,报错ADVERTISE_FAILED_DATA_TOO_LARGE

解决问题

最后发现是因为广播数据包括uuid+device name,字段太长导致广播失败。应当取消发送设备名称。


最后更新: July 16, 2022 10:57:39