Fichiers de transfert Android Bluetooth OPP (partie 1)

Tiré de : http://blog.csdn.net/vnanyesheshou/article/details/70256004

Les trois aspects de la fonction Bluetooth d'Android ( Bluetooth traditionnel, ble, hid ) ont été écrits dans des blogs précédents. Découvrons maintenant les fonctions liées au transfert de fichiers Bluetooth OPP . Lorsque vous utilisez des téléphones Android, il est souvent nécessaire de partager des fichiers avec des amis proches via Bluetooth. Alors, comment cela est-il mis en œuvre exactement, la plupart des amis ne le savent pas très bien. Jetez un œil à la façon dont le code source implémente cette fonction.


1 Activité BluetoothOppLauncher

Lorsqu'un téléphone Android clique sur un fichier à partager via Bluetooth , il accède à l'application Bluetooth intégrée au système.
Fichier spécifique :
packages/apps/Bluetooth/src/com/android/bluetooth/opp/BluetoothOppLauncherActivity.java
Jetez un œil à la façon dont BluetoothOppLauncherActivity gère les demandes de partage de fichiers.

if (action.equals(Intent.ACTION_SEND) || action.equals(Intent.ACTION_SEND_MULTIPLE)) {
            //Check if Bluetooth is available in the beginning instead of at the end
            if (!isBluetoothAllowed()) {
                Intent in = new Intent(this, BluetoothOppBtErrorActivity.class);
                in.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
                in.putExtra("title", this.getString(R.string.airplane_error_title));
                in.putExtra("content", this.getString(R.string.airplane_error_msg));
                startActivity(in);
                finish();
                return;
            }
            //..........下面接着说。
}
 
 
  
  

BluetoothOppLauncherActivity n'a pas d'interface (pas de setContentView), c'est juste une station de transfert, et il saute en fonction du Bluetooth actuel et d'autres états associés. La différence entre Intent.ACTION_SEND et Intent.ACTION_SEND_MULTIPLE est que le premier représente un seul fichier, tandis que le second représente plusieurs fichiers. Ici nous étudions uniquement le partage d’un seul fichier. Je comprends le partage d’un seul fichier. Le principe des fichiers multiples est similaire.
Parmi eux, la fonction isBluetoothAllowed() déterminera d'abord si le mode vol est activé, et retournera true s'il n'est pas activé. S'il est activé, passez à l'étape suivante pour juger si le mode vol est important, et renvoyez vrai s'il n'est pas important (indiquant que Bluetooth peut être utilisé). Si c'est important, continuez à analyser si Bluetooth peut être activé en mode avion, et renvoyez vrai si Bluetooth peut être activé, sinon renvoyez faux. D'une manière générale, cette fonction consiste à juger si le Bluetooth actuel est autorisé à être utilisé. Accédez à BluetoothOppBtErrorActivity si Bluetooth n’est pas autorisé.
Puis vers le bas :

if (action.equals(Intent.ACTION_SEND)) { //单个文件
    final String type = intent.getType();
    final Uri stream = (Uri)intent.getParcelableExtra(Intent.EXTRA_STREAM);
    CharSequence extra_text = intent.getCharSequenceExtra(Intent.EXTRA_TEXT);
    if (stream != null && type != null) { //分享文件
        Thread t = new Thread(new Runnable() {
            public void run() {
                BluetoothOppManager.getInstance(BluetoothOppLauncherActivity.this)
                    .saveSendingFileInfo(type,stream.toString(), false);
                launchDevicePicker();
                finish();
            }
        });
        t.start();
        return;
    } else if (extra_text != null && type != null) { //分享text字符串,没有文件
        final Uri fileUri = creatFileForSharedContent(this, extra_text); //创建文件,将内容写入文件
        if (fileUri != null) {
            Thread t = new Thread(new Runnable() {
                public void run() {
                    BluetoothOppManager.getInstance(BluetoothOppLauncherActivity.this)
                        .saveSendingFileInfo(type,fileUri.toString(), false);
                    launchDevicePicker();
                    finish();
                }
            });
            t.start();
            return;
        }
        //.........
}
 
 
  
  

Ceux qui ont utilisé le partage du système Android doivent savoir qu'il prend en charge les fichiers (images, vidéos, etc.) et les chaînes. Ici, les fichiers et les chaînes seront distingués. Pour les chaînes, les fichiers seront d'abord créés puis partagés.
Dans la fonction launchDevicePicker(), déterminez d’abord si Bluetooth est activé.
Si Bluetooth n'est pas activé, passez à BluetoothOppBtEnableActivity pour afficher la boîte de dialogue (vous demandant si vous souhaitez activer Bluetooth). Cliquez sur Annuler pour quitter. Cliquez sur Ouvrir pour activer Bluetooth et passez à BluetoothOppBtEnablingActivity (cette activité affiche principalement une boîte de dialogue de progression). Lorsque Bluetooth est activé, l'interface BluetoothOppBtEnablingActivity est terminée. Le récepteur de diffusion BluetoothOppReceiver reçoit que Bluetooth est activé et passe à l'interface DevicePickerActivity (application Paramètres système).
Si Bluetooth est activé, accédez directement à l'interface DevicePickerActivity (application Paramètres système).
Code de saut sous launchDevicePicker() :

//ACTION_LAUNCH="android.bluetooth.devicepicker.action.LAUNCH"
Intent in1 = new Intent(BluetoothDevicePicker.ACTION_LAUNCH);
in1.setFlags(Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS);
in1.putExtra(BluetoothDevicePicker.EXTRA_NEED_AUTH, false);
in1.putExtra(BluetoothDevicePicker.EXTRA_FILTER_TYPE,
        BluetoothDevicePicker.FILTER_TYPE_TRANSFER);
in1.putExtra(BluetoothDevicePicker.EXTRA_LAUNCH_PACKAGE,
        Constants.THIS_PACKAGE_NAME);
in1.putExtra(BluetoothDevicePicker.EXTRA_LAUNCH_CLASS,
        BluetoothOppReceiver.class.getName());
startActivity(in1);
 
 
  
  

Le DevicePickerActivity correspondant à l’action se trouve dans AndroidManifest.xml dans l’application Paramètres système, donc le saut passera à DevicePickerActivity dans l’application Paramètres système.

<activity android:name=".bluetooth.DevicePickerActivity"
        android:uiOptions="splitActionBarWhenNarrow"
        android:theme="@android:style/Theme.Holo.DialogWhenLarge"
        android:label="@string/device_picker"
        android:clearTaskOnLaunch="true">
    <intent-filter>
        <action android:name="android.bluetooth.devicepicker.action.LAUNCH" />
        <category android:name="android.intent.category.DEFAULT" />
    </intent-filter>
</activity>
 
 
  
  

2 Sélecteur de périphériques

Le code dans DevicePickerActivity est très simple, il suffit de définir la mise en page.
setContentView(R.layout.bluetooth_device_picker);
Il y a un fragment dans bluetooth_device_picker.xml qui pointe vers DevicePickerFragment, c'est-à-dire que le traitement principal est dans DevicePickerFragment.
L' interface DevicePickerFragment affichera la liste Bluetooth couplée et analysée. Vous pouvez cliquer sur un appareil pour partager des fichiers.

void onDevicePreferenceClick(BluetoothDevicePreference btPreference) {
    mLocalAdapter.stopScanning(); //停止扫描
    LocalBluetoothPreferences.persistSelectedDeviceInPicker(
            getActivity(), mSelectedDevice.getAddress());
    if ((btPreference.getCachedDevice().getBondState() ==
            BluetoothDevice.BOND_BONDED) || !mNeedAuth) {
        sendDevicePickedIntent(mSelectedDevice);
        finish();
    } else {
        super.onDevicePreferenceClick(btPreference);
    }
}
 
 
  
  

Cliquer sur l'appareil déterminera s'il est dans l'état de liaison ou si mNeedAuth est faux. La valeur transmise par mNeedAuth via l'intention est fausse. Les conditions sont donc réunies.
Regardez ensuite sendDevicePickedIntent(). Cette fonction envoie une diffusion.

private void sendDevicePickedIntent(BluetoothDevice device) {
    //"android.bluetooth.devicepicker.action.DEVICE_SELECTED"
    Intent intent = new Intent(BluetoothDevicePicker.ACTION_DEVICE_SELECTED);
    intent.putExtra(BluetoothDevice.EXTRA_DEVICE, device);
    if (mLaunchPackage != null && mLaunchClass != null) {
        intent.setClassName(mLaunchPackage, mLaunchClass);
    }
    getActivity().sendBroadcast(intent);
}
 
 
  
  

3 Récepteur Opp Bluetooth

Vérifiez que cette diffusion est traitée dans la classe BluetoothOppReceiver dans l'application système Bluetooth . Toutefois, l’enregistrement du récepteur de diffusion dans AndroidManifest.xml en Bluetooth n’ajoute pas cette action. Cependant, l'émission peut être reçue. La raison devrait être que la diffusion porte le nom du package et le nom de la classe lors de son envoi.

<receiver
    android:process="@string/process"
    android:exported="true"
    android:name=".opp.BluetoothOppReceiver"
    android:enabled="@bool/profile_supported_opp">
    <intent-filter>
        <action android:name="android.bluetooth.adapter.action.STATE_CHANGED" />
        <!--action android:name="android.intent.action.BOOT_COMPLETED" /-->
        <action android:name="android.btopp.intent.action.OPEN_RECEIVED_FILES" />
    </intent-filter>
</receiver>  
 
 
  
  

Le code de traitement principal de BluetoothOppReceiver après réception de cette diffusion est le suivant, ajoutant cet enregistrement à la base de données.

// Insert transfer session record to database
mOppManager.startTransfer(remoteDevice);
 
 
  
  

L'objet BluetoothOppManager appelle la méthode startTransfer. Créez un thread InsertShareInfoThread dans la méthode startTransfer et démarrez l'exécution.
Dans le fil de discussion InsertShareInfoThread, il est distingué si un ou plusieurs fichiers sont partagés. Nous examinons ici uniquement la fonction insertSingleShare() pour traiter un seul fichier.

if (mIsMultiple) {//多个文件
    insertMultipleShare();
} else { //单个文件
    insertSingleShare();
}

private void insertSingleShare() {
    ContentValues values = new ContentValues();
    values.put(BluetoothShare.URI, mUri);
    values.put(BluetoothShare.MIMETYPE, mTypeOfSingleFile);
    values.put(BluetoothShare.DESTINATION, mRemoteDevice.getAddress());
    if (mIsHandoverInitiated) {
        values.put(BluetoothShare.USER_CONFIRMATION,
                BluetoothShare.USER_CONFIRMATION_HANDOVER_CONFIRMED);
    }
    final Uri contentUri = mContext.getContentResolver().insert(BluetoothShare.CONTENT_URI,
            values);
} 
 
 
  
  

À partir de mContext.getContentResolver().insert(), nous pouvons savoir qu'il a un fournisseur correspondant. BluetoothOppProvider hérite de ContextProvider. Découvrez la méthode d'insertion dans BluetoothOppProvider.

public Uri insert(Uri uri, ContentValues values) {
.....
if (rowID != -1) {
    context.startService(new Intent(context, BluetoothOppService.class));
    ret = Uri.parse(BluetoothShare.CONTENT_URI + "/" + rowID);
    context.getContentResolver().notifyChange(uri, null);
}
 
 
  
  

Il ressort de ce qui précède que BluetoothOppService sera démarré lors du partage via Bluetooth.


4 Service Opp Bluetooth

Dans BluetoothOppService, les modifications apportées au champ de base de données (BluetoothShare.CONTENT_URI) seront surveillées et la fonction updateFromProvider() sera appelée pour traitement. Les fonctions onCreate() et onStartCommand() appellent updateFromProvider().
updateFromProvider() -> Créer un fil UpdateThread -> insertShare().

private void insertShare(Cursor cursor, int arrayPos) {
     if (info.isReadyToStart()) {
         if (info.mDirection == BluetoothShare.DIRECTION_OUTBOUND) { //向外分享、发送
             /* 检查文件是否存在 */
         }
     }
     if (mBatchs.size() == 0) {
         if (info.mDirection == BluetoothShare.DIRECTION_OUTBOUND)  {
   
   //向外分享、发送
             mTransfer = new BluetoothOppTransfer(this, mPowerManager, newBatch);
         } else if (info.mDirection == BluetoothShare.DIRECTION_INBOUND) { //接收
             mServerTransfer = new BluetoothOppTransfer(this, mPowerManager, newBatch,
                     mServerSession);
         }

         if (info.mDirection == BluetoothShare.DIRECTION_OUTBOUND && mTransfer != null) {
             mTransfer.start();
         } else if (info.mDirection == BluetoothShare.DIRECTION_INBOUND
                 && mServerTransfer != null) {
             mServerTransfer.start();
         }
     }
     //........
}
 
 
  
  

5Transfert Opp Bluetooth

Ici on ne parle que d'envoi et de partage. Alors regardez BluetoothOppTransfer.

public void start() {
    //检查蓝牙是否打开,保证安全
    if (!mAdapter.isEnabled()) {
        return;
    }
    if (mHandlerThread == null) {
        //......
        if (mBatch.mDirection == BluetoothShare.DIRECTION_OUTBOUND) {
            /* for outbound transfer, we do connect first */
            startConnectSession();
        } 
        //....
    }
}
 
 
  
  

La fonction startConnectSession() commence à se connecter au périphérique distant. Cette fonction crée principalement un thread SocketConnectThread pour se connecter à d'autres périphériques.
Code principal du thread SocketConnectThread :

try {
    
    
    if (mIsInterrupted) {
    
    
        Log.e(TAG, "btSocket connect interrupted ");
        markConnectionFailed(mBtSocket);
        return;
    } else {
    
    
        mBtSocket = mDevice.createInsecureL2capSocket(mL2cChannel);
    }
} catch (IOException e1) {
    
    
    Log.e(TAG, "L2cap socket create error", e1);
    connectRfcommSocket();
    return;
}
try {
    
    
	mBtSocket.connect();
	if (D) {
    
    
	    Log.v(TAG, "L2cap socket connection attempt took " + (System.currentTimeMillis()
	            - mTimestamp) + " ms");
	}
	BluetoothObexTransport transport;
	transport = new BluetoothObexTransport(mBtSocket);
	BluetoothOppPreference.getInstance(mContext).setName(mDevice, mDevice.getName());
	if (V) {
    
    
	    Log.v(TAG, "Send transport message " + transport.toString());
	}
	mSessionHandler.obtainMessage(TRANSPORT_CONNECTED, transport).sendToTarget();
}
} catch (IOException e) {
    
    
	\...
}

Ici, nous créons d’abord un BluetoothSocket, puis nous nous connectons via le BluetoothSocket.
Le socket doit être L2capSocket.
transport = new BluetoothObexTransport(mBtSocket); sera utilisé plus tard.

case TRANSPORT_CONNECTED:
    /*
    * RFCOMM connected is for outbound share only! Create
    * BluetoothOppObexClientSession and start it
    */
    if (V) {
    
    
        Log.v(TAG, "Transfer receive TRANSPORT_CONNECTED msg");
    }

    mTransport = (ObexTransport) msg.obj;
    startObexSession();

    break;

连接成功后,通过Handler调用startObexSession()->new BluetoothOppObexClientSession ->BluetoothOppObexClientSession .start()


6 BluetoothOppObexClientSession

BluetoothOppObexClientSession类说明该设备作为obex client,向server发送文件。该类中主要功能:obex连接、发送分享文件的信息,发送数据等。
start() -> 创建ClientThread线程并运行 -> connect()。
connect()函数中,通过mTransport1(BluetoothOppRfcommTransport类型,该类型中主要包含之前创建的BluetoothSocket)对象,创建client session,连接远端设备。

private void connect(int numShares) {
    try {
   
   //创建obex client
        mCs = new ClientSession(mTransport1);
        mConnected = true;
    } catch (IOException e1) {
    }
    if (mConnected) {
        mConnected = false;
        HeaderSet hs = new HeaderSet(); //obex 连接携带信息
        hs.setHeader(HeaderSet.COUNT, (long) numShares);//文件数量
        synchronized (this) {
            mWaitingForRemote = true;
        }
        try { //obex连接
            mCs.connect(hs);
            mConnected = true;
        } catch (IOException e) {
        }
    }
    //.....
}
 
 
  
  

obex连接成功后,调用doSend(),该函数中先检查下文件是否存在,然后查看连接状态,连接状态下并且存在文件则sendFile才真正的开始发送文件。之会将相应的状态发送到BluetoothOppTransfer中。

private void doSend() {
    int status = BluetoothShare.STATUS_SUCCESS;
    while (mFileInfo == null) { //检查文件是否存在
        try {
            Thread.sleep(50);
        } catch (InterruptedException e) {
            status = BluetoothShare.STATUS_CANCELED;
        }
    }
    //检查连接状态
    if (!mConnected) {
        status = BluetoothShare.STATUS_CONNECTION_ERROR;
    }
    if (status == BluetoothShare.STATUS_SUCCESS) {
        /* 发送文件*/
        if (mFileInfo.mFileName != null) {
            status = sendFile(mFileInfo);
        } else {
            status = mFileInfo.mStatus;
        }
        waitingForShare = true;
    } else {
        Constants.updateShareStatus(mContext1, mInfo.mId, status);
    }
    //发送此次操作是否成功等信息。
}
 
 
  
  

真正的发送文件是在sendFile()函数中。不过该函数太长就不全贴出来了,只说一下重要的地方。

1 发送文件信息

HeaderSet request = new HeaderSet();
request.setHeader(HeaderSet.NAME, fileInfo.mFileName);  //文件名
request.setHeader(HeaderSet.TYPE, fileInfo.mMimetype);   //文件类型
request.setHeader(HeaderSet.LENGTH, fileInfo.mLength);  //文件大小
//通过obex发送传递文件请求
putOperation = (ClientOperation)mCs.put(request);
//putOperation类型为ClientOperation,具体java.obex包下的类没有向外透漏,不太清楚是具体怎么回事。
 
 
  
  

2 获取obex层输入输出流

 //获取输入输出流。
outputStream = putOperation.openOutputStream();
inputStream = putOperation.openInputStream();
 
 
  
  

3 发送第一个包

//从文件中读取内容
BufferedInputStream a = new BufferedInputStream(fileInfo.mInputStream, 0x4000);
readLength = readFully(a, buffer, outputBufferSize);
//先向远程设备发送第一个包 该操作会阻塞等待远端设备的接收读取。
outputStream.write(buffer, 0, readLength);
position += readLength;
如果文件太小,一个包就已经发送完,则将输出流关闭。outputStream.close();
 
 
  
  

4 查看回应
接着查看远端设备的回应,是否接受。

/* check remote accept or reject */
responseCode = putOperation.getResponseCode();
if (responseCode == ResponseCodes.OBEX_HTTP_CONTINUE
     || responseCode == ResponseCodes.OBEX_HTTP_OK) {
     //接收
     okToProceed = true;
     updateValues = new ContentValues();
     updateValues.put(BluetoothShare.CURRENT_BYTES, position);
     mContext1.getContentResolver().update(contentUri, updateValues, null,
                                    null);
} else {
   
   //拒绝接收
      Log.i(TAG, "Remote reject, Response code is " + responseCode);
}
 
 
  
  

5 判断发送数据
接着循环判断、从文件读取数据、发送数据。

while (!mInterrupted && okToProceed && (position != fileInfo.mLength)) {
    readLength = a.read(buffer, 0, outputBufferSize);
    outputStream.write(buffer, 0, readLength);

    /* check remote abort */
    responseCode = putOperation.getResponseCode();
    if (responseCode != ResponseCodes.OBEX_HTTP_CONTINUE
            && responseCode != ResponseCodes.OBEX_HTTP_OK) {
        okToProceed = false;
    } else {
        position += readLength;
        //更行进度
        updateValues = new ContentValues();
        updateValues.put(BluetoothShare.CURRENT_BYTES, position);
        mContext1.getContentResolver().update(contentUri, updateValues,
                null, null);
    }
}
 
 
  
  

outputStream是PrivateOutputStream对象

PrivateOutputStream.java
public synchronized void write(byte[] buffer, int offset, int count) throws IOException {
    
    
    int offset1 = offset;
    int remainLength = count;

    if (buffer == null) {
    
    
        throw new IOException("buffer is null");
    }
    if ((offset | count) < 0 || count > buffer.length - offset) {
    
    
        throw new IndexOutOfBoundsException("index outof bound");
    }

    ensureOpen();
    mParent.ensureNotDone();
    while ((mArray.size() + remainLength) >= mMaxPacketSize) {
    
    
        int bufferLeft = mMaxPacketSize - mArray.size();
        mArray.write(buffer, offset1, bufferLeft);
        offset1 += bufferLeft;
        remainLength -= bufferLeft;
        mParent.continueOperation(true, false);
    }
    if (remainLength > 0) {
    
    
        mArray.write(buffer, offset1, remainLength);
    }
}
public synchronized boolean continueOperation(boolean sendEmpty, boolean inStream)
         throws IOException {
    
    

     // One path to the first put operation - the other one does not need to
     // handle SRM, as all will fit into one packet.

     if (mGetOperation) {
    
    
         ...
     } else {
    
    
         // PUT operation
         if ((!inStream) && (!mOperationDone)) {
    
    
             // to deal with outputstream in put operation
             if (mReplyHeader.responseCode == -1) {
    
    
                 mReplyHeader.responseCode = ResponseCodes.OBEX_HTTP_CONTINUE;
             }
             sendRequest(ObexHelper.OBEX_OPCODE_PUT);
             return true;
         } else if ((inStream) && (!mOperationDone)) {
    
    
             // How to deal with inputstream  in put operation ?
             return false;

         } else if (mOperationDone) {
    
    
             return false;
         }

     }
     return false;
 }

mGetOperation为ClientOperation构造函数中的type。

public ClientOperation(int maxSize, ClientSession p, HeaderSet header, boolean type)
        throws IOException {
    
    

    mParent = p;
    mEndOfBodySent = false;
    mInputOpen = true;
    mOperationDone = false;
    mMaxPacketSize = maxSize;
    mGetOperation = type;
    ...
}

前面有putOperation = (ClientOperation)mCs.put(request); type赋值为false。

ClientSession.java
public Operation put(HeaderSet header) throws IOException {
    
    
    ...
    return new ClientOperation(mMaxTxPacketSize, this, head, false);
}

因此后面调用sendRequest(ObexHelper.OBEX_OPCODE_PUT);

后面和connect流程相同。

Je suppose que tu aimes

Origine blog.csdn.net/a13821684483/article/details/102776350
conseillé
Classement