Android 藍牙開發(五)OPP接收文件

轉載請註明出處: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函數通過流獲取到藍牙數據,並將數據寫入本地文件中。更新接收到進度。到此接收文件的具體過程就分析完了。

歡迎掃一掃關注我的微信公衆號,定期推送優質技術文章:

這裏寫圖片描述

發佈了129 篇原創文章 · 獲贊 270 · 訪問量 97萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章