专栏首页编程思想之路闲叙蓝牙OPP(二)---文件传输BluetoothOppService专讲

闲叙蓝牙OPP(二)---文件传输BluetoothOppService专讲

从上文可以看出,文件插入db后直接就是BluetoothOppService(下文会缩写成BtOppService)的处理了,在BtOppService中建立传输通道,但在讲述传输通道之前,先来好好分析一下BtOppService。

那么在分析该BtOppService之前先提一个问题,如果让你去设计这么一个service你会怎么设计?service都需要实现哪些功能?

对于BtOppService这个service在opp中承上启下,像a2dp的话client和server端会有分别对应的service,但是opp的话不论是server端还是client端都在BtOppService中处理。说白了也就是你本机设备想要分享文件出去或者是要接收分享的文件都是在BtOppService处理。不论是分享文件还是接受文件针对obex来说的话都是object push 功能,在使用object push 功能时有client和server之分,发起分享动作的就是client。所以两个设备的角色在分享开启之前是不确定的,只有分享动作发起之后至分享动作结束前才会有client和server之分。所以BtOppService必须要实现既可以作为client又可以作为server,所以如果是让我们去设计这个BtOppService的话,那么它一定要满足两大基本功能

作为server端监听。蓝牙开启后开启监听,为接收远端设备分享的文件做准备。从协议层角度来讨论的话就是,作为server端会在l2cap层创建一个套接字Serversocket,相当于server打开L2CAP的门翘首以盼,等待对端的到来。

作为client端,在分享文件时完成相关操作,从协议层来说的话就是发起L2cap连接,也就是client自己驱车前往server端的门口和server汇合形成L2cap层的连接通路。

如下图所示 以往写app实现蓝牙文件传输功能时这就是最基本的必须要实现的功能,那么是不是还有别的需要我们做的呢?比如文件传输过程中的文件状态信息等会实时的保存在数据库,那么设计者是否需要处理这些信息呢?如果说完全不需要考虑用户的感受,不需要将传输状态、传输进度呈现给用户,你只需要在文件分享时后台开启BtOppService去分享,然后在文件到来时BtOppService在后台接收,用户在前台是没有任何感知的,其实到此也可以了。但是没办法,谷歌是不允许背着用户去这么操作的,会被骂的。所以不得已,你必须要以一种notification的方式通知用户,该方式可以被用户处理也可以不处理。所以随之而来的就是在BtOppService中需要有队notification的控制。既然是notification那么就设计到文件传输过程的状态更新,所以如果在obex或者L2CAP层文件传输已经失败并且更新了db,那么就一定在BtOppService中进行监听处理notification。在代码设计中上下层状态保持一致至关重要,所以在db更新后,BtOppService一定要实时监测到并进行对应处理,于是乎这就需要一个对db实时监控的东西,那就是ContentObserver对象来监听db的改变。

如果是我们自己设计,那就是BtOppService要满足一下四大基本功能.

作为server端,在蓝牙开启后开启L2cap层或者是Rfcomm层的监听大门

作为client端,当有新文件要分享时也就是在BluetoothOppProvider的insert时需要向server的大门发送建立连接的消息。

通知用户,在分享开启后以notification的方式通知给用户,并给用户管理传输工程的权利

开启ContentObserver,监听btOpp.db的变化,开启一个UpdateThread线程,在db发生变化时也要对传输过程和notification进行更新。

好了目前BtOppService的大框架定了下来,那么接下来就是具体细节实现以及异常处理了。有了自己的想法之后接下来看看源码的设计,看看是不是有什么感觉设计不对的地方

可以看到在create方法中我们要求的四件事全都做了

监听db变化:BluetoothShareContentObserver对象,但是在监听到db变化时只有一句代码那就是调用updateFromProvider,至于updateFromProvider是干什么的,再往下看

通知用户:BluetoothOppNotification对象,将分享进度、状态呈现给用户

注册receiver监听蓝牙状态改变:在receiver中监听到蓝牙状态为STATE_ON时会做两件事,一是startListener开启监听,二是判断mSendingFlags字段,这个字段说明蓝牙是在文件分享时正在开启的道路上,所以一旦检测到flag为true,那就说明还没到蓝牙选择界面,所以要跳转到蓝牙选择界面去选择目标设备。而当监听到state_turning_off状态时,则需要stopListeners,虽然startListener只是开启serversocket监听,但是stopListeners既要停止掉作为server的监听又要停止掉作为client的UpdateThread。其实看到这儿我本身是感觉很奇怪的,印象中BtOppService也是在蓝牙开启后启动的核心服务,那么为什么不在BtOppService开启的地方直接开启listener监听opp文件传输请求?查这个问题之前,我做了一些猜想,我想是不是说startListener需要一些条件,带着问题看了一下有些失望,没发现什么强有力的理由来说服我非要在BtOppService开启后开启receiver监听到STATE_ON之后再开启listener。于是乎开始怀疑BtOppService开启的地方,追了一下才发现,BtOppService是在蓝牙一旦处于STATE_BLE_ON时便会开启,其他的蓝牙核心服务也是如此,至于如此设计的原因就不得而知了。很明显比startListener找了一个状态,是在ble蓝牙开启时就会开启BtOppService的,而startListener是在传统蓝牙开启后才开启的。

updateFromProvider:该方法的目的是保证BtOppService的一个线程能够处于存活状态,这个线程就是UpdateThread,该线程就是根据db的更新来进行一些操作。

对于notification会在整体分析完毕后再分析,所以这么看来未知点就变成了两个

UpdateThread线程中做了什么

startListener开启监听是如何获取ServerSocket的

首先是看一下startListener做了什么

一,startListener

startListener经过发送message给handle最终会调用startSocketListener,该方法内容如下

可以看到该方法做了两件事,

创建serverSocket:这个本身怎么创建的可以看源码,关键看传入了一个参数—this,该参数是IObexConnectionHandler对象,用于监听serversocket.accept的结果回调,如果连接成功,就会回调OnConnect方法,如果连接失败就会回调onAcceptFailed方法。BtOppService实现了IObexConnectionHandler,所以在BtOppService中可以监听到有连接请求过来时的连接结果也就是serversocket.accept的结果。

向sdp服务列表中添加OBEX Object Push服务,client端在创建L2cap连接时会借助sdp搜索server所支持的服务列表,如果检测到支持OBEX Object Push就会去创建L2cap连接,具体在后续创建L2cap连接之时再说,总之,此处就是往sdb的record list服务列表添加一个所支持的服务,等被其他设备发现时告诉他们自己所支持的sdp服务列表。这也是为什么OPP文件传输协议栈会需要sdp的原因

二,UpdateThread

关于db的操作不外乎就是增删该查,在这里updateThread中根据db的增删该查会进行notification的更新、记录文件传输的数据列表mShares(ArrayList)的更新—更新目的是和db保持一致。线程中的代码看起来有些乱,但只要明白了规则也就不难理解了

以上是规则的英文注释,通俗点儿说就是一个原则:以db为基准,mShares要和db保持一致。而且在db和mShares中数据是按照id的顺序由小到大排列的,所以有了这两个限制,自然而然就可以设计了,所以按照数据的排序位置进行遍历cursor时也就有了下列规则,

如果相对应位置cursor有数据但是mShares无数据,那就表示mShares数据长度就到这里所以cursor多余的数据需要添加到mShares中insertShare

如果相应位置cursor有数据并且mShares也有数据,这就需要分情况考虑,如果两个数据的id(数据唯一标识)一致,说明是同一条数据此时需要updataShare更新数据。但是如果两个数据的id不一致,那就说明不是同一条数据,此时就要看两个id的大小了,如果cursor中数据id大于mShares数据id那么说明mShares中这条数据是多余的就需要删除掉,但是如果cursor中数据id小于mShares数据id,那么就说明mShares漏掉了一条数据,所以就需要将cursor中该条数据插入mShares的此处insertShare,mShares中id大的数据要往后排继续等待筛选。

如果相应位置cursor无数据但是mShares有数据,此时需要删除mShares中多余的数据deleteShare

这里insertShare、updateShare、deleteShare并不是简简单单的对mShares列表的操作,还包括对连接通路的管理:insert会建立,update会更新,delete会stop会话,是通道的管理者。

一不小心又写了很多,先暂停吧,文章太长估计看完什么印象都没了,希望看完本文,你大概能知道BtOppService都做了哪几件事,关于通道管理待下文。这几次更新的文章之间的关联性比较大,主要讲解的是蓝牙opp文件传输,如果对opp感兴趣的可以通过获取历史消息按照一二三四的顺序阅读

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

我来说两句

0 条评论
登录 后参与评论

相关文章

  • Android6.0锁屏源码分析之界面布局分析

    大致先介绍一下锁屏界面 Android的锁屏界面可以分为两级, 一级锁屏界面暂且称之为锁屏界面LockScreen,即平常用到的无需任何输入和验证,只需滑动解锁...

    fanfan
  • Android中View研究自学之路 Android6.0源码分析之View(一)Android6.0源码分析之View(二)

    Android中View研究自学之路 http://blog.csdn.net/zrf1335348191/article/details/54171263 ...

    fanfan
  • Android中View研究自学之路

    写这篇博客呢是在研究了view将近一个月之后,算是对自己的学习做一个总结,进而反思一下学习方法,本博文不涉及代码分析。

    fanfan
  • 你和未来智慧门店的距离,只差一次和我们的相遇

    科幻小说中的超能力商店似乎很遥远,但是现在已经离你越来越近,这可不是天方夜谭,这是腾讯云正在探索与研发,帮助线下门店实现数据化和智能化的解决方案,新的未来大门...

    腾讯云智慧零售
  • PHP中empty,is_null,isset的区别

    isset 判断变量是否已存在,如果变量存在则返回 TRUE,否则返回 FALSE。

    用户7657330
  • 电商项目(中)

    感谢你学习今天的内容,如果你觉得这篇文章对你有帮助的话,也欢迎把它分享给更多的朋友,感谢。

    达达前端
  • Python爬虫之urllib库—进阶篇

    urllib库除了一些基础的用法外,还有很多高级的功能,可以更加灵活的适用在爬虫应用中,比如,用HTTP的POST请求方法向服务器提交数据实现用户登录、当服务器...

    用户2769421
  • [译] 定制 create-react-app:如何制作自己的模版

    原文:https://auth0.com/blog/how-to-configure-create-react-app/#Test-Your-Custom-Sc...

    江米小枣
  • Excel单元格向下,向右自动填充的快捷键

    假设C3单元格已经设置好了计算公式,如果想将其计算逻辑复制到C4~C6上,只需要选中C3~C6,按快捷键Ctrl+D,就可以完成公式复制。如果想向右复制,就用快...

    Jerry Wang
  • [Memcache] memcache中add和set方法的区别

    add方法添加时,如果存在返回false set方法添加时,如果存在就是覆盖,不存就是添加

    陶士涵

扫码关注云+社区

领取腾讯云代金券