android -- 蓝牙 bluetooth (四)OPP文件传输
在前面android -- 蓝牙 bluetooth (一) 入门文章结尾中提到了会按四个方面来写这系列的文章,前面已写了蓝牙打开和蓝牙搜索,这次一起来看下蓝牙文件分享的流程,也就是蓝牙应用opp目录下的代码,作为蓝牙最基本的一个功能,这部分的代码在之前的版本中就已经有了,新旧版本代码对比很多类名都是一样的,这一部分新东西不多,写在这里帮助大家梳理下流程吧。
有没有这种感觉,智能手机的普及让我们提高了一点对蓝牙的关注,手机间使用蓝牙互传文件应该是最常用的应用之一,手机与电脑也可以通过蓝牙做同样的事情,大部分笔记本都支持蓝牙功能,本本上蓝牙芯片多数是broadcom的,也有其它厂商(比如东芝)不过数量不多,毕竟broadcom在BT这方面是老大。不过本本上蓝牙一般只支持蓝牙耳机听歌,并没实现对opp的支持,如果体验下手机与电脑的蓝牙文件传输怎么办呢,安装一个叫bluesoleil(中文名好像是千月)软件就可以了,这个软件对蓝牙功能的支持还是比较全的。可能需要卸载本本自带蓝牙驱动。扯淡结束,本文还是要关注手机间蓝牙opp的代码流程,这段的废话也许能帮助你提高下对蓝牙的体验。
蓝牙发送文件时发送端先来到这里packages/apps/Bluetooth/src/com/android/bluetooth/opp/BluetoothOppLauncherActivity.java,一个没有界面只是提取下文件信息的中转站,源码的注释写的很清楚了,两个分支action.equals(Intent.ACTION_SEND)和action.equals(Intent.ACTION_SEND_MULTIPLE)
- 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;
- }
- if (action.equals(Intent.ACTION_SEND)) {
- .......
- Thread t = new Thread(new Runnable() {
- public void run() {
- BluetoothOppManager.getInstance(BluetoothOppLauncherActivity.this)
- .saveSendingFileInfo(type,fileUri.toString(), false);
- //Done getting file info..Launch device picker
- //and finish this activity
- launchDevicePicker();
- finish();
- }
- }); ......
- } else if (action.equals(Intent.ACTION_SEND_MULTIPLE)) {
- .......
- }
最前面那个isBluetoothAllowed()会判断是否处于飞行模式,如果是会禁止发送的。在launchDevicePicker()里还会判断蓝牙是否已经打开,就是下面这个条件语句(!BluetoothOppManager.getInstance(this).isEnabled())。如果已经打开了蓝牙,如果蓝牙打开了就进入设备选择界面DeviceListPreferenceFragment(DevicePickerFragment)选择设备,这个跳转过程简单说明下,注意这个new Intent(BluetoothDevicePicker.ACTION_LAUNCH)里字符串,完整定义public static final String ACTION_LAUNCH = "android.bluetooth.devicepicker.action.LAUNCH";路径frameworks/base/core/java/android/bluetooth/BluetoothDevicePicker.java,你会在setting应用的manifest.xml里发现
- <activity android:name=".bluetooth.DevicePickerActivity"
- 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>
这样目标就指向了DevicePickerActivity,注意此时它的代码路径是packages/apps/Settings/src/com/android/settings/bluetooth/DevicePickerActivity.java,这个类代码很简单,只有一个onCreate并只在里加载了一个布局文件bluetooth_device_picker.xml,就是这个布局文件指明下一站在哪,看下面就知道怎么来到DevicePickerFragment了
- <fragment
- android:id="@+id/bluetooth_device_picker_fragment"
- android:name="com.android.settings.bluetooth.DevicePickerFragment"
- android:layout_width="match_parent"
- android:layout_height="0dip"
- android:layout_weight="1" />
到了这里,已经可看到配对过的蓝牙列表了,选择其中一个点击会来到这里,里面那个sendDevicePickedIntent是我们关心的,又发了一个广播,去找谁收了广播就好了
- 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);
- }
- }<div> public static final String ACTION_LAUNCH = "android.bluetooth.devicepicker.action.LAUNCH";
- private void sendDevicePickedIntent(BluetoothDevice device) {
- 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);}
- </div>
通过BluetoothDevicePicker.ACTION_DEVICE_SELECTED查找,会在/packages/apps/Bluetooth/src/com/android/bluetooth/opp/BluetoothOppReceiver.java这个找到对该广播的处理,也就是下面的代码:
- else if (action.equals(BluetoothDevicePicker.ACTION_DEVICE_SELECTED)) {
- BluetoothOppManager mOppManager = BluetoothOppManager.getInstance(context);
- BluetoothDevice remoteDevice = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);
- // Insert transfer session record to database
- mOppManager.startTransfer(remoteDevice);
- // Display toast message
- String deviceName = mOppManager.getDeviceName(remoteDevice);
- .......
- }
看来关键代码是mOppManager.startTransfer(remoteDevice),在packages/apps/Bluetooth/src/com/android/bluetooth/opp/BluetoothOppManager.java,里面开启线程执行发送动作,既然是开启线程,直接去看run方法就是了,方法里面依旧区分单个和多个文件的发送,看一个就可以。
- public void startTransfer(BluetoothDevice device) {
- if (V) Log.v(TAG, "Active InsertShareThread number is : " + mInsertShareThreadNum);
- InsertShareInfoThread insertThread;
- synchronized (BluetoothOppManager.this) {
- if (mInsertShareThreadNum > ALLOWED_INSERT_SHARE_THREAD_NUMBER) {
- ...........
- return;
- }
- insertThread = new InsertShareInfoThread(device, mMultipleFlag, mMimeTypeOfSendingFile,
- mUriOfSendingFile, mMimeTypeOfSendingFiles, mUrisOfSendingFiles,
- mIsHandoverInitiated);
- if (mMultipleFlag) {
- mfileNumInBatch = mUrisOfSendingFiles.size();
- }
- }
- insertThread.start();
- }
- public void run() {
- Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);
- ..........
- if (mIsMultiple) {
- insertMultipleShare();
- } else {
- insertSingleShare();
- }
- .......... }
以insertSingleShare() 为例,在它的实现会看到mContext.getContentResolver().insert,不多想了,要去provider里找到insert()函数了,
对应的代码在BluetoothOppProvider.java (bluetooth\src\com\android\bluetooth\opp),insert的函数实现如下,里面又拉起BluetoothOppService,开始还以为只是针对数据库的操作,差点错过了风景。路径/packages/apps/Bluetooth/src/com/android/bluetooth/opp/BluetoothOppService.java
- 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);
- } else {
- if (D) Log.d(TAG, "couldn't insert into btopp database");
- }
在BluetoothOppService的onStartCommand方法中会看到updateFromProvider(),这里又开启了一个线程UpdateThread,后续代码当然是看它的run方法了,这里面内容不少,好在这部分代码注释比较多,理解起来不难。先暂时只关心发送的动作insertShare方法,代码也不少,只贴出了告诉我们接下来去哪里的代码和有关的逻辑注释,在下面的代码我们可以看到 BluetoothOppTransfer.java的对象,下一站就是它了。
- private void insertShare(Cursor cursor, int arrayPos) {
- .........
- /*
- * Add info into a batch. The logic is
- * 1) Only add valid and readyToStart info
- * 2) If there is no batch, create a batch and insert this transfer into batch,
- * then run the batch
- * 3) If there is existing batch and timestamp match, insert transfer into batch
- * 4) If there is existing batch and timestamp does not match, create a new batch and
- * put in queue
- */
- if (info.isReadyToStart()) {
- .............
- if (mBatchs.size() == 0) {
- ........
- mBatchs.add(newBatch);
- 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();
- }
- } else {
- .........
- }}
虽然名字是start(),可实际并不是什么线程的,就是一普通方法的,路径是/packages/apps/Bluetooth/src/com/android/bluetooth/opp/BluetoothOppTransfer.java
- public void start() {
- ....这里省略未贴的代码是检查蓝牙是否打开,一个很谨慎的判断。看似无用,不过还是安全第一。
- if (mHandlerThread == null) {
- ........
- if (mBatch.mDirection == BluetoothShare.DIRECTION_OUTBOUND) {
- /* for outbound transfer, we do connect first */
- startConnectSession();
- } else if (mBatch.mDirection == BluetoothShare.DIRECTION_INBOUND) {
- /*
- * for inbound transfer, it's already connected, so we start
- * OBEX session directly
- */
- startObexSession();
- }
- }
- }
上面的代码是分发送文件和接收文件的,看下这两行代码就很清楚了,如果分享给别人是OUTBOUND,先执行startConnectSession(),这个函数最后还是要跑到startObexSession()这里的,如果收文件直接startObexSession,所以后面就只看startObexSession方法了
- // This transfer is outbound, e.g. share file to other device.
- public static final int DIRECTION_OUTBOUND = 0;
- // This transfer is inbound, e.g. receive file from other device.
- public static final int DIRECTION_INBOUND = 1;
还是在同一个类里,发送流程快结束了,同样区分是传入还是传出,发文件看OUTBOUND,去BluetoothOppObexClientSession.java
- private void startObexSession() {
- if (mBatch.mDirection == BluetoothShare.DIRECTION_OUTBOUND) {
- if (V) Log.v(TAG, "Create Client session with transport " + mTransport.toString());
- mSession = new BluetoothOppObexClientSession(mContext, mTransport);
- } else if (mBatch.mDirection == BluetoothShare.DIRECTION_INBOUND) {
- if (mSession == null) {
- markBatchFailed();
- mBatch.mStatus = Constants.BATCH_STATUS_FAILED;
- return;
- }
- if (V) Log.v(TAG, "Transfer has Server session" + mSession.toString());
- }
- mSession.start(mSessionHandler);
- processCurrentShare();
- }
同样名字是start,实际只是一个普通方法而已,会看又是一个线程 mThread = new ClientThread(mContext, mTransport),这时的start才是线程的start(),还是看run方法,一些线程状态的判断,看到doSend() 就是了,直正的发送在这里packages/apps/Bluetooth/src/com/android/bluetooth/opp/BluetoothOppObexClientSession.java,
- private void doSend() {
- int status = BluetoothShare.STATUS_SUCCESS;
- ........关于status值的判断
- if (status == BluetoothShare.STATUS_SUCCESS) {
- /* do real send */ //看到这个注释了没,它才是真家伙sendFile
- if (mFileInfo.mFileName != null) {
- status = sendFile(mFileInfo);
- } else {
- /* this is invalid request */
- status = mFileInfo.mStatus;
- }
- waitingForShare = true;
- } else {
- Constants.updateShareStatus(mContext1, mInfo.mId, status);
- }
- if (status == BluetoothShare.STATUS_SUCCESS) {
- Message msg = Message.obtain(mCallback);
- msg.what = BluetoothOppObexSession.MSG_SHARE_COMPLETE;
- msg.obj = mInfo;
- msg.sendToTarget();
- } else {
- Message msg = Message.obtain(mCallback);
- msg.what = BluetoothOppObexSession.MSG_SESSION_ERROR;
- mInfo.mStatus = status;
- msg.obj = mInfo;
- msg.sendToTarget();
- }
- }
sendFile是真正干活的,执行完sendFile会把分享成功或失败的消息传回去,sendFile里会执行打包的过程,对于字段的含义要看Headset.java,
代码路径在frameworks/base/obex/javax/obex/HeaderSet.java。这个sendFile方法行数虽然多,不过逻辑还是比较清晰的,在这里就不贴了。到这蓝牙发送文件流程也就此结束。由于发送文件时长肯定是不确定,所以在这个流程我们看到了很多开启线程代码也是很正常的,对于这线程,直接看对应的run方法就是了。
对于蓝牙接收文件时会收到MSG_INCOMING_BTOPP_CONNECTION消息,收到这个消息是由于在蓝牙打开,即蓝牙状态是 BluetoothAdapter.STATE_ON时会执行
startSocketListener(),在这个函数开启了监听程序,看下面贴在一起的代码就明白了,
- if (action.equals(BluetoothAdapter.ACTION_STATE_CHANGED)) {
- switch (intent.getIntExtra(BluetoothAdapter.EXTRA_STATE, BluetoothAdapter.ERROR)) {
- case BluetoothAdapter.STATE_ON:
- if (V) Log.v(TAG,"Receiver BLUETOOTH_STATE_CHANGED_ACTION, BLUETOOTH_STATE_ON");
- startSocketListener();
- break;
- private void startSocketListener() {
- if (V) Log.v(TAG, "start RfcommListener");
- mSocketListener.start(mHandler);
- if (V) Log.v(TAG, "RfcommListener started");
- }
- mSocketListener.start(mHandler);这个的实现在这里,比较长,没有贴上来
- /packages/apps/Bluetooth/src/com/android/bluetooth/opp/BluetoothOppRfcommListener.java
回到上面处理消息,在BluetoothOppService.java的handlemessage中这个分支 case BluetoothOppRfcommListener.MSG_INCOMING_BTOPP_CONNECTION, 创建一个 createServerSession(transport); 最后走/frameworks/base/obex/javax/obex/ServerSession.java的run方法中接收数据
- private void createServerSession(ObexTransport transport) {
- mServerSession = new BluetoothOppObexServerSession(this, transport);
- mServerSession.preStart();
- }
对于蓝牙接收文件部分的流程还没有细致的跟踪,暂时只看到这里,对于了解基本流程这此应该够用了,同时如果想更好理解蓝牙OPP文件传输,了解是OBEX基础协议也是有必要的,网上资料还是有不少的,多数是论文形式的。对于蓝牙OPP部分,本文只是描述android代码中的流程,旨在帮你快速的理清流程,本文对OPP本身并没有深入,相关的知识需要进一步学习才行,有同道先行的童鞋还望赐教一二,谢谢。
android -- 蓝牙 bluetooth (四)OPP文件传输的更多相关文章
- ZT android -- 蓝牙 bluetooth (四)OPP文件传输
android -- 蓝牙 bluetooth (四)OPP文件传输 分类: Android的原生应用分析 2013-06-22 21:51 2599人阅读 评论(19) 收藏 举报 4.2源码AND ...
- ZT android -- 蓝牙 bluetooth (五)接电话与听音乐
android -- 蓝牙 bluetooth (五)接电话与听音乐 分类: Android的原生应用分析 2013-07-13 20:53 2165人阅读 评论(9) 收藏 举报 蓝牙android ...
- ZT android -- 蓝牙 bluetooth (一) 入门
android -- 蓝牙 bluetooth (一) 入门 分类: Android的原生应用分析 2013-05-19 21:44 4543人阅读 评论(37) 收藏 举报 bluetooth4.2 ...
- android -- 蓝牙 bluetooth (三)搜索蓝牙
接上篇打开蓝牙继续,来一起看下蓝牙搜索的流程,触发蓝牙搜索的条件形式上有两种,一是在蓝牙设置界面开启蓝牙会直接开始搜索,另一个是先打开蓝牙开关在进入蓝牙设置界面也会触发搜索,也可能还有其它触发方式,但 ...
- 深入了解Android蓝牙Bluetooth——《进阶篇》
在 [深入了解Android蓝牙Bluetooth--<基础篇>](http://blog.csdn.net/androidstarjack/article/details/6046846 ...
- ZT android -- 蓝牙 bluetooth (三)搜索蓝牙
android -- 蓝牙 bluetooth (三)搜索蓝牙 分类: Android的原生应用分析 2013-05-31 22:03 2192人阅读 评论(8) 收藏 举报 bluetooth蓝牙s ...
- ZT android -- 蓝牙 bluetooth (二) 打开蓝牙
android -- 蓝牙 bluetooth (二) 打开蓝牙 分类: Android的原生应用分析 2013-05-23 23:57 4773人阅读 评论(20) 收藏 举报 androidblu ...
- 深入了解Android蓝牙Bluetooth ——《总结篇》
在我的上两篇博文中解说了有关android蓝牙的认识以及API的相关的介绍,蓝牙BLE的搜索,连接以及读取. 没有了解的童鞋们请參考: 深入了解Android蓝牙Bluetooth--<基础篇& ...
- android -- 蓝牙 bluetooth (一) 入门
前段时间在 网上看了一些关于android蓝牙的文章,发现大部分是基于老版本(4.1以前含4.1)的源码,虽然无碍了解蓝牙的基本原理和工作流程,但对着4.2.2的代码看起来总是有些遗憾.所以针对4.2 ...
随机推荐
- css伪类伪元素
在CSS中,模式(pattern)匹配规则决定哪种样式规则应用于文档树(document tree)的哪个元素.这些模式叫着选择符(selector). 一条CSS规则(rule)是选择符{属性:值; ...
- crt连接vitualbox中centos虚拟机
在virtalbox中安装了centos虚拟机后,在虚拟机中直接操作很是不方便,所以想用crt连接虚拟机, 1.打开virtualbox,设置-网络,网络连接2设置连接方式为“Bridged Adap ...
- php ajax提交数据 在本地可以执行,而在服务器不能执行
1.排除是服务器的问题 把单独的ajax项目传到服务器上,可以正常返回xml数据 2.排除是项目下的限制问题 把单独的ajax放在相应的项目文件夹下,单独访问该ajax发送数据的页面,能够正常执行 3 ...
- Windows Azure 网站自愈
编辑人员注释:本文章由 Windows Azure 网站团队的项目经理Apurva Joshi 撰写. 您有多少次在半夜被叫醒去解决一个仅需重新启动网站即可解决的问题?要是可以自动检测一些状况并自动恢 ...
- HDU 3571 N-dimensional Sphere
高斯消元,今天数学死了无数次…… #include <cstdio> #include <cstring> #include <cmath> #include &l ...
- installation - How to install Synaptic Package Manager? - Ask Ubuntu
installation - How to install Synaptic Package Manager? - Ask Ubuntu How to install Synaptic Package ...
- Objective-c 协议(protocol)
协议的作用类似地C++中对抽象基类的多重继承.类似于Java中的接口(interface)的概念. 协议是多个类共享方法的列表,协议中列出的方法在本类中并没有相应实现,而是别的类来实现这些方法. ...
- Java之SPI机制
之前开阿里的HSF框架,里面用到了Java的SPI机制,今天闲暇的时候去了解了一下,通过写博客来记录一下 SPI的全名为Service Provider Interface,我对于该机制的理解是为接口 ...
- Java之JDOM生成XML和解析
一.生成XML文件 1.JDOM是对Java原始的类进行了封装.让解析XML文件变得很方便 2.创建一个XML文件的根节点: Element root = new Element("HD&q ...
- filter, sort
def is_odd(n): return n % 2 == 1 t = list(filter(is_odd, [1, 2, 3, 4, 5, 6, 7, 8, 9, 0])) print(t) # ...