1 BluetoothOppLauncherActivity

Android手机点击某文件进行蓝牙分享的时候,会跳转到系统自带应用Bluetooth中。 
具体文件:packages/apps/Bluetooth/src/com/android/bluetooth/opp/BluetoothOppLauncherActivity.Java 
看一下BluetoothOppLauncherActivity是如何处理分享文件请求的。

Android 蓝牙OPP传输文件_java

BluetoothOppLauncherActivity并没有界面(没有setContentView),只是一个中转站,它根据当前蓝牙等相关状态进行跳转。Intent.ACTION_SEND和Intent.ACTION_SEND_MULTIPLE的区别是前者表示单个文件,后者表示多个文件。这里只研究下分享单个文件,分享单个文件懂了,多个文件道理类似。 
其中isBluetoothAllowed()函数会先判断飞行模式是否开启,如果没有开启则返回true。如果开启,则进行下一步判断飞行模式是否重要,如果不重要则返回true(说明蓝牙可以使用)。如果重要则继续分析飞行模式下是否可以打开蓝牙,可以打开蓝牙则返回true,否则返回false。总的来说该函数就是判断当前蓝牙是否允许使用。不允许使用蓝牙则跳转到BluetoothOppBtErrorActivity。

Android 蓝牙OPP传输文件_java_02

使用过Android系统分享的应该知道,其支持文件(图片、视频等)、字符串。而这里会对文件、字符串进行区分处理,字符串则先创建文件然后在进行分享。 
launchDevicePicker()函数中先判断蓝牙是否开启。 
如果蓝牙没有开启则跳转到BluetoothOppBtEnableActivity显示dialog(询问是否开启蓝牙),点击取消则则退出,点击打开则打开蓝牙并跳到BluetoothOppBtEnablingActivity(该activity主要显示一个progress dialog)。当蓝牙打开,则BluetoothOppBtEnablingActivity 界面finish。BluetoothOppReceiver广播接收者接收到蓝牙开启,跳转到DevicePickerActivity界面(系统Settings应用)。 
如果蓝牙已开启,则直接跳转到跳转到DevicePickerActivity界面(系统Settings应用)。 
launchDevicePicker()下的跳转代码:

Android 蓝牙OPP传输文件_java_03

系统Settings应用中AndroidManifest.xml中发现对应action的DevicePickerActivity,所以该跳转会跳转到系统Settings应用中的DevicePickerActivity中。

Android 蓝牙OPP传输文件_java_04

2 DevicePicker

DevicePickerActivity中代码很简单,只是设置了布局。 
setContentView(R.layout.bluetooth_device_picker); 
bluetooth_device_picker.xml中有一个fragment指向DevicePickerFragment,也就是主要的处理在DevicePickerFragment中。 
DevicePickerFragment界面会显示出配对、扫描到的蓝牙列表。可以点击一个设备进行分享文件。

Android 蓝牙OPP传输文件_java_05

点击设备,会判断是否是绑定状态,或者mNeedAuth为false。mNeedAuth是通过intent传过来的值为false。所以满足条件。 
接着看sendDevicePickedIntent()。该函数就是发了一个广播。

Android 蓝牙OPP传输文件_java_06

3 BluetoothOppReceiver 

查看系统应用Bluetooth中的BluetoothOppReceiver类中对此广播进行了处理。但是Bluetooth中的AndroidManifest.xml中该广播接收者的注册并没有添加此action。但是却可以接收此广播。原因应该是该广播发送时携带了包名、类名。

Android 蓝牙OPP传输文件_java_07

BluetoothOppReceiver收到此广播后的主要处理代码如下,将此条记录添加到数据库。

// Insert transfer session record todatabase

mOppManager.startTransfer(remoteDevice);

BluetoothOppManager对象调用startTransfer方法。在startTransfer方法中创建一个InsertShareInfoThread线程并开始运行。 
InsertShareInfoThread线程中区分分享的是一个文件还是多个文件。我们这里只看下处理单个文件insertSingleShare()函数。

Android 蓝牙OPP传输文件_java_08

由mContext.getContentResolver().insert()可知其有对应的provider。BluetoothOppProvider继承了ContextProvider。查看BluetoothOppProvider中的insert方法。

Android 蓝牙OPP传输文件_java_09

由上可知通过蓝牙分享的时候会start BluetoothOppService。

4 BluetoothOppService

在BluetoothOppService中会监听数据库字段(BluetoothShare.CONTENT_URI)的变化,调用updateFromProvider()函数进行处理。onCreate()和onStartCommand()函数都会调用updateFromProvider()。 
updateFromProvider() ->创建线程UpdateThread -> insertShare()。

Android 蓝牙OPP传输文件_java_10

5 BluetoothOppTransfer 

这里只说向外发送、分享(接收文件之后再分析)。接着看BluetoothOppTransfer。其start() 中检查蓝牙是否打开(保证安全),调用startConnectSession()

startConnectSession()函数中开始向远端设备进行连接,该函数中主要就是创建SocketConnectThread线程,用来连接其他设备。 
SocketConnectThread线程主要代码:

Android 蓝牙OPP传输文件_java_11

这里先创建BluetoothSocket,然后通过BluetoothSocket进行连接。 
连接成功后,startObexSession()->new BluetoothOppObexClientSession ->BluetoothOppObexClientSession .start()

6 BluetoothOppObexClientSession

BluetoothOppObexClientSession类说明该设备作为obex client,向server发送文件。该类中主要功能:obex连接、发送分享文件的信息,发送数据等。 
start() -> 创建ClientThread线程并运行 -> connect()。

Android 蓝牙OPP传输文件_java_12 

在connect()函数中,通过mTransport1(BluetoothOppRfcommTransport类型,该类型中主要包含之前创建的BluetoothSocket)对象,创建client session,连接远端设备。

obex连接成功后,调用doSend(),该函数中先检查下文件是否存在,然后查看连接状态,连接状态下并且存在文件则sendFile才真正的开始发送文件。之会将相应的状态发送到BluetoothOppTransfer中。

真正的发送文件是在sendFile()函数中。不过该函数太长就不全贴出来了,只说一下重要的地方。

1 发送文件信息

HeaderSet request = new HeaderSet();

request.setHeader(HeaderSet.NAME, fileInfo.mFileName); //文件名

request.setHeader(HeaderSet.TYPE, fileInfo.mMimetype); //文件类型

request.setHeader(HeaderSet.LENGTH, fileInfo.mLength);  //文件大小

//通过obex发送传递文件请求 

putOperation = (ClientOperation)mCs.put(request);

//putOperation类型为ClientOperation,具体java.obex包下的类没有向外透漏,不太清楚是具体怎么回事。

2 获取obex层输入输出流

//获取输入输出流。 

outputStream = putOperation.openOutputStream();

inputStream = putOperation.openInputStream();

3 发送第一个包

//从文件中读取内容 

BufferedInputStream a = new BufferedInputStream(fileInfo.mInputStream, 0x4000);

readLength = readFully(a, buffer, outputBufferSize); 

//先向远程设备发送第一个包,该操作会阻塞等待远端设备的接收读取。

outputStream.write(buffer, 0, readLength); 

position += readLength; 

//如果文件太小,一个包就已经发送完,则将输出流关闭。

//outputStream.close();

4 查看回应 
接着查看远端设备的回应,是否接受。

/* check remote accept or reject */

responseCode = putOperation.getResponseCode();

if (responseCode == ResponseCodes.OBEX_HTTP_CONTINUE||  responseCode == ResponseCodes.OBEX_HTTP_OK) {//接收、继续

    okToProceed = true;

    updateValues = new ContentValues();

    updateValues.put(BluetoothShare.CURRENT_BYTES, position);          

    mContext1.getContentResolver().update(contentUri, updateValues,null,                                    null); 

} else {//拒绝、断开接收

      Log.i(TAG, "Remote reject, Response code is " + responseCode);

}

5 判断发送数据 
接着循环判断、从文件读取数据、发送数据。

while (!mInterrupted && okToProceed && (position != fileInfo.mLength)) { 

    readLength = a.read(buffer, 0, outputBufferSize); 

    outputStream.write(buffer, 0, readLength);

    /* check remote abort */ 

    responseCode = putOperation.getResponseCode();

    if (responseCode != ResponseCodes.OBEX_HTTP_CONTINUE && responseCode != ResponseCodes.OBEX_HTTP_OK) {

        okToProceed = false;

    } else {

        position += readLength;

        //更新进度

        updateValues = new ContentValues();

        updateValues.put(BluetoothShare.CURRENT_BYTES, position);    

        mContext1.getContentResolver().update(contentUri, updateValues, null, null);     

    } 

}

在之后就是一些状态的处理了。到此通过蓝牙分享文件到流程基本上过了一遍,其中还有许多状态、进度等相关功还没能研究透彻,之后再继续研究。

https://mp.weixin.qq.com/s/0rkndSo4jIw2tQ19R3OOQg