原址

1 创建rfcomm层sever

要想通过蓝牙接收文件,首先要打开蓝牙。所以先从打开蓝牙进行分析。 
BluetoothOppReceiver在AndroidManifest.xml文件中进行了注册,其中action包括”Android.bluetooth.adapter.action.STATE_CHANGED”,也就是它会监听蓝牙状态的改变。

if (action.equals(BluetoothAdapter.ACTION_STATE_CHANGED)){if (BluetoothAdapter.STATE_ON == intent.getIntExtra(BluetoothAdapter.EXTRA_STATE, BluetoothAdapter.ERROR)) {context.startService(new Intent(context, BluetoothOppService.class));//......}
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

监听到蓝牙开启,BluetoothOppReceiver会打开服务BluetoothOppService。接着看BluetoothOppService。

mAdapter = BluetoothAdapter.getDefaultAdapter();  //创建蓝牙适配器
mSocketListener = new BluetoothOppRfcommListener(mAdapter);
mObserver = new BluetoothShareContentObserver(); //监听数据库的变化
getContentResolver().registerContentObserver(BluetoothShare.CONTENT_URI, true, mObserver);
mNotifier = new BluetoothOppNotification(this); // 创建BluetoothOppNotification对象
mNotifier.mNotificationMgr.cancelAll();  //取消所有状态栏通知
mNotifier.updateNotification();
IntentFilter filter = new IntentFilter(BluetoothAdapter.ACTION_STATE_CHANGED);
registerReceiver(mBluetoothReceiver, filter); //注册广播接受者,监听蓝牙状态的改变
synchronized (BluetoothOppService.this) {if (mAdapter == null) {//检查蓝牙是否可用。Log.w(TAG, "Local BT device is not enabled");} else {startListener();}
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16

在BluetoothOppService的onCreate()函数中,创建了BluetoothOppRfcommListener对象和BluetoothOppNotification对象。 
BluetoothOppRfcommListener类主要用来创建BluetoothServerSocket,接收其他设备的连接。 
BluetoothOppNotification则用来弹状态栏通知,显示发送、接收文件,及发送和接收进度等。 
BluetoothOppService中还注册了observer,监测数据库的变化。 
注册广播接受者,监听蓝牙状态的改变,开启蓝牙时,调用startSocketListener->mSocketListener.start,创建socket并开始监听其他设备的连接。关闭蓝牙时,调用mSocketListener.stop,关闭BluetoothServerSocket。 
接着看startListener();

private void startListener() {if (!mListenStarted) {//mListenStarted初始值为falseif (mAdapter.isEnabled()) {//检查蓝牙是否开启mHandler.sendMessage(mHandler.obtainMessage(START_LISTENER));mListenStarted = true;}}}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8

handler接收到消息START_LISTENER,则调用

//开始socket监听。
private void startSocketListener() {mSocketListener.start(mHandler);
}
  • 1
  • 2
  • 3
  • 4
  • 1
  • 2
  • 3
  • 4

mSocketListener为创建的BluetoothOppRfcommListener对象,调用其start()方法,携带handler对象。在start()内将此handler存为全局变量,用于之后向BluetoothOppService发送消息。下一步创建线程mSocketAcceptThread ,并开始运行该线程。

public synchronized boolean start(Handler callback) {if (mSocketAcceptThread == null) {mCallback = callback;//创建线程mSocketAcceptThreadmSocketAcceptThread = new Thread(TAG) {public void run() {if (Constants.USE_TCP_DEBUG) { //这个是实用tcp协议,可忽略。} else {boolean serverOK = true;//可能创建失败,尝试10次for (int i = 0; i < CREATE_RETRY_TIME && !mInterrupted; i++) {try {mBtServerSocket = mAdapter.listenUsingInsecureRfcommWithServiceRecord("OBEX Object Push", BluetoothUuid.ObexObjectPush.getUuid());} catch (IOException e1) {serverOK = false;}if (!serverOK) {synchronized (this) {try {//等待300msThread.sleep(300);} catch (InterruptedException e) {mInterrupted = true;}}} else { //创建BluetoothServerSocket成功,退出for循环。break;}}if (!serverOK) {mInterrupted = true;}BluetoothSocket clientSocket;while (!mInterrupted) {try {BluetoothServerSocket sSocket = mBtServerSocket;if (sSocket ==null) {mInterrupted = true;} else {//接收客户端的连接clientSocket = sSocket.accept();BluetoothOppRfcommTransport transport = new BluetoothOppRfcommTransport(clientSocket);Message msg = Message.obtain();msg.setTarget(mCallback);msg.what = MSG_INCOMING_BTOPP_CONNECTION;msg.obj = transport;msg.sendToTarget();}} catch (IOException e) {try {Thread.sleep(500);} catch (InterruptedException ie) {}}}}}};mInterrupted = false;if(!Constants.USE_TCP_SIMPLE_SERVER) {//该值为false,没有使用tcp相关的。mSocketAcceptThread.start();  //mSocketAcceptThread线程开始运行}}return true;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63

mSocketAcceptThread中创建BluetoothServerSocket,创建BluetoothServerSocket可能会失败,这里做的保护措施是进行10次尝试。listenUsingInsecureRfcommWithServiceRecord()函数表示此rfcomm连接是不安全的,所以连接时不会进行配对。 
sSocket.accept()是阻塞的,等待远程设备的连接。 
当与远程设备连接成功后,发送消息MSG_INCOMING_BTOPP_CONNECTION。mCallback对应BluetoothOppService的mHandler。所以在BluetoothOppService中看是如何处理此消息到。

private Handler mHandler = new Handler() {@Overridepublic void handleMessage(Message msg) {switch (msg.what) {case BluetoothOppRfcommListener.MSG_INCOMING_BTOPP_CONNECTION:ObexTransport transport = (ObexTransport)msg.obj;//传入连接策略://1. 如果没有向外分享文件,也没有保持到连接则 start//2. 如果正在通过向外蓝牙分享文件,保持20秒(1秒* 20次),//3. 如果之前有保持到连接,则直接拒绝。if (mBatchs.size() == 0 && mPendingConnection == null) {//判断如果没有正在向外分享文件,并且之前没有保留的连接,则创建obex server端。createServerSession(transport);} else {if (mPendingConnection != null) {//如果有保留的连接,则直接拒绝,关闭rfcomm连接。try {transport.close();} catch (IOException e) {Log.e(TAG, "close tranport error");}} else if (Constants.USE_TCP_DEBUG && !Constants.USE_TCP_SIMPLE_SERVER) {//忽略} else {//正在向外分享文件mIncomingRetries = mIncomingRetries + 1; //记录尝试次数mPendingConnection = transport; //保留连接。Message msg1 = Message.obtain(mHandler);msg1.what = MSG_INCOMING_CONNECTION_RETRY;mHandler.sendMessageDelayed(msg1, 1000); //1s后发送消息,}}break;case MSG_INCOMING_CONNECTION_RETRY: //尝试重连if (mBatchs.size() == 0) {     //分享文件完毕createServerSession(mPendingConnection);mIncomingRetries = 0;      //尝试次数清零mPendingConnection = null;  //保留的连接清除} else {//分享文件尚未完成if (mIncomingRetries == 20) { //最多进行19次重试,第二十次还是没有分享完成则关闭连接,不再接收此次远程设备分享。try {mPendingConnection.close();} catch (IOException e) {}mIncomingRetries = 0;mPendingConnection = null;} else {//1s后重新试mIncomingRetries = mIncomingRetries + 1;Message msg2 = Message.obtain(mHandler);msg2.what = MSG_INCOMING_CONNECTION_RETRY;mHandler.sendMessageDelayed(msg2, 1000);}}break;
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51

2 创建obex层server

createServerSession()函数中创建BluetoothOppObexServerSession,并准备接收文件,preStart()函数中创建serversession(new ServerSession(mTransport, this, null))。创建好serversession后就可以接收client的connect,put请求了。

private void createServerSession(ObexTransport transport) {//创建obex server端。mServerSession = new BluetoothOppObexServerSession(this, transport);mServerSession.preStart();  //进行准备
}   
  • 1
  • 2
  • 3
  • 4
  • 5
  • 1
  • 2
  • 3
  • 4
  • 5

client端socket连接成功后会调用ClientSession.connect()进行obex层的连接。server端回调onConnect函数

public int onConnect(HeaderSet request, HeaderSet reply) {Long objectCount = null;try {byte[] uuid = (byte[])request.getHeader(HeaderSet.TARGET);if(uuid != null) { //如果target有内容则返回OBEX_HTTP_NOT_ACCEPTABLEreturn ResponseCodes.OBEX_HTTP_NOT_ACCEPTABLE;} //获取分享文件数量信息objectCount = (Long) request.getHeader(HeaderSet.COUNT);} catch (IOException e) {return ResponseCodes.OBEX_HTTP_INTERNAL_ERROR;}。。。return ResponseCodes.OBEX_HTTP_OK;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14

onConnect函数中会对请求头中的target信息进行判断,没有才返回ok。 
client端connect成功后,调用put()函数发送文件详细信息。server端回调onPut。

public int onPut(Operation op) {HeaderSet request;  String name, mimeType;Long length;int obexResponse = ResponseCodes.OBEX_HTTP_OK;//对于分享的多个文件,用户拒绝第一个文件,其他也拒绝。if (mAccepted == BluetoothShare.USER_CONFIRMATION_DENIED) {return ResponseCodes.OBEX_HTTP_FORBIDDEN;}try {boolean pre_reject = false;request = op.getReceivedHeader();  //获取请求信息name = (String)request.getHeader(HeaderSet.NAME);  //文件名length = (Long)request.getHeader(HeaderSet.LENGTH);  //文件长度mimeType = (String)request.getHeader(HeaderSet.TYPE); // 文件类型if (length == 0) {  //长度为0,设置response。pre_reject = true;obexResponse = ResponseCodes.OBEX_HTTP_LENGTH_REQUIRED;}if (name == null || name.equals("")) {  //name为null或空,设置responsepre_reject = true;obexResponse = ResponseCodes.OBEX_HTTP_BAD_REQUEST;}if (!pre_reject) {String extension, type;int dotIndex = name.lastIndexOf(".");   //查看文件名中是否有‘.’,if (dotIndex < 0 && mimeType == null) {pre_reject = true;obexResponse = ResponseCodes.OBEX_HTTP_BAD_REQUEST;} else { //获取后缀名extension = name.substring(dotIndex + 1).toLowerCase();MimeTypeMap map = MimeTypeMap.getSingleton();type = map.getMimeTypeFromExtension(extension);  //查看后缀对应的文件类型if (type != null) {mimeType = type;} else {if (mimeType == null) { //请求信息没有类型信息,文件名的后缀也没有对应的类型。pre_reject = true;obexResponse = ResponseCodes.OBEX_HTTP_UNSUPPORTED_TYPE;}}if (mimeType != null) {mimeType = mimeType.toLowerCase();   //小写}}}if (!pre_reject&& (mimeType == null|| (!isWhitelisted && !Constants.mimeTypeMatches(mimeType,Constants.ACCEPTABLE_SHARE_INBOUND_TYPES))|| Constants.mimeTypeMatches(mimeType,Constants.UNACCEPTABLE_SHARE_INBOUND_TYPES))) {// mimeType为null或不可接受的列表,拒绝传输pre_reject = true;obexResponse = ResponseCodes.OBEX_HTTP_UNSUPPORTED_TYPE;}if (pre_reject && obexResponse != ResponseCodes.OBEX_HTTP_OK) {// 一些坏的实施客户端不会发送断开连接return obexResponse;}} catch (IOException e) {return ResponseCodes.OBEX_HTTP_BAD_REQUEST;}ContentValues values = new ContentValues();values.put(BluetoothShare.FILENAME_HINT, name);   //文件名values.put(BluetoothShare.TOTAL_BYTES, length.intValue()); //文件长度 values.put(BluetoothShare.MIMETYPE, mimeType); //文件类型values.put(BluetoothShare.DESTINATION, destination);  //设备蓝牙地址values.put(BluetoothShare.DIRECTION, BluetoothShare.DIRECTION_INBOUND);  //接收values.put(BluetoothShare.TIMESTAMP, mTimestamp);  //时间boolean needConfirm = true;if (!mServerBlocking) {  //对于多个文件,接收了第一个,所以我们自动接受后面的values.put(BluetoothShare.USER_CONFIRMATION,BluetoothShare.USER_CONFIRMATION_AUTO_CONFIRMED);needConfirm = false;}if (isWhitelisted) { //对于白名单的社保,则不需要用户确认。白名单哪里设置暂时没有发现。values.put(BluetoothShare.USER_CONFIRMATION,BluetoothShare.USER_CONFIRMATION_HANDOVER_CONFIRMED);needConfirm = false;} //将此条记录插入到数据库。用来提示用户,接收还是拒绝,这个稍后再说弹提示框。Uri contentUri = mContext.getContentResolver().insert(BluetoothShare.CONTENT_URI, values);mLocalShareInfoId = Integer.parseInt(contentUri.getPathSegments().get(1));if (needConfirm) {  //需要确认,发送广播,广播接受者中弹了一个toast。Intent in = new Intent(BluetoothShare.INCOMING_FILE_CONFIRMATION_REQUEST_ACTION);in.setClassName(Constants.THIS_PACKAGE_NAME, BluetoothOppReceiver.class.getName());mContext.sendBroadcast(in);}synchronized (this) {if (mWakeLock.isHeld()) {mPartialWakeLock.acquire();mWakeLock.release(); //释放锁}mServerBlocking = true;try {   while (mServerBlocking) {//进行阻塞,等待用户确认是否接收。wait(1000);if (mCallback != null && !mTimeoutMsgSent) {//发送超时信息,避免用户没有进行确认。mCallback.sendMessageDelayed(mCallback.obtainMessage(BluetoothOppObexSession.MSG_CONNECT_TIMEOUT),BluetoothOppObexSession.SESSION_TIMEOUT);mTimeoutMsgSent = true;}}} catch (InterruptedException e) {}} //........
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84
  • 85
  • 86
  • 87
  • 88
  • 89
  • 90
  • 91
  • 92
  • 93
  • 94
  • 95
  • 96
  • 97
  • 98
  • 99
  • 100
  • 101
  • 102
  • 103
  • 104
  • 105
  • 106
  • 107
  • 108
  • 109
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84
  • 85
  • 86
  • 87
  • 88
  • 89
  • 90
  • 91
  • 92
  • 93
  • 94
  • 95
  • 96
  • 97
  • 98
  • 99
  • 100
  • 101
  • 102
  • 103
  • 104
  • 105
  • 106
  • 107
  • 108
  • 109

onPut中对请求的文件信息(文件名、文件长度、文件类型)进行检查。其中文件类型是否可接收在Constants中有定义(ACCEPTABLE_SHARE_INBOUND_TYPES和UNACCEPTABLE_SHARE_INBOUND_TYPES)。 
符合规定则插入数据库。(插入数据库后,根据数据内容提示用户有文件接收请求。) 
然后阻塞,等待用户的确认,发送延时消息(50s),避免用户没有处理该传入文件请求。 
在BluetoothOppObexServerSession的onPut函数中会调用mContext.getContentResolver().insert(BluetoothShare.CONTENT_URI, values);将数据插入数据库。


3 创建文件

BluetoothOppProvider.insert()-> startService()-> BluetoothOppService.onStartCommand()->updateFromProvider() ->保证mUpdateThread创建运行(有的话则不再创建)。 
在mUpdateThread中会调用根据数据库进行不同的操作,包括:创建文件、显示传入文件确认通知等。在mUpdateThread线程中调用insertShare,插入共享文件到ArrayList mShares中。然后:

mServerTransfer = new BluetoothOppTransfer(this, mPowerManager, newBatch,mServerSession);
mServerTransfer.start();
  • 1
  • 2
  • 3
  • 1
  • 2
  • 3

BluetoothOppTransfer.start() -> startObexSession() -> processCurrentShare() ->mSession.addShare(mCurrentShare); 
接着看BluetoothOppObexServerSession中的addShare。

public void addShare(BluetoothOppShareInfo info) {mInfo = info;  //当前传输信息mFileInfo = processShareInfo(); //处理传输信息
}private BluetoothOppReceiveFileInfo processShareInfo() {BluetoothOppReceiveFileInfo fileInfo = BluetoothOppReceiveFileInfo.generateFileInfo(mContext, mInfo.mId);return fileInfo;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11

generateFileInfo()中判断是否有sd卡。判断/sdcard/blueooth是否是文件夹,及创建该文件夹是否成功。通过蓝牙接收的文件存储路径就是/sdcard/blueooth目录下。 
检查存储空间是否满足。检查文件名和文件类型,生成唯一的文件名(因为可能接收到同名的文件多次则在文件名后,类型名前加“-1”,如:img-1.jpg,img-2.jpg。) 
最后创建文件,返回BluetoothOppReceiveFileInfo对象。

public static BluetoothOppReceiveFileInfo generateFileInfo(Context context, int id) {ContentResolver contentResolver = context.getContentResolver();Uri contentUri = Uri.parse(BluetoothShare.CONTENT_URI + "/" + id);String filename = null, hint = null, mimeType = null;long length = 0;Cursor metadataCursor = contentResolver.query(contentUri, new String[] {BluetoothShare.FILENAME_HINT, BluetoothShare.TOTAL_BYTES, BluetoothShare.MIMETYPE}, null, null, null);if (metadataCursor != null) {try {if (metadataCursor.moveToFirst()) {hint = metadataCursor.getString(0);length = metadataCursor.getInt(1);mimeType = metadataCursor.getString(2);}} finally {metadataCursor.close();}}File base = null;StatFs stat = null;//判断是否有sd卡if (Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED)) {String root = Environment.getExternalStorageDirectory().getPath();base = new File(root + Constants.DEFAULT_STORE_SUBDIR);if (!base.isDirectory() && !base.mkdir()) { // /sdcard/bluetooth不是文件夹并且创建文件夹失败return new BluetoothOppReceiveFileInfo(BluetoothShare.STATUS_FILE_ERROR);}stat = new StatFs(base.getPath());} else { //没有sd卡return new BluetoothOppReceiveFileInfo(BluetoothShare.STATUS_ERROR_NO_SDCARD);}//检查文件系统是否有足够的空间来保存文件if (stat.getBlockSize() * ((long)stat.getAvailableBlocks() - 4) < length) {return new BluetoothOppReceiveFileInfo(BluetoothShare.STATUS_ERROR_SDCARD_FULL);}filename = choosefilename(hint);if (filename == null) {return new BluetoothOppReceiveFileInfo(BluetoothShare.STATUS_FILE_ERROR);}String extension = null;int dotIndex = filename.lastIndexOf(".");if (dotIndex < 0) {if (mimeType == null) {return new BluetoothOppReceiveFileInfo(BluetoothShare.STATUS_FILE_ERROR);} else {extension = "";}} else {extension = filename.substring(dotIndex);filename = filename.substring(0, dotIndex);}filename = base.getPath() + File.separator + filename;// 生成唯一的文件名。   String fullfilename = chooseUniquefilename(filename, extension);if (!safeCanonicalPath(fullfilename)) {// If this second check fails, then we better reject the transferreturn new BluetoothOppReceiveFileInfo(BluetoothShare.STATUS_FILE_ERROR);}if (fullfilename != null) {try {//创建文件,将流关闭。new FileOutputStream(fullfilename).close();int index = fullfilename.lastIndexOf('/') + 1;// 更新显示名称if (index > 0) {String displayName = fullfilename.substring(index);ContentValues updateValues = new ContentValues();updateValues.put(BluetoothShare.FILENAME_HINT, displayName);context.getContentResolver().update(contentUri, updateValues, null, null);}return new BluetoothOppReceiveFileInfo(fullfilename, length, new FileOutputStream(fullfilename), 0);} catch (IOException e) {return new BluetoothOppReceiveFileInfo(BluetoothShare.STATUS_FILE_ERROR);}} else {return new BluetoothOppReceiveFileInfo(BluetoothShare.STATUS_FILE_ERROR);}
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82

4 传入文件确认通知

BluetoothOppProvider.insert()-> startService()-> BluetoothOppService.onStartCommand()->updateFromProvider() ->保证mUpdateThread创建运行(有的话则不再创建)。mUpdateThread中会调用mNotifer.updateNotification()。 
updateNotification()会创建线程NotificationUpdateThread(),并运行该线程。该线程会进行更新notification,包括: 
- updateActiveNotification()表示正在传输的notification,用来更新传输或接收进度; 
- updateCompletedNotification()用来显示已完成的notification; 
- updateIncomingFileConfirmNotification()用来显示传入文件的通知。

主要看一下显示传入文件确认的通知

private void updateIncomingFileConfirmNotification() {Cursor cursor = mContext.getContentResolver().query(BluetoothShare.CONTENT_URI, null,WHERE_CONFIRM_PENDING, null, BluetoothShare._ID);if (cursor == null) {return;}for (cursor.moveToFirst(); !cursor.isAfterLast(); cursor.moveToNext()) {CharSequence title =mContext.getText(R.string.incoming_file_confirm_Notification_title);CharSequence caption = mContext.getText(R.string.incoming_file_confirm_Notification_caption);int id = cursor.getInt(cursor.getColumnIndexOrThrow(BluetoothShare._ID));long timeStamp = cursor.getLong(cursor.getColumnIndexOrThrow(BluetoothShare.TIMESTAMP));Uri contentUri = Uri.parse(BluetoothShare.CONTENT_URI + "/" + id);//创建Notification,设置notification的图标、标志、标题等Notification n = new Notification();n.icon = R.drawable.bt_incomming_file_notification;n.flags |= Notification.FLAG_ONLY_ALERT_ONCE;n.flags |= Notification.FLAG_ONGOING_EVENT;n.defaults = Notification.DEFAULT_SOUND;n.tickerText = title;Intent intent = new Intent(Constants.ACTION_INCOMING_FILE_CONFIRM);intent.setClassName(Constants.THIS_PACKAGE_NAME, BluetoothOppReceiver.class.getName());intent.setDataAndNormalize(contentUri);n.when = timeStamp; //时间//设置点击此notification时,发送Constants.ACTION_INCOMING_FILE_CONFIRM广播。n.setLatestEventInfo(mContext, title, caption, PendingIntent.getBroadcast(mContext, 0,intent, 0));intent = new Intent(Constants.ACTION_HIDE);intent.setClassName(Constants.THIS_PACKAGE_NAME, BluetoothOppReceiver.class.getName());intent.setDataAndNormalize(contentUri);//设置删除此notification时,发送Constants.ACTION_HIDE广播。n.deleteIntent = PendingIntent.getBroadcast(mContext, 0, intent, 0);mNotificationMgr.notify(id, n);  //显示该notification。}cursor.close();}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39

一般情况我们接收文件都会去点击该notification,点击该notification会发送Constants.ACTION_INCOMING_FILE_CONFIRM广播。现在使用的手机许多都改成直接弹对话框了,而是直接弹出对话框(直接发送该广播就可以了)。

if (action.equals(Constants.ACTION_INCOMING_FILE_CONFIRM)) {Uri uri = intent.getData();Intent in = new Intent(context, BluetoothOppIncomingFileConfirmActivity.class);in.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);in.setDataAndNormalize(uri);context.startActivity(in); //打开BluetoothOppIncomingFileConfirmActivity界面。NotificationManager notMgr = (NotificationManager)context.getSystemService(Context.NOTIFICATION_SERVICE);if (notMgr != null) {  //清除该notificationnotMgr.cancel((int)ContentUris.parseId(intent.getData()));}
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12

在BluetoothOppReceiver中接收到该广播Constants.ACTION_INCOMING_FILE_CONFIRM,打开BluetoothOppIncomingFileConfirmActivity界面。该界面样式是一个dialog,用来判断是否接收该文件。点击接收或取消后都更新数据库,BluetoothShare.USER_CONFIRMATION字段的值不同,分别为USER_CONFIRMATION_CONFIRMED和USER_CONFIRMATION_DENIED。 
BluetoothProvider对应的update方法中调用notifyChange(uri, null)。通知监测数据库变化的observer。 
BluetoothOppService检测到数据库的变化->updateFromProvider ->创建UpdateThread线程并运行 - >updateShare()。 
如果用户点击了接收或取消,也就是对此请求进行了确认,调用mServerTransfer.setConfirmed()。 
BluetoothOppTransfer setConfirmed()函数。在该函数中创建notifyThread 线程,使server session停止阻塞

public void setConfirmed() {/* unblock server session */final Thread notifyThread = new Thread("Server Unblock thread") {public void run() {synchronized (mSession) {mSession.unblock();  //停止阻塞mSession.notify();   //该函数应该其父类实现的,没有源码。}}};notifyThread.start();   //运行notifyThread线程。}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12

BluetoothOppObexServerSession的unblock()函数设置mServerBlocking为false。使onPut函数阻塞取消。 
onPut阻塞时发送50s延时消息BluetoothOppObexSession.MSG_CONNECT_TIMEOUT。当用户超过时间并没有点击接收文件的notification,或者并没有点击dialog上的接收或取消,看一下是怎么处理的

// 删除传入文件确认通知
NotificationManager nm = (NotificationManager)mContext.getSystemService(Context.NOTIFICATION_SERVICE);
nm.cancel(mCurrentShare.mId);
//用户确认的界面如果已开启,则dialog提示改为“接受来自‘’的文件时发生超时”,2s后关闭界面。
Intent in = new Intent(BluetoothShare.USER_CONFIRMATION_TIMEOUT_ACTION);
mContext.sendBroadcast(in);
markShareTimeout(mCurrentShare); //更新数据库。
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8

超时后用户没有处理,则会先清除传入文件确认的通知,然后ui超时处理(dialog内容关闭,2s后关闭),如果没有显示用户确认的dialog则不用管。更新数据库,流程和上面点击接收或取消流程类似,都会调到mSession.unblock(),停止onPut函数的阻塞。


5 接收文件

onPut函数取消阻塞后,会根据用户确认状态进行接收或拒绝接收处理。接着onPut函数取消阻塞后的内容分析。

mAccepted = mInfo.mConfirm;  //用户确认
int status = BluetoothShare.STATUS_SUCCESS;
//确认或自动确认(对于多个文件后面的文件)
if (mAccepted == BluetoothShare.USER_CONFIRMATION_CONFIRMED|| mAccepted == BluetoothShare.USER_CONFIRMATION_AUTO_CONFIRMED|| mAccepted == BluetoothShare.USER_CONFIRMATION_HANDOVER_CONFIRMED) {if (mFileInfo.mFileName == null) {  //检查文件名status = mFileInfo.mStatus;mInfo.mStatus = mFileInfo.mStatus;Constants.updateShareStatus(mContext, mInfo.mId, status);obexResponse = ResponseCodes.OBEX_HTTP_INTERNAL_ERROR;}if (mFileInfo.mFileName != null) {ContentValues updateValues = new ContentValues();contentUri = Uri.parse(BluetoothShare.CONTENT_URI + "/" + mInfo.mId);updateValues.put(BluetoothShare._DATA, mFileInfo.mFileName);updateValues.put(BluetoothShare.STATUS, BluetoothShare.STATUS_RUNNING);mContext.getContentResolver().update(contentUri, updateValues, null, null);//接收文件status = receiveFile(mFileInfo, op);if (status != BluetoothShare.STATUS_SUCCESS) {obexResponse = ResponseCodes.OBEX_HTTP_INTERNAL_ERROR;}Constants.updateShareStatus(mContext, mInfo.mId, status);  }if (status == BluetoothShare.STATUS_SUCCESS) {  //接收成功Message msg = Message.obtain(mCallback, BluetoothOppObexSession.MSG_SHARE_COMPLETE);msg.obj = mInfo;msg.sendToTarget();} else {if (mCallback != null) {Message msg = Message.obtain(mCallback,BluetoothOppObexSession.MSG_SESSION_ERROR);mInfo.mStatus = status;msg.obj = mInfo;msg.sendToTarget();}}
} else if (mAccepted == BluetoothShare.USER_CONFIRMATION_DENIED|| mAccepted == BluetoothShare.USER_CONFIRMATION_TIMEOUT) {//用户拒绝接收,或者超时未进行确认if (mFileInfo.mFileName != null) {try {mFileInfo.mOutputStream.close();  //关闭文件输出流} catch (IOException e) {}new File(mFileInfo.mFileName).delete(); //删除文件}status = BluetoothShare.STATUS_CANCELED;   // 设置状态本地取消Constants.updateShareStatus(mContext, mInfo.mId, status);obexResponse = ResponseCodes.OBEX_HTTP_FORBIDDEN;  //response设为被禁止Message msg = Message.obtain(mCallback);msg.what = BluetoothOppObexSession.MSG_SHARE_INTERRUPTED;mInfo.mStatus = status;msg.obj = mInfo;msg.sendToTarget();  //发送消息。
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59

首先判断用户是否接收文件。

  1. 接收文件:检查文件名是否有问题,没有问题,则更新数据看接收状态,开始接收文件。接收文件完成后更新notification。
  2. 拒绝接收或超时未处理:关闭文件输出流,删除空文件。更新notification。 
    接着看接收文件的具体代码receiveFile
private int receiveFile(BluetoothOppReceiveFileInfo fileInfo, Operation op) {int status = -1;BufferedOutputStream bos = null;InputStream is = null;boolean error = false;try {is = op.openInputStream();  //获取obex连接输入流} catch (IOException e1) {status = BluetoothShare.STATUS_OBEX_DATA_ERROR;error = true;}Uri contentUri = Uri.parse(BluetoothShare.CONTENT_URI + "/" + mInfo.mId);if (!error) {ContentValues updateValues = new ContentValues();updateValues.put(BluetoothShare._DATA, fileInfo.mFileName);mContext.getContentResolver().update(contentUri, updateValues, null, null);}int position = 0;if (!error) { //获取输出流,输出到fileInfo文件中bos = new BufferedOutputStream(fileInfo.mOutputStream, 0x10000);}if (!error) {int outputBufferSize = op.getMaxPacketSize(); //获取最大的包大小byte[] b = new byte[outputBufferSize];int readLength = 0;long timestamp = 0;try {while ((!mInterrupted) && (position != fileInfo.mLength)) {readLength = is.read(b); //获取蓝牙分享的内容if (readLength == -1) { //表示接收结束。break;}bos.write(b, 0, readLength);  //将读到的内容写入到本地文件中。position += readLength; //接收到数据大小//更新数据库下载大小。BluetoothOppProvider.update->BluetoothOppService->BluetoothOppNotification。更新接收进度条。ContentValues updateValues = new ContentValues();updateValues.put(BluetoothShare.CURRENT_BYTES, position);mContext.getContentResolver().update(contentUri, updateValues, null, null);}} catch (IOException e1) {/* OBEX Abort packet received from remote device */if ("Abort Received".equals(e1.getMessage())) {status = BluetoothShare.STATUS_CANCELED;} else {status = BluetoothShare.STATUS_OBEX_DATA_ERROR;}error = true;}}if (mInterrupted) {  //接收被用户打断status = BluetoothShare.STATUS_CANCELED;} else {if (position == fileInfo.mLength) { //判断接收到的和文件长度相同,则接收完成status = BluetoothShare.STATUS_SUCCESS;} else {//接收失败if (status == -1) {status = BluetoothShare.STATUS_UNKNOWN_ERROR;}}}if (bos != null) {try {bos.close(); //关闭输出流。} catch (IOException e) {}}return status;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68

receiveFile函数通过流获取到蓝牙数据,并将数据写入本地文件中。更新接收到进度。到此接收文件的具体过程就分析完了。

Android 蓝牙开发(五)OPP接收文件相关推荐

  1. android蓝牙聊天设备,Android蓝牙开发——实现蓝牙聊天

    最近课上刚好需要做一个课程设计关于蓝牙的就挑选了个蓝牙聊天室,其实关键还是在于对蓝牙API的了解 一.蓝牙API 与蓝牙开发主要的相关类是以下四个 BluetoothAdapter 字面上则理解为蓝牙 ...

  2. Android蓝牙开发 — 经典蓝牙BLE蓝牙

    一,前期基础知识储备 1)蓝牙是一种支持设备之间短距离通信的无线电技术(其他还包括红外,WIFI): 支持移动电话.笔记本电脑.无线耳机等设备之间进行信息的交换: Android支持的蓝牙协议栈:Bl ...

  3. Android 蓝牙开发(扫描设备、绑定、解绑)Kotlin版

    Kotlin版 蓝牙开发 (扫描设备.绑定.解绑) 前言 运行效果图 正文 ① 配置项目 ② 布局和样式 ③ 编码 1. 通知栏样式修改 2. 蓝牙设备列表适配器编写 3. 权限请求 4. 初始化蓝牙 ...

  4. Android 蓝牙开发(扫描设备、绑定、解绑)

    Android 蓝牙开发(扫描设备.绑定.解绑) 前言 效果图 一.配置项目 二.布局和样式 三.编码 四.源码 前言 公司最近给我丢了一个蓝牙开发的项目,不了解怎么办呢,那当然是从最基础的开始了,所 ...

  5. Android 蓝牙开发(一) -- 传统蓝牙聊天室

    Android 蓝牙开发(一) – 传统蓝牙聊天室 Android 蓝牙开发(三) – 低功耗蓝牙开发 项目工程BluetoothDemo 一.蓝牙概览 以下是蓝牙的介绍,来自维基百科: 蓝牙(英语: ...

  6. Android - 蓝牙开发

    文章目录 科普 SIG 类型 制式 选择 逻辑链路控制适配协议 (L2CAP) L2CAP的功能 蓝牙框架和 RFCOMM 协议 蓝牙安全 白名单机制 编程 蓝牙权限 Classic Bluetoot ...

  7. Android蓝牙开发(一)蓝牙模块及核心API

    本文主要介绍Android蓝牙开发中基础知识:蓝牙模块及核心API. 关于蓝牙的连接及通讯功能实现,欢迎查阅下一篇文章:Android蓝牙开发(二)蓝牙消息传输实现. 蓝牙模块 从蓝牙4.0开始包含两 ...

  8. Android 蓝牙开发(三) -- 低功耗蓝牙开发

    Android 蓝牙开发(一) – 传统蓝牙聊天室 Android 蓝牙开发(三) – 低功耗蓝牙开发 项目工程BluetoothDemo 前面已经学习了经典蓝牙开发,学习了蓝牙的配对连接和通信,又通 ...

  9. Android 蓝牙开发,申请打开蓝牙

    申请打开蓝牙 <!-- 蓝牙权限 --> <uses-permission android:name="android.permission.BLUETOOTH" ...

  10. Android蓝牙开发—经典蓝牙详细开发流程

    文章目录 开发流程 权限 核心API BlueToothAdapter getDefaultAdapter():获取BluetoothAdapter对象 判断设备是否支持蓝牙 判断蓝牙是否开启 get ...

最新文章

  1. WdatePicker 日历控件的onchange事件无作用
  2. 使用伪指令#pragma pack
  3. html li标签横向排列_HTML简易的常用标签
  4. Linux CentOS安装zsh插件提示/usr/bin/env: python: No such file or directory。
  5. 20191024:单调栈问题的引出
  6. python快速编程入门课本中的名片管理器_python——实现名片管理器
  7. 软件设计方案说明书的编写
  8. 兜兜线报软件合集_柚子快报淘抢购秒杀系列【送秒杀软件】
  9. 排队论模型(三):M / M / s/ s 损失制排队模型
  10. 实战!使用Docker安装OnlyOffice
  11. 吴伯凡-认知方法论-给思维一个支点
  12. 【发际线大作战】C++学习记录之循环语句(发际线-1cm)
  13. php身份证实名认证接口
  14. 【前端知识之JS】reduce()方法与使用
  15. 游戏:杀戮尖塔(Slay the spire)mod--拉格朗·月
  16. vimdiff简单使用
  17. 为什么定义补码等于反码加一,知其所以然
  18. 楼天城楼教主的acm心路历程 ---- 抄自网上
  19. Matlab数字图像处理——图像处理工具箱Image Processing Toolbox
  20. SAP-ABAP-OOALV进阶-子屏幕;各种方法示例;

热门文章

  1. 关于CSS一些细节问题
  2. Linux服务器创建及维护记录
  3. SDUT 2170 The Largest SCC bfs+tarjan
  4. DataRowView 笔记
  5. Spring Cloud 是什么
  6. java 栈 堆 区别_java中栈与堆的区别
  7. 关于微信小程序中uView中通过packer选择器修改表单无法触发form组件的表单验证的问题
  8. 小敏同学利用计算机设计,福建省晋江一中、华侨中学2015-2016学年七年级数学上学期期中质量检测试题(无答案) 华东师大版...
  9. vue实现增删改查功能
  10. 第8.18节 Python类中内置析构方法__del__