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

Posted fanfan-公众号-码农修仙儿

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了闲叙蓝牙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感兴趣的可以通过获取历史消息按照一二三四的顺序阅读

原文来自
原文链接

以上是关于闲叙蓝牙OPP---文件传输BluetoothOppService专讲的主要内容,如果未能解决你的问题,请参考以下文章

Android 蓝牙开发OPP传输文件

闲叙蓝牙

蓝牙 OPP 配置文件 - '客户端代理不可用'

为啥Android在通过蓝牙OPP接收时对可接受的文件类型进行如此严格的限制?

android移动支持蓝牙opp [关闭]

使用蓝牙打开远程设备文件管理器 [Android]