跟我一块儿学OPP(一)

摘要概述

在蓝牙opp时既然是发送文件,client为发送方,那么还须要明确一个接收方做为server,待发送方和接受方肯定后就要在两个设备之间点对点的打通一条光明大道做为传输通道。android

 

固然还有你要运输的信息,有了这四要素,你就能够进行完美的运输了。sql

 

在运输结束以后须要把通道给拆了,由于每一个设备的通道是有限的。因此运输的前提是保证server端存在,而且通道能够正确创建。数据库

 

那么在创建以后开始传输,当信息送到server端时server是有自主选择权的,能够选择是否接受。若是被server拒绝的话,那么信息就会被丢弃了。ide

通道是否创建以及传输的具体状况都须要通知给client和server,因此应运而生的就是必不可少的notification。工具

 

因此,做为client端首先从功能上考虑总共须要处理5件事线程

 

  1. 选择接收方:在保证蓝牙开启的状况下,开启选择附近可用蓝牙设备界面,供用户选择目标设备进行分享设计

  2. 将要发送的文件存入db:以便随时更新状态,随时通知client。将须要分享的file插入到btopp.db中,由BluetoothOppProvider提供对数据库的增删改查。用于记录file的状态,协助传输和notification3d

  3. 创建传输通道:经过sdp搜索简历l2cap链接,若sdp搜索失败或者l2cap链接创建失败,则会去创建rfcomm链接。obex层connect操做,会在connect以后进行put发送。server

  4. 拆除通道:在发送完毕后断开obex和l2cap或者是Rfcomm,即断开所创建的通道。sqlite

  5. notification:对于文件的等待状态,正在发送状态,以及发送结束(包括发送成功或者失败或者被server拒绝)进行更新通notification,是直接和用户交互的窗口,贯穿整个文件传输过程。

 

以下图所示,client和server端进行opp传输时的具体协议栈状况

 

watermark,size_14,text_QDUxQ1RP5Y2a5a6i,color_FFFFFF,t_100,g_se,x_10,y_10,shadow_20,type_ZmFuZ3poZW5naGVpdGk=

 

源码真算是博大精深,在实现opp时没有多余的废话,每一个功能都缺一不可,在功能设计上让人佩服。

 

至于实现过程当中源码是否是有不恰当的地方,就须要好好研究一下了。接下来分析各个功能项的具体实现了。

 

选择接收方

如上所示,选择接收方也就是选择目标方server。那么功能是如何实现的呢?

 

在选择蓝牙分享时会发送action_send(分享单个对象)或者是

action_send_multiple(分享多个对象),负责监听处理该广播的为BluetoothOppLauncherActivity。那么接下来看看接收到send以后都作了哪些处理。

 

这里就不贴代码了,直接总结出他所做的事情

  1. 判断蓝牙是否可用

  2. 保存要分享的文件

  3. 开启蓝牙设备选择界面

 

接下来就是各个击破了

 

第一:判断蓝牙是否可用isBluetoothAllowed():注意此处判断蓝牙是否可用并不是是指判断isEnable而是判断canEnable。那么蓝牙是否可用和什么相关呢?代码以下

 

 

watermark,size_14,text_QDUxQ1RP5Y2a5a6i,color_FFFFFF,t_100,g_se,x_10,y_10,shadow_20,type_ZmFuZ3poZW5naGVpdGk=

 

首先确认,该方法返回为true时表示此时蓝牙可用。能够看到蓝牙是否可用有三个判断条件

 

  1. isAirplaneModeOn:是否处于飞行模式

  2. isAirplaneSensitive:从字面上看是说是否对飞行模式敏感,也就是说在a条件知足即飞行模式开启时蓝牙是否被disable

  3. isAirplaneToggleable:在a和b条件都知足的状况下即飞行模式开启且在开启飞行模式时bluetooth会被disable的状况下,是否支持用户从新开启reenable蓝牙。

 

第二:保存要分享的对象saveSendingFileInfo:注意此时要分享的对象并非保存在db,而是会被保存至BluetoothOppManager和BluetoothOppUtility供确认目标方后开启分享时使用。

 

saveSendingFileInfo是BluetoothOppManager的实例方法,以保存单个分享对象为例进行说明

 

 

watermark,size_14,text_QDUxQ1RP5Y2a5a6i,color_FFFFFF,t_100,g_se,x_10,y_10,shadow_20,type_ZmFuZ3poZW5naGVpdGk=

 

在保存时有这么几个参数

  1. mMultipleFlag:表示要分享的是不是多个文件,该参数值由所监听到的send的action决定,若是是send_multiple则为true

  2. mMimeTypeOfSendingFile:表示要分享的类型,好比image/png,从监听到action的intent中获取

  3. mUriOfSendingFile:要分享的文件的uri,好比file:///storage/emulated/0/DCIM/Camera/IMG_20180607_142700R.jpg@3348f35,该参数也是在监听到send的action时从intent中获取的。

  4. mIsHandoverInitiated:这次文件传输是不是由NFC发起的,普及一下,nfc传输文件实际上走的也是蓝牙opp传输

 

而后就是具体的保存了,保存分为两大部分:

一部分是storeAppicationData(),将file经过sharedPreference方法保存到文件OPPMGR.xml中,文件所保存的信息以下所示:

 

 

watermark,size_14,text_QDUxQ1RP5Y2a5a6i,color_FFFFFF,t_100,g_se,x_10,y_10,shadow_20,type_ZmFuZ3poZW5naGVpdGk=

 

其中sendingflag表示程序是否在开启蓝牙的过程当中为。其余参数解释见上文。

 

而关于保存的另外一部分操做就是在BluetoothOppUtility的putSendFileInfo方法了,在putSendFileInfo方法中其实就是保存早BluetoothOppUtility私有的map集合sSendFileMap,key值为uri,value为generateFileInfo所生成的BluetoothOppSendFileInfo对象。

 

每一个BluetoothOppSendFileInfo对象包含如下6个字段

 

 

watermark,size_14,text_QDUxQ1RP5Y2a5a6i,color_FFFFFF,t_100,g_se,x_10,y_10,shadow_20,type_ZmFuZ3poZW5naGVpdGk=

 

其中每一个字段的取值示例以下所示

 

 

watermark,size_14,text_QDUxQ1RP5Y2a5a6i,color_FFFFFF,t_100,g_se,x_10,y_10,shadow_20,type_ZmFuZ3poZW5naGVpdGk=

 

该对象就是蓝牙opp传输进行时所操做的对象了。也就是说在传输过程当中对于文件传输的状态、进度、输入流都跟该对象相关。

 

至此,准备工做作完了,接下来就是去打开蓝牙设备选择界面选择设备了

 

第三:启动蓝牙设备选择界面。经过在BluetoothOppLauncherActivity中调用launchDevicePicker方法来启动蓝牙选择界面加载可用设备。

 

固然在加载该界面以前须要保证蓝牙处于开启状态,因此在这里作了个蓝牙isEnable的判断,若是蓝牙未开启,则会去先开启蓝牙而后再去启动蓝牙选择界面。

 

对于蓝牙选择界面是依靠com.android.example.notificationlistener.LAUNCH来启动DevicePickerActivity的。

 

关于界面的UI加载不在赘述,当用户选择设备后会发送android.bluetooth.devicepicker.action.DEVICE_SELECTED广播,交由BluetoothOppReceiver处理选择设备以后的事情。

 

好了,目前为止第一部分也是最简单的部分选择目标设备的分析完成了,那么整个传输过程彻底就能够到此为止了。

 

按照以前的总结再加上对代码的分析能够看出选择目标方这一过程其实作了四件事

 

第一:检测蓝牙的可用性即canEnable,这是必要的,若是目前处于飞行模式而且飞行模式下会关闭蓝牙且不支持用户从新enable蓝牙。

 

第二:将一些file相关的type和length以及uri的信息保存到BluetoothOppManager中,而这个信息就是在接下来保存到db时作铺垫,须要将manager中的信息存入db

 

第三:固然每一个file都会对应一个BluetoothSendFileInfo对象,该对象会在文件开始传输时发挥做用,记录file的状态并提供输入流。

 

第四:启动蓝牙选择界面,供用户选择目标设备。

若是对某部分有疑问请往上翻...

 

存入btopp.db

在接收到设备选择后的广播以后调用BluetoothOppManager的实例方法startTransfer开始传输,本文会将传输分红两大部分解释,一是将文件存入DB,另外一个就是开启传输。

 

本部分讲述将文件存入DB,其实存入db本来是没什么好说的也不是文章的重点,可是在蓝牙OPP过程当中存入DB之时作了一件决定性的操做,因此就看一下db的存入来避免错太重点。

 

好了接下来看一下具体代码,以下

 

 

watermark,size_14,text_QDUxQ1RP5Y2a5a6i,color_FFFFFF,t_100,g_se,x_10,y_10,shadow_20,type_ZmFuZ3poZW5naGVpdGk=

 

能够看到,在该方法几乎算是只作了一件事,那就是开启了插入db的线程InsertShareInfoThread。

 

直接奔到线程的run方法中,代码逻辑很简单,就再也不贴出,run方法中处理以下:若是是插入单个文件则调用insertSingleShared,若是是插入多个文件则调用insertMultipleShare。

 

本次分析以插入单个文件为例。因此直接看插入单个文件的代码。

 

 

watermark,size_14,text_QDUxQ1RP5Y2a5a6i,color_FFFFFF,t_100,g_se,x_10,y_10,shadow_20,type_ZmFuZ3poZW5naGVpdGk=

 

根据这几行代码,你要确认3个信息

 

  1. 所出入的对象values所包含的信息有:能够看到会包含file的uri、file的文件类型、以及目标设备。固然若是是经过nfc启动的文件传输须要传入一个确认方式即为nfc确认。是否是以为少?还记得文章第一部分说的文件保存吗?保存方式是以map的形式,key为uri,value为BluetoothOppSendFileInfo对象,因此一旦有了uri,那么对应的value也就不难获取了

  2. 所插入的数据库为:这个能够从contentUri中获得,为btopp.db

  3. 执行插入操做的contentprovider为:根据contenturi一样也能够得出执行insert操做的为BluetoothOppProvider(根据contenturi推出provider相信你们都很明白,再也不赘述)

 

看到这儿有没有一脸懵的感受...说好的文件传输呢?怎么insert到DB就完事儿了?

 

难道你觉得insert就是单纯的将数据写入DB?那你就太单纯了...目前到这儿只有insert的实现不明,因此直接奔到BluetoothOppProvider的insert方法中看看究竟是怎么作的...

 

insert方法基本上是分为两部分,一部分实现insert一条数据到db,而另外一部分就是开启BluetoothOppService。

 

第一,insert数据

 

insert数据其实流程都同样:首先构造values对象,而后insert到db,代码就不贴了,想看的能够经过androidxref看谷歌源码。

 

这里贴一下btopp.db数据库示例,该数据库通常位于data/data/com.android.bluetooth/databases/目录下,root以后能够导出来经过sqlite工具查看,先来看一下数据库都有哪些字段

 

 

watermark,size_14,text_QDUxQ1RP5Y2a5a6i,color_FFFFFF,t_100,g_se,x_10,y_10,shadow_20,type_ZmFuZ3poZW5naGVpdGk=

 

共包含14个字段,先来看一个db数据示例

 

watermark,size_14,text_QDUxQ1RP5Y2a5a6i,color_FFFFFF,t_100,g_se,x_10,y_10,shadow_20,type_ZmFuZ3poZW5naGVpdGk=

 

其中id属于插入db的序列号,uri是文章第一部分所保存的uri,hint文件名,direction表示是发送文件仍是接受文件,destination表示目标方设备地址,comfirm表示文件传输时的用户确认方式,status表示文件的状态等等。

 

stauts是个很重要的字段,在文件传输过程当中就是经过更新status状态来让其余人能够经过查询db来查看当前的文件状态:是否正在等待发送,是否正在发送,是否发送结束等等,也是更新notification的依据。

 

status的更新贯穿整个opp过程,会把status和notification放在一块儿进行分析。current_bytes和total_bytes用于更新文件传输过程当中的进度条。

 

timeStamp字段在这里不起眼很容易被忽略掉,在后续的开启transfer时须要建立batch,而至因而新建batch仍是插入已有的batch就是由该timestamp决定的。

 

若是分享单个文件那么毫无疑问timestamp是在BluetoothOppProvider的insert赋值的,但若是是分享多个文件那么timestamp实在BluetoothOppManager的插入db以前完成的。

 

也就是说一次分享多个文件时这些文件拥有同一个timestamp值那么在后续的transfer中也就会拥有同一个batch

 

第二,开启BluetoothOppService

insert里经过startService来开启服务。在开启蓝牙时就会去检测是否支持Opp并开启服务,那为何这里又要开启呢?并且仍是连续两次?,以下

 

watermark,size_14,text_QDUxQ1RP5Y2a5a6i,color_FFFFFF,t_100,g_se,x_10,y_10,shadow_20,type_ZmFuZ3poZW5naGVpdGk=

 

明明在蓝牙开启以后service就会启动了,那为何在真正insert到db以前会启动一次service,insert以后再次start一次,什么目的?

 

首先明确一点调用startService启动服务时若是service未启动则走create-->start流程,若是service已经存在则会去触发service的start,而在opp的service的start方法会根据db数据库的更新来开启传输。

 

因此此处猜想之因此启动两次一是避免service已经被杀死,二是及时根据db的更新来更新发送。

 

基本上往db存数据就到这儿了,主要就是存入db,顺便重启service保证oppservice不死。接下来就是看看当把数据插入到db以后是如何影响service来开启transfer的

https://mp.weixin.qq.com/s/P0BgNKxQxlCrP8Y1Zm3AIw

相关文章
相关标签/搜索