蓝牙文件分享源码全解析

本文深入探讨了蓝牙文件分享的全过程,从Android系统蓝牙模块的实现到文件读取、Socket发送,涉及BluetoothOppObexClientSession的内部类ClientThread的sendFile方法。通过流程图解析,展示了从启动传输到数据发送的细节,并提到了相关硬件接口和传输协议。同时,推荐了相关博客以了解更详细的蓝牙和ContentProvider知识。
摘要由CSDN通过智能技术生成

     最近这段时间,领导在谈北美项目,如果前期的谈判完成了,我们这边估计就要大忙了,有时候想想,到了差不多四十多岁的时候,如果能搞到像我们老大那样子,手下百十号人,完全可以基于原生的东西进行一番改造,弄出一个OS了,这样的职业生涯应该还算可以吧!当然这里所说的OS其实本质还不是真正的OS,就像华为的EMUI、小米的MIUI一样,只是对界面、控件样式进行修改的系统了。

     上一次处理那个Beam分享的问题单,自己也一直没完全放下,Beam最终使用的还是蓝牙的传输功能,上一节课我们也分析了Okhttp文件上传的全过程,可以看到,在底层的实现就是通过Socket来实现的,那蓝牙底层是如何实现这整个过程的呢?今天我们就带着这个问题来看看蓝牙模块文件传输的全过程。

     下面的流程图是我整理出来的,可以看到整个过程非常长,但是看看代码就会发现,其实这里的调用逻辑还是比较简单的,基本上都是一些封装。真下执行文件传输的地方是在BluetoothOppObexClientSession的内部类ClientThread的sendFile方法中完成的,流程图中也用红色标注出来了,我的流程图是用visio画的,如果大家感觉图片看不清,也可以去下载我的源文件,地址如下:

     蓝牙文件分享流程解析

     那么接下来,我们就一起来看看中间的调用过程。


     关于蓝牙这块的东西,肯定还会涉及到硬件方面的东西,各种传输协议什么的,在网上查了下,下面的博客也写的非常好,推荐大家看一下:

     Android bluetooth介绍(一):基本概念及硬件接口

     那么在开始之前,我们还需要说一点,我们从图库中执行文件分享,传递到蓝牙模块的是一个文件的Uri,在传送前,目标图片的uri已经在BluetoothOppReceiver类的BluetoothAdapter.ACTION_STATE_CHANGED广播当中接收进来了,接收到要传送的uri数据后,调用BluetoothOppManager类的storeApplicationData方法将数据保存在了OPP_PREFERENCE_FILE文件SharedPreferences当中。大家将蓝牙模块的data目录下com.android.bluetooth完整的目录pull出来,看一下它的SharedPreferences。


     目标文件的Uri就保存在OPPMGR文件当中,打开它,可以看到内容如下:


     FILE_URI就是图库进程传递过来的目标文件的Uri,那么我们下面要作的事情就是根据这个Uri,把文件读取出来,解析成流,然后通过Socket发送出去。我们开始的操作是在蓝牙选择界面,会显示所有的可见的蓝牙设备,这个界面对应的是packages/apps/Settings/src/com/android/settings/bluetooth/DevicePickerFragment类,当我们点击一个设备条目后,会执行onDevicePreferenceClick方法响应点击,这个方法是调用sendDevicePickedIntent方法来发送一个BluetoothDevicePicker.ACTION_DEVICE_SELECTED广播的,sendDevicePickedIntent方法的实现如下:

    private void sendDevicePickedIntent(BluetoothDevice device) {
        mDeviceSelected = true;
        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);
    }
     这里就是我们使用的非常普遍的发送广播了,那么广播在经过系统AMS的包装传递,到BroadcastQueue中再进一步处理,最终会调用BroadcastQueue类的performReceiveLocked方法,当中执行app.thread.scheduleRegisteredReceiver将广播发到应用进程当中,app.thread也就是对应我们应用主线程的ActivityThread当中的binder客户端的ApplicationThread对象了,那么它又调用了receiver.performReceive来进一步处理,这里的receiver是一个IIntentReceiver类型的对象,它的performReceive方法是由LoadedApk类的内部类InnerReceiver来实现的,再它当中又构造了一个Args对象,然后执行它的run方法,run方法当中就会回调onReceive将广播最终发到目标接收者身上,我们把这个方法的代码贴出来看一下:

            public void run() {
                final BroadcastReceiver receiver = mReceiver;
                final boolean ordered = mOrdered;
                
                if (ActivityThread.DEBUG_BROADCAST) {
                    int seq = mCurIntent.getIntExtra("seq", -1);
                    Slog.i(ActivityThread.TAG, "Dispatching broadcast " + mCurIntent.getAction()
                            + " seq=" + seq + " to " + mReceiver);
                    Slog.i(ActivityThread.TAG, "  mRegistered=" + mRegistered
                            + " mOrderedHint=" + ordered);
                }
                
                final IActivityManager mgr = ActivityManagerNative.getDefault();
                final Intent intent = mCurIntent;
                mCurIntent = null;
                
                if (receiver == null || mForgotten) {
                    if (mRegistered && ordered) {
                        if (ActivityThread.DEBUG_BROADCAST) Slog.i(ActivityThread.TAG,
                                "Finishing null broadcast to " + mReceiver);
                        sendFinished(mgr);
                    }
                    return;
                }

                Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "broadcastReceiveReg");
                try {
                    ClassLoader cl =  mReceiver.getClass().getClassLoader();
                    intent.setExtrasClassLoader(cl);
                    setExtrasClassLoader(cl);
                    receiver.setPendingResult(this);
                    receiver.onReceive(mContext, intent);
                } catch (Exception e) {
                    if (mRegistered && ordered) {
                        if (ActivityThread.DEBUG_BROADCAST) Slog.i(ActivityThread.TAG,
                                "Finishing failed broadcast to " + mReceiver);
                        sendFinished(mgr);
                    }
                    if (mInstrumentation == null ||
                            !mInstrumentation.onException(mReceiver, e)) {
                        Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
                        throw new RuntimeException(
                            "Error receiving broadcast " + intent
                            + " in " + mReceiver, e);
                    }
                }
                
                if (receiver.getPendingResult() != null) {
                    finish();
                }
                Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
            }
     在这呢,我们稍微把广播的流程大概走了下,那么这里的目标receiver就是蓝牙进程的BluetoothOppReceiver了,所以在流程图中,我是用虚线表示这段逻辑的,是指从系统调用过来的,BluetoothOppReceiver在接收到系统的广播后,就根据action开始进行处理了,在else if (action.equals(BluetoothDevicePicker.ACTION_DEVICE_SELECTED)) 分支当中的其他逻辑都是一些赋值操作,完成之后,显示Toast信息提示已开始传输文件,主要的逻辑是调用mOppManager.startTransfer(remoteDevice)来完成的,mOppManager是一个BluetoothOppManager对象,在它的startTransfer方法中,就是构造了一个InsertShareInfoThread对象,然后调用它的start方法,run方法的实现如下:

        @Override
        public void run() {
            Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);
            if (mRemoteDevice == null) {
                Log.e(TAG, "Target bt device is null!");
                return;
            }
            if (mIsMultiple) {
                insertMultipleShare();
            } else {
                insertSingleShare();
            }
            synchronized (BluetoothOppManager.this) {
                mInsertShareThreadNum--;
            }
        }
     我们当前的操作只传输一张图片,所以mIsMultiple的值为false,如果我们要传输多个文件的话,那么一开始保存的Uri也是多个,当然是指一次传输多个文件,不能分开,如果分开了,那么Uri还是只保存一个,大家可以自己试一下。这里呢就继续调用insertSingleShare()进行处理。insertSingleShare方法当中就是我们获取到的目标文件的Uri保存在数据库中,我们也可以看一下数据库中的文件:


     我们打开蓝牙目录当中的数据库,可以看到这里保存了所有我们传输过的文件信息。保存完成后,那么系统就会回调BluetoothOppService.BluetoothShareContentObserver对象的onChange方法了,通知它数据已经变化了,关于ContentProvider数据更新

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

红-旺永福

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值