Android 蓝牙开发(四)OPP传输文件

转载请注明出处:http://blog.csdn.net/vnanyesheshou/article/details/70256004

本文已授权微信公众号 fanfan程序媛 独家发布 扫一扫文章底部的二维码或在微信搜索 fanfan程序媛 即可关注

Android蓝牙功能(传统蓝牙、ble、hid)这三方面功能之前的博客都已经写了。现在接着了解蓝牙OPP传输文件相关功能。Android手机使用中,经常会用到通过蓝牙分享文件给附近的朋友。那么具体是如何实现的,大部分朋友都不是很清楚。看一下源码是如何实现该功能的。


1 BluetoothOppLauncherActivity

Android手机点击某文件进行蓝牙分享的时候,会跳转到系统自带应用Bluetooth中。

具体文件: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

DevicePickerActivity中代码很简单,只是设置了布局。

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

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

<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>  

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

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

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

这里只说向外发送、分享。接着看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

BluetoothOppObexClientSession类说明该设备作为obex client,向server发送文件。该类中主要功能:obex连接、发送分享文件的信息,发送数据等。

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);
    }
}

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

欢迎扫一扫关注我的微信公众号,定期推送优质技术文章:

时间: 2024-11-03 21:37:36

Android 蓝牙开发(四)OPP传输文件的相关文章

Android蓝牙开发入门

目录: 1. 蓝牙简史,现状 2. 蓝牙的应用场景 3. 蓝牙相关概念 4. Android蓝牙开发 1. 蓝牙简史: 蓝牙( Bluetooth)是一种无线技术标准,可以实现短距离(通常是几米范围之内)的无线通信.蓝牙技术始于1994年,迄今已经发展了超过20年.本质上它和其它几种射频通信技术类似,比如手机移动通信,近场通信技术(NFC),都是通过电磁波来实现不同设备的信息交换.区别在于无线电波的频率和发射功率不一样,从而传输距离也不一样. 2. 蓝牙的应用场景: l 移动电话和免提设备之间的

如何实现android蓝牙开发 自动配对连接,并不弹出提示框

如何实现android蓝牙开发 自动配对连接,并不弹出提示框 之前做一个android版的蓝牙,遇到最大的难题就是自动配对. 上网查资料说是用反射createBond()和setPin(),但测试时进行配对还是会出现提示,但配对是成功了 我就开始查找怎么关闭这个蓝牙配对提示框,后面还是伟大的android源码帮助了我. 在源码 BluetoothDevice 类中还有两个隐藏方法 cancelBondProcess()和cancelPairingUserInput() 这两个方法一个是取消配对进

Android蓝牙开发的一些经验

转载请注明来自:http://blog.csdn.net/icyfox_bupt/article/details/25487125 最近在实验室做项目,使用了Android的蓝牙开发,这里面有好多坑..所以还是希望能记下来这些东西和大家分享,不要再走我的老路了. 先说一下背景,我是开发手机与带蓝牙的智能设备(蓝牙血压计.血糖仪.手环等)设备对接的APP.也就是说,在设备端没有什么可以操作的,手机负责发起数据传输. 蓝牙连接,不需要配对 由于被曾经使用蓝牙的思路所误导,一直以为使用蓝牙是必须一个配

Android蓝牙开发

Android蓝牙开发 近期做蓝牙小车,须要Android端来控制小车的运动.以此文记录开发过程. 使用HC-06无线蓝牙串口透传模块.对于其它的蓝牙设备本文相同适用. 蓝牙开发的流程: 获取本地蓝牙适配器    -->     打开蓝牙    -->    搜索设备  -->   连接设备  -->   发送信息 首先为了避免以往我们先写入蓝牙权限: <uses-permission android:name="android.permission.BLUETOO

玩转Android Camera开发(四):预览界面四周暗中间亮,只拍摄矩形区域图片(附完整源码)

杂家前文曾写过一篇关于只拍摄特定区域图片的demo,只是比较简陋,在坐标的换算上不是很严谨,而且没有完成预览界面四周暗中间亮的效果,深以为憾,今天把这个补齐了. 在上代码之前首先交代下,这里面存在着换算的两种模式.第一种,是以屏幕上的矩形区域为基准进行换算.举个例子,屏幕中间一个 矩形框为100dip*100dip.这里一定要使用dip为单位,否则在不同的手机上屏幕呈现的矩形框大小不一样.先将这个dip换算成px,然后根据屏幕的宽和高的像素计算出矩形区域,传给Surfaceview上铺的一层Vi

Android蓝牙开发,报BluetoothAdapter﹕ Can&#39;t create handler inside thread that has not called Looper.prepare

这个错误翻译的意思是:不能在没有Looper.prepare的线程里面创建handler. 起初我很疑惑,我根本没有用到工作线程,也没有创建handler.报错的代码如下: // Device scan callback. private BluetoothAdapter.LeScanCallback mLeScanCallback = new BluetoothAdapter.LeScanCallback() { @Override public void onLeScan(final Blu

Android网络编程只局域网传输文件

Android网络编程之局域网传输文件: 首先创建一个socket管理类,该类是传输文件的核心类,主要用来发送文件和接收文件 具体代码如下: 1 package com.jiao.filesend; 2 3 import java.io.BufferedReader; 4 import java.io.BufferedWriter; 5 import java.io.FileInputStream; 6 import java.io.FileOutputStream; 7 import java

Android 蓝牙开发之搜索、配对、连接、通信大全

        蓝牙( Bluetooth®):是一种无线技术标准,可实现固定设备.移动设备和楼宇个人域网之间的短距离数据 交换(使用2.4-2.485GHz的ISM波段的UHF无线电波).蓝牙设备最多可以同时和7个其它蓝牙设备建立连接,进 行通信,当然并不是每一个蓝牙都可以达到最大值.下面,我们从蓝牙的基本概念开始,一步一步开始了解蓝牙. 基本概念: 安卓平台提供对蓝牙的通讯栈的支持,允许设别和其他的设备进行无线传输数据.应用程序层通过安卓API来调用蓝牙的相关功 能,这些API使程序无线连接

Android蓝牙开发简介

Android蓝牙系统 蓝牙是一种支持设备短距离通信(一般10m内)的无线电技术,可以在众多设备之间进行无线信息交换. Android系统中的蓝牙模块 Android包含了对蓝牙网络协议栈的支持,使蓝牙设备能够无线连接其他蓝牙设备以便交换数据. 通过使用蓝牙API,一个Android应用程序能够实现如下功能: - 扫描其他蓝牙设备 - 查询本地蓝牙适配器用于配对蓝牙设备 - 建立RFCOMM信道 - 通过服务发现连接其他设备 - 数据通信 - 管理多个连接 Android平台中蓝牙系统从上到下主