蓝牙开发经验小结——自动文件传输(OBEX)
来源:互联网 发布:软件开发的发展趋势 编辑:程序博客网 时间:2024/06/03 05:02
场景:控制端——普通手机;被控制端——XX设备(无屏幕、无法用户操作、有系统权限)
网上关于文件传输实现的文章较少,没有发现满足我需求的资料,于是我索性深入到系统源码里头,研究了系统蓝牙(com.android.bluetooth不同的平台包名可能有差异)是如何实现文件收发的,然后再设计出自己的实现方案。
对于已经实现了蓝牙socket通讯(BluetoothChatService和BluetoothUtil)的小伙伴们,很容易想到的文件传输方式是采用文件流,1.通过socket获取输入输出流来处理传输(分别使用getInputStream()和getOutputStream())。2.用read(byte[])和write(byte[])来实现读写。然而实际上并没有那么简单,如“数据包截断”问题,以及传输过程中穿插其他信令交互的问题等等。如果基于蓝牙socket,通过文件流的形式传输,要做到比较可靠,其实就相当于你要实现OBEX协议(对象交换)的具体内容,复杂度和工作量还是不小的。
请注意,普通手机端,由于可以用户操作,依据通用API直接调用com.android.bluetooth的文件传输功能即可,这里说的是XX设备端,由于无法交互,需要程序自动确认传输的实现方式。
直接上代码吧,XX设备端,发送文件的处理:
/** * 通过系统的蓝牙文件传输功能,基于OBEX协议,向外发送文件 * @param file * @param destination */ public void sendFileByBluetooth(File file, String destination) { String uri = "file://" + file.getAbsolutePath(); Intent sharingIntent = new Intent(android.content.Intent.ACTION_SEND); sharingIntent.setType("*/*"); sharingIntent.setComponent(new ComponentName("com.android.bluetooth", "com.android.bluetooth.opp.BluetoothOppLauncherActivity")); sharingIntent.putExtra(Intent.EXTRA_STREAM, Uri.parse(uri)); App.getInstance().startActivity(sharingIntent); //以上代码看上去和第三方应用调用蓝牙发送没什么太多区别 //下面的代码,就是直接跳过原本需要用户手动操作的步骤了 //稍等片刻,提高发送成功率的关键 try { Thread.sleep(500); }catch (InterruptedException e){ e.printStackTrace(); logd(e); } Intent intent = new Intent(BluetoothDevicePicker.ACTION_DEVICE_SELECTED);//需要系统应用uid=1000,才可以发送 intent.putExtra(BluetoothDevice.EXTRA_DEVICE, getBTDevice(destination)); intent.setClassName("com.android.bluetooth", "com.android.bluetooth.opp.BluetoothOppReceiver"); App.getInstance().sendBroadcast(intent);//这一段发送了一个定向广播,告诉com.android.bluetooth已经选择了一个设备//下面这一段,就是要把弹出的蓝牙选择界面自动去除(模拟按下返回键) new Thread(new Runnable() {//自动去掉蓝牙选择的界面 @Override public void run() { boolean cover = false;//被遮挡 while (true) { if (!isAppOnForeground(App.getInstance(), App.getInstance().getPackageName())) { try { Instrumentation inst = new Instrumentation(); inst.sendKeyDownUpSync(KeyEvent.KEYCODE_BACK); } catch (Exception e) { e.printStackTrace(); } cover = true; } if (cover && isAppOnForeground(App.getInstance(), App.getInstance().getPackageName())) { logd("break"); break; } else { try { Thread.sleep(500); } catch (InterruptedException e) { e.printStackTrace(); logd(e); } } } } }).start(); }
XX设备端,接收文件的处理:
(使用了ContentObserver ,至于为什么可查看源码,这里不多解释)
String sendFileName = ""; File targetFileReceive = null; File targetFileSend = null; boolean transferFlag = false; private ContentObserver btObserver = new ContentObserver(mHandler) {//用于自动确认并接收蓝牙文件 @Override public void onChange(boolean selfChange) { super.onChange(selfChange); logd("btObserver onChange.");//需要申请权限android.permission.ACCESS_BLUETOOTH_SHARE,并且需要同Bluetooth的签名,即系统签名 if(!transferFlag){ return; } Cursor cursor = App.getInstance().getContentResolver().query(BluetoothShare.CONTENT_URI, null, null, null, BluetoothShare._ID); if(cursor == null){ logd("cursor null."); return; } cursor.moveToLast();//直接跳至最后一条记录(最新记录) int id = cursor.getInt(cursor.getColumnIndex(BluetoothShare._ID)); int direction = cursor.getInt(cursor.getColumnIndex(BluetoothShare.DIRECTION)); int user_confirm = cursor.getInt(cursor.getColumnIndex(BluetoothShare.USER_CONFIRMATION)); String fileName = cursor.getString(cursor.getColumnIndex(BluetoothShare.FILENAME_HINT)); StringBuilder stringBuilder = new StringBuilder(); if (direction == BluetoothShare.DIRECTION_INBOUND && user_confirm == BluetoothShare.USER_CONFIRMATION_PENDING) { stringBuilder.append("a new incoming file confirm,"); if (!TextUtils.isEmpty(sendFileName) && sendFileName.equals(fileName)) { if (fileName != null) { targetFileReceive = new File(FileConfig.FILE_BLUETOOTH, fileName); if(targetFileReceive.exists()){//避免有重名的文件,先删除 targetFileReceive.delete(); } } ContentValues mUpdateValues = new ContentValues(); mUpdateValues.put(BluetoothShare.USER_CONFIRMATION, BluetoothShare.USER_CONFIRMATION_CONFIRMED); App.getInstance().getContentResolver().update(BluetoothShare.CONTENT_URI, mUpdateValues, null, null); stringBuilder.append("confirm it."); startBTTransferMonitering(id, direction);//开始监控接收过程 } else { stringBuilder.append("refuse it."); ContentValues mUpdateValues = new ContentValues(); mUpdateValues.put(BluetoothShare.USER_CONFIRMATION, BluetoothShare.USER_CONFIRMATION_DENIED); App.getInstance().getContentResolver().update(BluetoothShare.CONTENT_URI, mUpdateValues, null, null); } logd(stringBuilder.append("id = ").append(id).toString());//打印该条记录 } else if (direction == BluetoothShare.DIRECTION_OUTBOUND && user_confirm == BluetoothShare.USER_CONFIRMATION_AUTO_CONFIRMED) { logd(stringBuilder.append("a new outcoming file request:").append("id = ").append(id));//打印该条记录 startBTTransferMonitering(id, direction);//开始监控发送过程 } for (int i = 0; i < 3 && !cursor.isBeforeFirst(); i++) {//打印最后三条记录 logd(buildBtCursorInfo(cursor)); cursor.moveToPrevious(); } if (cursor != null) { cursor.close(); } } }; private String buildBtCursorInfo(Cursor cursor){ if(cursor == null){ return null; } StringBuilder stringBuilder = new StringBuilder(); int id = cursor.getInt(cursor.getColumnIndex(BluetoothShare._ID)); String uri = cursor.getString(cursor.getColumnIndex(BluetoothShare.URI)); String fileName = cursor.getString(cursor.getColumnIndex(BluetoothShare.FILENAME_HINT)); String data = cursor.getString(cursor.getColumnIndex(BluetoothShare._DATA)); String mineType = cursor.getString(cursor.getColumnIndex(BluetoothShare.MIMETYPE)); int direction = cursor.getInt(cursor.getColumnIndex(BluetoothShare.DIRECTION)); String destination = cursor.getString(cursor.getColumnIndex(BluetoothShare.DESTINATION)); int visibility = cursor.getInt(cursor.getColumnIndex(BluetoothShare.VISIBILITY)); int user_confirm = cursor.getInt(cursor.getColumnIndex(BluetoothShare.USER_CONFIRMATION)); int status = cursor.getInt(cursor.getColumnIndex(BluetoothShare.STATUS)); int total_bytes = cursor.getInt(cursor.getColumnIndex(BluetoothShare.TOTAL_BYTES)); int current_bytes = cursor.getInt(cursor.getColumnIndex(BluetoothShare.CURRENT_BYTES)); int timeStamp = cursor.getInt(cursor.getColumnIndex(BluetoothShare.TIMESTAMP)); int media_scanned = cursor.getInt(cursor.getColumnIndex("scanned")); stringBuilder.append(BluetoothShare._ID).append(" = ").append(id).append(",") .append(BluetoothShare.URI).append(" = ").append(uri).append(",") .append(BluetoothShare.FILENAME_HINT).append(" = ").append(fileName).append(",") .append(BluetoothShare._DATA).append(" = ").append(data).append(",") .append(BluetoothShare.MIMETYPE).append(" = ").append(mineType).append(",") .append(BluetoothShare.DIRECTION).append(" = ").append(direction).append(",") .append(BluetoothShare.DESTINATION).append(" = ").append(destination).append(",") .append(BluetoothShare.VISIBILITY).append(" = ").append(visibility).append(",") .append(BluetoothShare.USER_CONFIRMATION).append(" = ").append(user_confirm).append(",") .append(BluetoothShare.STATUS).append(" = ").append(status).append(",") .append(BluetoothShare.TOTAL_BYTES).append(" = ").append(total_bytes).append(",") .append(BluetoothShare.CURRENT_BYTES).append(" = ").append(current_bytes).append(",") .append(BluetoothShare.TIMESTAMP).append(" = ").append(timeStamp).append(",") .append("scanned").append(" = ").append(media_scanned); return stringBuilder.toString(); } private void startBTTransferMonitering(final int id, final int direction){ new Thread(new Runnable() { @Override public void run() { logt("bt Transfer Monitor Thread start."); Cursor cursor = null; long start = System.currentTimeMillis(); boolean success = false; while (true){ cursor = App.getInstance().getContentResolver().query(BluetoothShare.CONTENT_URI, null, null, null, BluetoothShare._ID); if(cursor == null){ logt("bt transfer monitering failed, cursor null."); break; } else { for(cursor.moveToFirst(); !cursor.isAfterLast(); cursor.moveToNext()){ if(cursor.getInt(cursor.getColumnIndex(BluetoothShare._ID)) == id){ break; } } //logt(buildBtCursorInfo(cursor)); int status = cursor.getInt(cursor.getColumnIndex(BluetoothShare.STATUS)); if (BluetoothShare.isStatusCompleted(status)) { if (BluetoothShare.isStatusSuccess(status)) { success = true; logt("Status Success."); } else if (BluetoothShare.isStatusError(status)) { if (BluetoothShare.isStatusClientError(status)) { logt("Status Client Error."); } else if (BluetoothShare.isStatusServerError(status)) { logt("Status Server Error."); } else { logt("Status Error"); } } else { logt("Status Completed."); } break; } else if (BluetoothShare.isStatusInformational(status)) { //logt("Status Informational."); } else if (BluetoothShare.isStatusSuspended(status)) { logt("Status Suspended."); } else { logt("Status = " + status); } } if(System.currentTimeMillis() - start >= 10*60*1000){//十分钟超时 break; } try{ Thread.sleep(500); }catch (InterruptedException e){ e.printStackTrace(); logt(e); } } if (cursor != null) { cursor.close(); } if (direction == BluetoothShare.DIRECTION_OUTBOUND) {//发送文件结束 String res = buildBtMessage(success ? TcpConfig.BT_COPY_FILE : TcpConfig.BT_DELETE_FILE, targetFileSend.getName()); if (!TextUtils.isEmpty(res)) { sendMessage(res); } if (targetFileSend != null && targetFileSend.exists()) {//删除压缩包 targetFileSend.delete(); targetFileSend = null; } } else {//接收文件结束 if (targetFileReceive != null && targetFileReceive.exists()) {//把目标文件(apk升级包)剪切至Download目录 if (success) { ShellUtil.execCommand("cp " + targetFileReceive.getAbsolutePath() + " " + FileConfig.FILE_DOWNLOAD + targetFileReceive.getName(), false); //FileUtil.copyFile(targetFileReceive.getAbsolutePath(), FileConfig.FILE_DOWNLOAD + targetFileReceive.getName(), false); } sendMessage(buildBtMessage(TcpConfig.BT_SEND_APK, success ? "成功" : "失败")); targetFileReceive.delete(); targetFileReceive = null; } } logt("bt Transfer Monitor Thread quit. Duration time = " + getSimpleFormat(System.currentTimeMillis() - start)); } private void logt(Object o) { logd("Thread_" + Thread.currentThread().getId() + ":" + o); } }).start(); } public String getSimpleFormat(long cost){ if (cost >= 60 * 60 * 1000) { return String.valueOf(UniversalUtil.getDoubleFomat((float) cost / (60 * 60 * 1000), 3)) + " h"; } else if (cost >= 60 * 1000) { return String.valueOf(UniversalUtil.getDoubleFomat((float) cost / (60 * 1000), 3)) + " min"; } else if(cost>=1000){ return String.valueOf(UniversalUtil.getDoubleFomat((float) cost / 1000, 3)) + " s"; } else { return String.valueOf(cost) + "ms"; } }
以上实现方案,纯属自行研究结果,如有更优方案,欢迎交流指教。
根据实测经验,蓝牙传输的速率,相比于热点连接,会慢很多。
阅读全文
0 0
- 蓝牙开发经验小结——自动文件传输(OBEX)
- 蓝牙开发经验小结——自动配对
- 蓝牙文件传输+obex+xp
- 蓝牙开发经验小结——蓝牙通讯
- OBEX和蓝牙开发
- 蓝牙OBEX剖析(二)-- 流程解析
- 蓝牙文件传输之obex层之上的分析【Android源码解析】
- Symbian 3rd 开发蓝牙OBEX 详细设计与代码
- Symbian 3rd 开发蓝牙OBEX 详细设计与代码
- 蓝牙的OBEX协议
- 蓝牙的OBEX协议
- 蓝牙 Obex协议介绍
- 蓝牙OBEX剖析(一)
- 蓝牙的OBEX协议
- 蓝牙的OBEX协议
- Android蓝牙开发经验
- 续蓝牙自动配对,添加蓝牙文件传输功能
- 使用 JSR-82 和 OBEX 进行文件传输
- 再谈select, iocp, epoll,kqueue及各种I/O复用机制,以及各平台的实现方案
- “Name node is in safe mode” 错误解决方法
- webpack打包编译学习记录
- Spring MVC中对于邮件的初始化,和发送邮件实例代码
- 用c++实现扫雷
- 蓝牙开发经验小结——自动文件传输(OBEX)
- kafka的使用
- 深入浅出Prim算法
- GOTURN算法在ubuntu14.04+only_cpu环境下编译运行
- linux 下新安装tomcat执行.sh文件总是提示permission denied
- Context类getString(@StringRes int resId, Object... formatArgs)的使用
- 【bzoj4289: PA2012 Tax】图论--建图
- Eclipse中配置SVN插件
- 插入排序