本文还是要关注手机间蓝牙opp的代码流程,这段的废话也许能帮助你提高下对蓝牙的体验。

        蓝牙发送文件时发送端先来到这里packages/apps/Bluetooth/src/com/android/bluetooth/opp/BluetoothOppLauncherActivity.java,一个没有界面只是提取下文件信息的中转站,源码的注释写的很清楚了,两个分支action.equals(Intent.ACTION_SEND)和action.equals(Intent.ACTION_SEND_MULTIPLE)



    1.  if (action.equals(Intent.ACTION_SEND) || action.equals(Intent.ACTION_SEND_MULTIPLE)) {  
    2. //Check if Bluetooth is available in the beginning instead of at the end  
    3. if (!isBluetoothAllowed()) {  
    4. new Intent(this, BluetoothOppBtErrorActivity.class);  
    5.                 in.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);  
    6. "title", this.getString(R.string.airplane_error_title));  
    7. "content", this.getString(R.string.airplane_error_msg));  
    8.                 startActivity(in);  
    9.                 finish();  
    10. return;  
    11.             }  
    12. if (action.equals(Intent.ACTION_SEND)) {  
    13.                .......   
    14. new Thread(new Runnable() {  
    15. public void run() {  
    16. this)  
    17. false);  
    18. //Done getting file info..Launch device picker  
    19. //and finish this activity  
    20.                                 launchDevicePicker();  
    21.                                 finish();  
    22.                             }  
    23.                         });  ......           
    24. else if (action.equals(Intent.ACTION_SEND_MULTIPLE)) {  
    25.               .......                     
    26.             }


               最前面那个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里发现



      1. <activity android:name=".bluetooth.DevicePickerActivity"  
      2. android:theme="@android:style/Theme.Holo.DialogWhenLarge"  
      3. android:label="@string/device_picker"  
      4. android:clearTaskOnLaunch="true">  
      5. <intent-filter>  
      6. <action android:name="android.bluetooth.devicepicker.action.LAUNCH" />  
      7. <category android:name="android.intent.category.DEFAULT" />  
      8. </intent-filter>  
      9. </activity>


                这样目标就指向了DevicePickerActivity,注意此时它的代码路径是packages/apps/Settings/src/com/android/settings/bluetooth/DevicePickerActivity.java,这个类代码很简单,只有一个onCreate并只在里加载了一个布局文件bluetooth_device_picker.xml,就是这个布局文件指明下一站在哪,看下面就知道怎么来到DevicePickerFragment了



      1. <fragment  
      2. android:id="@+id/bluetooth_device_picker_fragment"  
      3. android:name="com.android.settings.bluetooth.DevicePickerFragment"  
      4. android:layout_width="match_parent"  
      5. android:layout_height="0dip"  
      6. android:layout_weight="1" />

                到了这里,已经可看到配对过的蓝牙列表了,选择其中一个点击会来到这里,里面那个sendDevicePickedIntent是我们关心的,又发了一个广播,去找谁收了广播就好了



      1.     void onDevicePreferenceClick(BluetoothDevicePreference btPreference) {  
      2.         mLocalAdapter.stopScanning();  
      3.         LocalBluetoothPreferences.persistSelectedDeviceInPicker(  
      4.                 getActivity(), mSelectedDevice.getAddress());  
      5. if ((btPreference.getCachedDevice().getBondState() ==  
      6.                 BluetoothDevice.BOND_BONDED) || !mNeedAuth) {  
      7.             sendDevicePickedIntent(mSelectedDevice);  
      8.             finish();  
      9. else {  
      10. super.onDevicePreferenceClick(btPreference);  
      11.         }  
      12. public static final String ACTION_LAUNCH = "android.bluetooth.devicepicker.action.LAUNCH";  
      13. private void sendDevicePickedIntent(BluetoothDevice device) {  
      14. new Intent(BluetoothDevicePicker.ACTION_DEVICE_SELECTED);  
      15.          intent.putExtra(BluetoothDevice.EXTRA_DEVICE, device);  
      16. if (mLaunchPackage != null && mLaunchClass != null) {  
      17.              intent.setClassName(mLaunchPackage, mLaunchClass);  
      18.          }  
      19.         getActivity().sendBroadcast(intent);}  
      20. </div>


              通过BluetoothDevicePicker.ACTION_DEVICE_SELECTED查找,会在/packages/apps/Bluetooth/src/com/android/bluetooth/opp/BluetoothOppReceiver.java这个找到对该广播的处理,也就是下面的代码:


      1. else if (action.equals(BluetoothDevicePicker.ACTION_DEVICE_SELECTED)) {  
      2.           BluetoothOppManager mOppManager = BluetoothOppManager.getInstance(context);           
      3.           BluetoothDevice remoteDevice = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);   
      4.            
      5. // Insert transfer session record to database  
      6.           mOppManager.startTransfer(remoteDevice);  
      7.            
      8. // Display toast message  
      9.           String deviceName = mOppManager.getDeviceName(remoteDevice);  
      10.           .......  
      11. }

              看来关键代码是mOppManager.startTransfer(remoteDevice),在packages/apps/Bluetooth/src/com/android/bluetooth/opp/BluetoothOppManager.java,里面开启线程执行发送动作,既然是开启线程,直接去看run方法就是了,方法里面依旧区分单个和多个文件的发送,看一个就可以。



      1.    public void startTransfer(BluetoothDevice device) {  
      2. if (V) Log.v(TAG, "Active InsertShareThread number is : " + mInsertShareThreadNum);  
      3.         InsertShareInfoThread insertThread;  
      4. synchronized (BluetoothOppManager.this) {  
      5. if (mInsertShareThreadNum > ALLOWED_INSERT_SHARE_THREAD_NUMBER) {  
      6.                 ...........  
      7. return;  
      8.             }  
      9. new InsertShareInfoThread(device, mMultipleFlag, mMimeTypeOfSendingFile,  
      10.                     mUriOfSendingFile, mMimeTypeOfSendingFiles, mUrisOfSendingFiles,  
      11.                     mIsHandoverInitiated);  
      12. if (mMultipleFlag) {  
      13.                 mfileNumInBatch = mUrisOfSendingFiles.size();  
      14.             }  
      15.         }  
      16.         insertThread.start();  
      17.     }             
      18. public void run() {  
      19.             Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);  
      20.             ..........  
      21. if (mIsMultiple) {  
      22.                 insertMultipleShare();  
      23. else {  
      24.                 insertSingleShare();  
      25.             }  
      26.             .......... }

             以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


      1. public Uri insert(Uri uri, ContentValues values) {  
      2. if (rowID != -1) {  
      3. new Intent(context, BluetoothOppService.class));  
      4. "/" + rowID);  
      5. null);  
      6. else {  
      7. if (D) Log.d(TAG, "couldn't insert into btopp database");  
      8.  }


             在BluetoothOppService的onStartCommand方法中会看到updateFromProvider(),这里又开启了一个线程UpdateThread,后续代码当然是看它的run方法了,这里面内容不少,好在这部分代码注释比较多,理解起来不难。先暂时只关心发送的动作insertShare方法,代码也不少,只贴出了告诉我们接下来去哪里的代码和有关的逻辑注释,在下面的代码我们可以看到 BluetoothOppTransfer.java的对象,下一站就是它了。



      1. private void insertShare(Cursor cursor, int arrayPos) {  
      2.     .........  
      3. /*
      4.      * Add info into a batch. The logic is
      5.      * 1) Only add valid and readyToStart info
      6.      * 2) If there is no batch, create a batch and insert this transfer into batch,
      7.      * then run the batch
      8.      * 3) If there is existing batch and timestamp match, insert transfer into batch
      9.      * 4) If there is existing batch and timestamp does not match, create a new batch and
      10.      * put in queue
      11.      */  
      12. if (info.isReadyToStart()) {  
      13.         .............  
      14. if (mBatchs.size() == 0) {  
      15.            ........  
      16.             mBatchs.add(newBatch);  
      17. if (info.mDirection == BluetoothShare.DIRECTION_OUTBOUND) {  
      18. new BluetoothOppTransfer(this, mPowerManager, newBatch);  
      19. else if (info.mDirection == BluetoothShare.DIRECTION_INBOUND) {  
      20. new BluetoothOppTransfer(this, mPowerManager, newBatch,  
      21.                         mServerSession);  
      22.             }  
      23.   
      24. if (info.mDirection == BluetoothShare.DIRECTION_OUTBOUND && mTransfer != null) {  
      25.                 mTransfer.start();  
      26. else if (info.mDirection == BluetoothShare.DIRECTION_INBOUND  
      27. null) {  
      28.                 mServerTransfer.start();  
      29.             }  
      30.   
      31. else {  
      32.             .........  
      33.     }}

              虽然名字是start(),可实际并不是什么线程的,就是一普通方法的,路径是/packages/apps/Bluetooth/src/com/android/bluetooth/opp/BluetoothOppTransfer.java



        1. public void start() {  
        2.       ....这里省略未贴的代码是检查蓝牙是否打开,一个很谨慎的判断。看似无用,不过还是安全第一。  
        3.   
        4. if (mHandlerThread == null) {  
        5.           ........  
        6. if (mBatch.mDirection == BluetoothShare.DIRECTION_OUTBOUND) {  
        7. /* for outbound transfer, we do connect first */  
        8.               startConnectSession();  
        9. else if (mBatch.mDirection == BluetoothShare.DIRECTION_INBOUND) {  
        10. /*
        11.                * for inbound transfer, it's already connected, so we start
        12.                * OBEX session directly
        13.                */  
        14.               startObexSession();  
        15.           }  
        16.       }  
        17.   }


                上面的代码是分发送文件和接收文件的,看下这两行代码就很清楚了,如果分享给别人是OUTBOUND,先执行startConnectSession(),这个函数最后还是要跑到startObexSession()这里的,如果收文件直接startObexSession,所以后面就只看startObexSession方法了



        1. // This transfer is outbound, e.g. share file to other device.  
        2. public static final int DIRECTION_OUTBOUND = 0;  
        3. // This transfer is inbound, e.g. receive file from other device.  
        4. public static final int DIRECTION_INBOUND = 1;

               还是在同一个类里,发送流程快结束了,同样区分是传入还是传出,发文件看OUTBOUND,去BluetoothOppObexClientSession.java



        1. private void startObexSession() {       
        2. if (mBatch.mDirection == BluetoothShare.DIRECTION_OUTBOUND) {  
        3. if (V) Log.v(TAG, "Create Client session with transport " + mTransport.toString());  
        4. new BluetoothOppObexClientSession(mContext, mTransport);  
        5. else if (mBatch.mDirection == BluetoothShare.DIRECTION_INBOUND) {           
        6. if (mSession == null) {  
        7.                markBatchFailed();  
        8.               mBatch.mStatus = Constants.BATCH_STATUS_FAILED;  
        9. return;  
        10.           }  
        11. if (V) Log.v(TAG, "Transfer has Server session" + mSession.toString());  
        12.       }  
        13.       mSession.start(mSessionHandler);  
        14.       processCurrentShare();  
        15.   }

               同样名字是start,实际只是一个普通方法而已,会看又是一个线程 

        mThread 
         =  
        new 
          
        ClientThread 
        ( 
        mContext 
        ,  
        mTransport

        ),这时的start才是线程的start(),还是看run方法,一些线程状态的判断,看到doSend() 就是了,直正的发送在这里packages/apps/Bluetooth/src/com/android/bluetooth/opp/BluetoothOppObexClientSession.java,



        1. private void doSend() {  
        2.   
        3. int status = BluetoothShare.STATUS_SUCCESS;  
        4.      ........关于status值的判断  
        5. if (status == BluetoothShare.STATUS_SUCCESS) {  
        6. /* do real send */ //看到这个注释了没,它才是真家伙sendFile  
        7. if (mFileInfo.mFileName != null) {  
        8.              status = sendFile(mFileInfo);  
        9. else {  
        10. /* this is invalid request */  
        11.              status = mFileInfo.mStatus;  
        12.          }  
        13. true;  
        14. else {  
        15.          Constants.updateShareStatus(mContext1, mInfo.mId, status);  
        16.      }  
        17.   
        18. if (status == BluetoothShare.STATUS_SUCCESS) {  
        19.          Message msg = Message.obtain(mCallback);  
        20.          msg.what = BluetoothOppObexSession.MSG_SHARE_COMPLETE;  
        21.          msg.obj = mInfo;  
        22.          msg.sendToTarget();  
        23. else {  
        24.          Message msg = Message.obtain(mCallback);  
        25.          msg.what = BluetoothOppObexSession.MSG_SESSION_ERROR;  
        26.          mInfo.mStatus = status;  
        27.          msg.obj = mInfo;  
        28.          msg.sendToTarget();  
        29.      }  
        30.  }

                sendFile是真正干活的,执行完sendFile会把分享成功或失败的消息传回去,sendFile里会执行打包的过程,对于字段的含义要看Headset.java,

        代码路径在frameworks/base/obex/javax/obex/HeaderSet.java。这个sendFile方法行数虽然多,不过逻辑还是比较清晰的,在这里就不贴了。到这蓝牙发送文件流程也就此结束。由于发送文件时长肯定是不确定,所以在这个流程我们看到了很多开启线程代码也是很正常的,对于这线程,直接看对应的run方法就是了。

                对于蓝牙接收文件时会收到MSG_INCOMING_BTOPP_CONNECTION消息,收到这个消息是由于在蓝牙打开,即蓝牙状态是 BluetoothAdapter.STATE_ON时会执行

        startSocketListener(),在这个函数开启了监听程序,看下面贴在一起的代码就明白了,


        1. if (action.equals(BluetoothAdapter.ACTION_STATE_CHANGED)) {  
        2. switch (intent.getIntExtra(BluetoothAdapter.EXTRA_STATE, BluetoothAdapter.ERROR)) {  
        3. case BluetoothAdapter.STATE_ON:  
        4. if (V) Log.v(TAG,"Receiver BLUETOOTH_STATE_CHANGED_ACTION, BLUETOOTH_STATE_ON");  
        5.             startSocketListener();  
        6. break;  
        7.                           
        8. private void startSocketListener() {  
        9. if (V) Log.v(TAG, "start RfcommListener");  
        10.         mSocketListener.start(mHandler);  
        11. if (V) Log.v(TAG, "RfcommListener started");  
        12. }  
        13. mSocketListener.start(mHandler);这个的实现在这里,比较长,没有贴上来     
        14. /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方法中接收数据


        1. private void createServerSession(ObexTransport transport) {  
        2. new BluetoothOppObexServerSession(this, transport);  
        3.      mServerSession.preStart();       
        4.  }


                对于蓝牙接收文件部分的流程还没有细致的跟踪,暂时只看到这里,对于了解基本流程这此应该够用了,同时如果想更好理解蓝牙OPP文件传输,了解是OBEX基础协议也是有必要的,网上资料还是有不少的,多数是论文形式的。对于蓝牙OPP部分,本文只是描述android代码中的流程,旨在帮你快速的理清流程,本文对OPP本身并没有深入,相关的知识需要进一步学习才行,有同道先行的童鞋还望赐教一二,谢谢。