君不见长松卧壑困风霜,时来屹立扶明堂。这篇文章主要讲述Android 蓝牙开发OPP传输文件相关的知识,希望能为你提供帮助。
转载请注明出处:
http://blog.csdn.net/vnanyesheshou/article/details/70256004
本文已授权微信公众号 fanfan程序媛 独家发布 扫一扫文章底部的二维码或在微信搜索 fanfan程序媛 即可关注android蓝牙功能( 传统蓝牙、ble、hid) 这三方面功能之前的博客都已经写了。现在接着了解蓝牙OPP传输文件相关功能。Android手机使用中, 经常会用到通过蓝牙分享文件给附近的朋友。那么具体是如何实现的, 大部分朋友都不是很清楚。看一下源码是如何实现该功能的。
1 BluetoothOppLauncherActivity |
具体文件: packages/apps/Bluetooth/src/com/android/bluetooth/opp/BluetoothOppLauncherActivity.java
看一下BluetoothOppLauncherActivity是如何处理分享文件请求的。
if (action.equals(Intent.ACTION_SEND) || action.equals(Intent.ACTION_SEND_MULTIPLE)) {
//Check if Bluetooth is available in the beginning instead of at the end
if (!isBluetoothAllowed()) {
Intent in =
new Intent(this, BluetoothOppBtErrorActivity.class);
in.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
in.putExtra("
title"
, this.getString(R.string.airplane_error_title));
in.putExtra("
content"
, this.getString(R.string.airplane_error_msg));
startActivity(in);
finish();
return;
}
//..........下面接着说。
}
BluetoothOppLauncherActivity并没有界面(没有setContentView), 只是一个中转站,它根据当前蓝牙等相关状态进行跳转。Intent.ACTION_SEND和Intent.ACTION_SEND_MULTIPLE的区别是前者表示单个文件, 后者表示多个文件。这里只研究下分享单个文件, 分享单个文件懂了, 多个文件道理类似。
其中isBluetoothAllowed()函数会先判断飞行模式是否开启, 如果没有开启则返回true。如果开启, 则进行下一步判断飞行模式是否重要, 如果不重要则返回true( 说明蓝牙可以使用) 。如果重要则继续分析飞行模式下是否可以打开蓝牙, 可以打开蓝牙则返回true, 否则返回false。总的来说该函数就是判断当前蓝牙是否允许使用。不允许使用蓝牙则跳转到BluetoothOppBtErrorActivity。
接着向下:
if (action.equals(Intent.ACTION_SEND)) { //单个文件
final String type =
intent.getType();
final Uri stream =
(Uri)intent.getParcelableExtra(Intent.EXTRA_STREAM);
CharSequence extra_text =
intent.getCharSequenceExtra(Intent.EXTRA_TEXT);
if (stream !=
null &
&
type !=
null) { //分享文件
Thread t =
new Thread(new Runnable() {
public void run() {
BluetoothOppManager.getInstance(BluetoothOppLauncherActivity.this)
.saveSendingFileInfo(type,stream.toString(), false);
launchDevicePicker();
finish();
}
});
t.start();
return;
} else if (extra_text !=
null &
&
type !=
null) { //分享text字符串,
没有文件
final Uri fileUri =
creatFileForSharedContent(this, extra_text);
//创建文件,将内容写入文件
if (fileUri !=
null) {
Thread t =
new Thread(new Runnable() {
public void run() {
BluetoothOppManager.getInstance(BluetoothOppLauncherActivity.this)
.saveSendingFileInfo(type,fileUri.toString(), false);
launchDevicePicker();
finish();
}
});
t.start();
return;
}
//.........
}
使用过Android系统分享的应该知道, 其支持文件(图片、视频等)、字符串。而这里会对文件、字符串进行区分处理, 字符串则先创建文件然后在进行分享。
launchDevicePicker()函数中先判断蓝牙是否开启。
如果蓝牙没有开启则跳转到BluetoothOppBtEnableActivity显示dialog( 询问是否开启蓝牙) , 点击取消则则退出, 点击打开则打开蓝牙并跳到BluetoothOppBtEnablingActivity( 该activity主要显示一个progress dialog) 。当蓝牙打开, 则BluetoothOppBtEnablingActivity 界面finish。BluetoothOppReceiver广播接收者接收到蓝牙开启, 跳转到DevicePickerActivity界面( 系统Settings应用) 。
如果蓝牙已开启, 则直接跳转到跳转到DevicePickerActivity界面( 系统Settings应用) 。
launchDevicePicker()下的跳转代码:
//ACTION_LAUNCH=
"
android.bluetooth.devicepicker.action.LAUNCH"
Intent in1 =
new Intent(BluetoothDevicePicker.ACTION_LAUNCH);
in1.setFlags(Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS);
in1.putExtra(BluetoothDevicePicker.EXTRA_NEED_AUTH, false);
in1.putExtra(BluetoothDevicePicker.EXTRA_FILTER_TYPE,
BluetoothDevicePicker.FILTER_TYPE_TRANSFER);
in1.putExtra(BluetoothDevicePicker.EXTRA_LAUNCH_PACKAGE,
Constants.THIS_PACKAGE_NAME);
in1.putExtra(BluetoothDevicePicker.EXTRA_LAUNCH_CLASS,
BluetoothOppReceiver.class.getName());
startActivity(in1);
系统Settings应用中AndroidManifest.xml中发现对应action的DevicePickerActivity, 所以该跳转会跳转到系统Settings应用中的DevicePickerActivity中。
<
activity android:name=
"
.bluetooth.DevicePickerActivity"
android:uiOptions=
"
splitActionBarWhenNarrow"
android:theme=
"
@
android:style/Theme.Holo.DialogWhenLarge"
android:label=
"
@
string/device_picker"
android:clearTaskOnLaunch=
"
true"
>
<
intent-filter>
<
action android:name=
"
android.bluetooth.devicepicker.action.LAUNCH"
/>
<
category android:name=
"
android.intent.category.DEFAULT"
/>
<
/intent-filter>
<
/activity>
2 DevicePicker |
setContentView(R.layout.bluetooth_device_picker);
bluetooth_device_picker.xml中有一个fragment指向DevicePickerFragment, 也就是主要的处理在DevicePickerFragment中。
DevicePickerFragment界面会显示出配对、扫描到的蓝牙列表。可以点击一个设备进行分享文件。
void onDevicePreferenceClick(BluetoothDevicePreference btPreference) {
mLocalAdapter.stopScanning();
//停止扫描
LocalBluetoothPreferences.persistSelectedDeviceInPicker(
getActivity(), mSelectedDevice.getAddress());
if ((btPreference.getCachedDevice().getBondState() =
=
BluetoothDevice.BOND_BONDED) || !mNeedAuth) {
sendDevicePickedIntent(mSelectedDevice);
finish();
} else {
super.onDevicePreferenceClick(btPreference);
}
}
点击设备, 会判断是否是绑定状态, 或者mNeedAuth为false。mNeedAuth是通过intent传过来的值为false。所以满足条件。
接着看sendDevicePickedIntent()。该函数就是发了一个广播。
private void sendDevicePickedIntent(BluetoothDevice device) {
//"
android.bluetooth.devicepicker.action.DEVICE_SELECTED"
Intent intent =
new Intent(BluetoothDevicePicker.ACTION_DEVICE_SELECTED);
intent.putExtra(BluetoothDevice.EXTRA_DEVICE, device);
if (mLaunchPackage !=
null &
&
mLaunchClass !=
null) {
intent.setClassName(mLaunchPackage, mLaunchClass);
}
getActivity().sendBroadcast(intent);
}
3 BluetoothOppReceiver |
<
receiver
android:process=
"
@
string/process"
android:exported=
"
true"
android:name=
"
.opp.BluetoothOppReceiver"
android:enabled=
"
@
bool/profile_supported_opp"
>
<
intent-filter>
<
action android:name=
"
android.bluetooth.adapter.action.STATE_CHANGED"
/>
<
!--action android:name=
"
android.intent.action.BOOT_COMPLETED"
/-->
<
action android:name=
"
android.btopp.intent.action.OPEN_RECEIVED_FILES"
/>
<
/intent-filter>
<
/receiver>
【Android 蓝牙开发OPP传输文件】BluetoothOppReceiver收到此广播后的主要处理代码如下, 将此条记录添加到数据库。
// Insert transfer session record to database
mOppManager.startTransfer(remoteDevice);
BluetoothOppManager对象调用startTransfer方法。在startTransfer方法中创建一个InsertShareInfoThread线程并开始运行。
InsertShareInfoThread线程中区分分享的是一个文件还是多个文件。我们这里只看下处理单个文件insertSingleShare()函数。
if (mIsMultiple) {//多个文件
insertMultipleShare();
} else { //单个文件
insertSingleShare();
}private void insertSingleShare() {
ContentValues values =
new ContentValues();
values.put(BluetoothShare.URI, mUri);
values.put(BluetoothShare.MIMETYPE, mTypeOfSingleFile);
values.put(BluetoothShare.DESTINATION, mRemoteDevice.getAddress());
if (mIsHandoverInitiated) {
values.put(BluetoothShare.USER_CONFIRMATION,
BluetoothShare.USER_CONFIRMATION_HANDOVER_CONFIRMED);
}
final Uri contentUri =
mContext.getContentResolver().insert(BluetoothShare.CONTENT_URI,
values);
}
由mContext.getContentResolver().insert()可知其有对应的provider。BluetoothOppProvider继承了ContextProvider。查看BluetoothOppProvider中的insert方法。
public Uri insert(Uri uri, ContentValues values) {
.....
if (rowID !=
-1) {
context.startService(new Intent(context, BluetoothOppService.class));
ret =
Uri.parse(BluetoothShare.CONTENT_URI +
"
/"
+
rowID);
context.getContentResolver().notifyChange(uri, null);
}
由上可知通过蓝牙分享的时候会start BluetoothOppService。
4 BluetoothOppService |
updateFromProvider() -> 创建线程UpdateThread -> insertShare()。
private void insertShare(Cursor cursor, int arrayPos) {
if (info.isReadyToStart()) {
if (info.mDirection =
=
BluetoothShare.DIRECTION_OUTBOUND) { //向外分享、发送
/* 检查文件是否存在 */
}
}
if (mBatchs.size() =
=
0) {
if (info.mDirection =
=
BluetoothShare.DIRECTION_OUTBOUND){//向外分享、发送
mTransfer =
new BluetoothOppTransfer(this, mPowerManager, newBatch);
} else if (info.mDirection =
=
BluetoothShare.DIRECTION_INBOUND) { //接收
mServerTransfer =
new BluetoothOppTransfer(this, mPowerManager, newBatch,
mServerSession);
}if (info.mDirection =
=
BluetoothShare.DIRECTION_OUTBOUND &
&
mTransfer !=
null) {
mTransfer.start();
} else if (info.mDirection =
=
BluetoothShare.DIRECTION_INBOUND
&
&
mServerTransfer !=
null) {
mServerTransfer.start();
}
}
//........
}
5 BluetoothOppTransfer |
public void start() {
//检查蓝牙是否打开,
保证安全
if (!mAdapter.isEnabled()) {
return;
}
if (mHandlerThread =
=
null) {
//......
if (mBatch.mDirection =
=
BluetoothShare.DIRECTION_OUTBOUND) {
/* for outbound transfer, we do connect first */
startConnectSession();
}
//....
}
}
startConnectSession()函数中开始向远端设备进行连接, 该函数中主要就是创建SocketConnectThread线程, 用来连接其他设备。
SocketConnectThread线程主要代码:
try { //创建BluetoothSocket
btSocket =
device.createInsecureRfcommSocketToServiceRecord(BluetoothUuid.ObexObjectPush.getUuid());
} catch (IOException e1) {//....
}
try {
btSocket.connect();
//;
连接设备
BluetoothOppRfcommTransport transport;
transport =
new BluetoothOppRfcommTransport(btSocket);
BluetoothOppPreference.getInstance(mContext).setName(device, device.getName());
mSessionHandler.obtainMessage(RFCOMM_CONNECTED, transport).sendToTarget();
} catch (IOException e) {//....
}
这里先创建BluetoothSocket, 然后通过BluetoothSocket进行连接。
连接成功后, startObexSession()-> new BluetoothOppObexClientSession -> BluetoothOppObexClientSession .start()
6 BluetoothOppObexClientSession |
start() -> 创建ClientThread线程并运行 -> connect()。
在connect()函数中, 通过mTransport1( BluetoothOppRfcommTransport类型, 该类型中主要包含之前创建的BluetoothSocket) 对象, 创建client session, 连接远端设备。
private void connect(int numShares) {
try {//创建obex client
mCs =
new ClientSession(mTransport1);
mConnected =
true;
} catch (IOException e1) {
}
if (mConnected) {
mConnected =
false;
HeaderSet hs =
new HeaderSet();
//obex 连接携带信息
hs.setHeader(HeaderSet.COUNT, (long) numShares);
//文件数量
synchronized (this) {
mWaitingForRemote =
true;
}
try { //obex连接
mCs.connect(hs);
mConnected =
true;
} catch (IOException e) {
}
}
//.....
}
obex连接成功后, 调用doSend(),该函数中先检查下文件是否存在, 然后查看连接状态, 连接状态下并且存在文件则sendFile才真正的开始发送文件。之会将相应的状态发送到BluetoothOppTransfer中。
private void doSend() {
int status =
BluetoothShare.STATUS_SUCCESS;
while (mFileInfo =
=
null) { //检查文件是否存在
try {
Thread.sleep(50);
} catch (InterruptedException e) {
status =
BluetoothShare.STATUS_CANCELED;
}
}
//检查连接状态
if (!mConnected) {
status =
BluetoothShare.STATUS_CONNECTION_ERROR;
}
if (status =
=
BluetoothShare.STATUS_SUCCESS) {
/* 发送文件*/
if (mFileInfo.mFileName !=
null) {
status =
sendFile(mFileInfo);
} else {
status =
mFileInfo.mStatus;
}
waitingForShare =
true;
} else {
Constants.updateShareStatus(mContext1, mInfo.mId, status);
}
//发送此次操作是否成功等信息。
}
真正的发送文件是在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);
}
}
在之后就是一些状态的处理了。到此通过蓝牙分享文件到流程基本上过了一遍, 其中还有许多状态、进度等相关功还没能研究透彻, 之后再继续研究。
欢迎扫一扫关注我的微信公众号, 定期推送优质技术文章:
文章图片
推荐阅读
- 学生赚app开发系统平台
- 黎活明8天快速掌握android视频教程--17_创建数据库与完成数据添删改查
- android中调用相机拍照返回null的问题的终极解决办法
- 消费全返商城开发app平台java
- android 客户端增量更新
- android http post
- 随机扫描和光栅扫描显示的区别
- 如何使用Python列出FTP服务器中的所有文件和目录()
- 如何在Python中读取电子邮件(详细示例教程)