轉載請註明出處:http://blog.csdn.net/vnanyesheshou/article/details/70340027
本文已授權微信公衆號 fanfan程序媛獨家發佈 掃一掃文章底部的二維碼或在微信搜索 fanfan程序媛 即可關注
這篇文章主要說一下手機是如何通過藍牙接收文件的。
1 創建rfcomm層sever |
要想通過藍牙接收文件,首先要打開藍牙。所以先從打開藍牙進行分析。
BluetoothOppReceiver在AndroidManifest.xml文件中進行了註冊,其中action包括”android.bluetooth.adapter.action.STATE_CHANGED”,也就是它會監聽藍牙狀態的改變。
f (action.equals(BluetoothAdapter.ACTION_STATE_CHANGED)){
if (BluetoothAdapter.STATE_ON == intent.getIntExtra(BluetoothAdapter.EXTRA_STATE, BluetoothAdapter.ERROR)) {
context.startService(new Intent(context, BluetoothOppService.class));
//......
}
}
監聽到藍牙開啓,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();
}
}
在BluetoothOppService的onCreate()函數中,創建了BluetoothOppRfcommListener對象和BluetoothOppNotification對象。
BluetoothOppRfcommListener類主要用來創建BluetoothServerSocket,接收其他設備的連接。
BluetoothOppNotification則用來彈狀態欄通知,顯示發送、接收文件,及發送和接收進度等。
BluetoothOppService中還註冊了observer,監測數據庫的變化。
註冊廣播接受者,監聽藍牙狀態的改變,開啓藍牙時,調用startSocketListener->mSocketListener.start,創建socket並開始監聽其他設備的連接。關閉藍牙時,調用mSocketListener.stop,關閉BluetoothServerSocket。
接着看startListener();
private void startListener() {
if (!mListenStarted) {//mListenStarted初始值爲false
if (mAdapter.isEnabled()) {//檢查藍牙是否開啓
mHandler.sendMessage(mHandler.obtainMessage(START_LISTENER));
mListenStarted = true;
}
}
}
handler接收到消息START_LISTENER,則調用
//開始socket監聽。
private void startSocketListener() {
mSocketListener.start(mHandler);
}
mSocketListener爲創建的BluetoothOppRfcommListener對象,調用其start()方法,攜帶handler對象。在start()內將此handler存爲全局變量,用於之後向BluetoothOppService發送消息。下一步創建線程mSocketAcceptThread ,並開始運行該線程。
public synchronized boolean start(Handler callback) {
if (mSocketAcceptThread == null) {
mCallback = callback;
//創建線程mSocketAcceptThread
mSocketAcceptThread = 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 {//等待300ms
Thread.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;
}
mSocketAcceptThread中創建BluetoothServerSocket,創建BluetoothServerSocket可能會失敗,這裏做的保護措施是進行10次嘗試。listenUsingInsecureRfcommWithServiceRecord()函數表示此rfcomm連接是不安全的,所以連接時不會進行配對。
sSocket.accept()是阻塞的,等待遠程設備的連接。
當與遠程設備連接成功後,發送消息MSG_INCOMING_BTOPP_CONNECTION。mCallback對應BluetoothOppService的mHandler。所以在BluetoothOppService中看是如何處理此消息到。
private Handler mHandler = new Handler() {
@Override
public void handleMessage(Message msg) {
switch (msg.what) {
case BluetoothOppRfcommListener.MSG_INCOMING_BTOPP_CONNECTION:
ObexTransport transport = (ObexTransport)msg.obj;
//傳入連接策略:
//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;
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(); //進行準備
}
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_ACCEPTABLE
return 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;
}
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); //文件長度
if (length ==0) { //長度爲0,設置response。
pre_reject =true;
obexResponse = ResponseCodes.OBEX_HTTP_LENGTH_REQUIRED;
}
if (name ==null|| name.equals("")) { //name爲null或空,設置response
pre_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();
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) {
}
}
//........
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();
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;
}
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);
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 transfer
return 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);
}
}
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();
}
一般情況我們接收文件都會去點擊該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) { //清除該notification
notMgr.cancel((int)ContentUris.parseId(intent.getData()));
}
}
在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線程。
}
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); //更新數據庫。
超時後用戶沒有處理,則會先清除傳入文件確認的通知,然後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(); //發送消息。
}
首先判斷用戶是否接收文件。
接收文件:檢查文件名是否有問題,沒有問題,則更新數據看接收狀態,開始接收文件。接收文件完成後更新notification。
拒絕接收或超時未處理:關閉文件輸出流,刪除空文件。更新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 = newbyte[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); //將讀到的內容寫入到本地文件中。
//更新數據庫下載大小。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;
}
receiveFile函數通過流獲取到藍牙數據,並將數據寫入本地文件中。更新接收到進度。到此接收文件的具體過程就分析完了。
歡迎掃一掃關注我的微信公衆號,定期推送優質技術文章: