代码使用的是Android T。首先我们知道蓝牙传输文件属于OPP,
代码位置:http://aospxref.com/android-13.0.0_r3/xref/packages/modules/Bluetooth/android/app/src/com/android/bluetooth/opp/
比如现在在图库中选中一张图片,点击“分享”然后选择蓝牙的方式,此时就会启动:BluetoothOppLauncherActivity.java
http://aospxref.com/android-13.0.0_r3/xref/packages/modules/Bluetooth/android/app/src/com/android/bluetooth/opp/
BluetoothOppLauncherActivity.java#onCreate
这里面主要理清楚发送单个文件和多个文件即可,这里我们先看单个文件:
// 单个文件:Intent.ACTION_SEND
if (action.equals(Intent.ACTION_SEND)) {
// type 就是类型,比如是图片,还是视频
final String type = intent.getType();
// 1. stream != null && type != null : 文件类型
// 2. extraText != null && type != null : 文本类型
final Uri stream = (Uri) intent.getParcelableExtra(Intent.EXTRA_STREAM);
CharSequence extraText = intent.getCharSequenceExtra(Intent.EXTRA_TEXT);
if (stream != null && type != null) {
// Get ACTION_SEND intent: Uri = content://0@media/external/images/media/1125; mimetype = image/jpeg
Log.v(TAG, "Get ACTION_SEND intent: Uri = " + stream + "; mimetype = " + type);
// Save type/stream, will be used when adding transfer session to DB.
Thread t = new Thread(new Runnable() {
@Override
public void run() {
sendFileInfo(type, stream.toString(), false /* isHandover */, true /* fromExternal */);
}
});
t.start();
return;
}
private void sendFileInfo(String mimeType, String uriString, boolean isHandover, boolean fromExternal) {
BluetoothOppManager manager = BluetoothOppManager.getInstance(getApplicationContext());
try {
manager.saveSendingFileInfo(mimeType, uriString, isHandover, fromExternal); ---> 先把信息存起来
launchDevicePicker(); ---> 这里面判断一些蓝牙是否打开以及打开了处理的后续
finish();
} catch (IllegalArgumentException exception) {
showToast(exception.getMessage());
finish();
}
}
接着上面的,先看下发送前的存储过程:
http://aospxref.com/android-13.0.0_r3/xref/packages/modules/Bluetooth/android/app/src/com/android/bluetooth/opp/
BluetoothOppManager.java#saveSendingFileInfo
public void saveSendingFileInfo(String mimeType, String uriString, boolean isHandover, boolean fromExternal) throws IllegalArgumentException {
synchronized (BluetoothOppManager.this) {
mMultipleFlag = false; ---> 单个文件标志位
mMimeTypeOfSendingFile = mimeType; ---> 文件类型(图片、视频还是音乐类型)
mIsHandoverInitiated = isHandover; ---> 此次文件传输是否是由NFC发起,应为nfc传输文件实际上走的也是蓝牙opp传输
Uri uri = Uri.parse(uriString); ---> 路径转换成Uri
//根据信息作一些判断,最终变成BluetoothOppSendFileInfo
BluetoothOppSendFileInfo sendFileInfo = BluetoothOppSendFileInfo.generateFileInfo(mContext, uri, mimeType, fromExternal);
// sendFileInfo 和 uri 对应起来
uri = BluetoothOppUtility.generateUri(uri, sendFileInfo);
// 放到ConcurrentHashMap sSendFileMap中
BluetoothOppUtility.putSendFileInfo(uri, sendFileInfo);
mUriOfSendingFile = uri.toString();
// 用SharedPreference存储
storeApplicationData();
}
}
信息存储看完了,在看launchDevicePicker()方法做了啥
http://aospxref.com/android-13.0.0_r3/xref/packages/modules/Bluetooth/android/app/src/com/android/bluetooth/opp/
BluetoothOppLauncherActivity.java#launchDevicePicker
private void launchDevicePicker() {
// TODO: In the future, we may send intent to DevicePickerActivity directly,
// and let DevicePickerActivity to handle Bluetooth Enable.
if (!BluetoothOppManager.getInstance(this).isEnabled()) {
if (V) {
Log.v(TAG, "Prepare Enable BT!! ");
}
// 蓝牙如果没有打开,就转到 BluetoothOppBtEnableActivity 类里面
Intent in = new Intent(this, BluetoothOppBtEnableActivity.class);
in.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
startActivity(in);
} else {
if (V) {
Log.v(TAG, "BT already enabled!! ");
}
// 蓝牙打开就转到 BluetoothDevicePicker 类里面
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, getPackageName());
in1.putExtra(BluetoothDevicePicker.EXTRA_LAUNCH_CLASS, BluetoothOppReceiver.class.getName());
if (V) {
Log.d(TAG, "Launching " + BluetoothDevicePicker.ACTION_LAUNCH);
}
startActivity(in1);
}
}
继续看蓝牙未打开时候的场景
packages/modules/Bluetooth/android/app/src/com/android/bluetooth/opp/BluetoothOppBtEnableActivity.java
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
getWindow().addSystemFlags(SYSTEM_FLAG_HIDE_NON_SYSTEM_OVERLAY_WINDOWS);
// Set up the "dialog"
mOppManager = BluetoothOppManager.getInstance(this);
mOppManager.mSendingFlag = false;
mAlertBuilder.setIconAttribute(android.R.attr.alertDialogIcon);
mAlertBuilder.setTitle(getString(R.string.bt_enable_title));
mAlertBuilder.setView(createView());
// 弹框询问是否打开 蓝牙,这里我们点击Ok
mAlertBuilder.setPositiveButton(R.string.bt_enable_ok, (dialog, which) -> onEnableBluetooth());
mAlertBuilder.setNegativeButton(R.string.bt_enable_cancel, (dialog, which) -> finish());
setupAlert();
}
// 点击Ok确认后,执行 onEnableBluetooth方法:
private void onEnableBluetooth {
// 打开蓝牙的操作
mOppManager.enableBluetooth(); // this is an asyn call
mOppManager.mSendingFlag = true; ---> 标志位,表明发送文件
Toast.makeText(this, getString(R.string.enabling_progress_content), Toast.LENGTH_SHORT).show();
// 启动 BluetoothOppBtEnablingActivity
Intent in = new Intent(this, BluetoothOppBtEnablingActivity.class);
in.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
this.startActivity(in);
finish();
}
继续往下看,启动BluetoothOppBtEnablingActivity
http://aospxref.com/android-13.0.0_r3/xref/packages/modules/Bluetooth/android/app/src/com/android/bluetooth/opp/
BluetoothOppBtEnablingActivity.java
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
// 表明窗口不被覆盖
getWindow().addSystemFlags(SYSTEM_FLAG_HIDE_NON_SYSTEM_OVERLAY_WINDOWS);
// If BT is already enabled jus return.
BluetoothAdapter adapter = BluetoothAdapter.getDefaultAdapter();
if (adapter.isEnabled()) { ---> 判断现在的状态是不是 BluetoothAdapter.STATE_ON
finish();
return;
}
//前面已经有打开蓝牙的操作了,这里注册一个蓝牙状态变化
IntentFilter filter = new IntentFilter(BluetoothAdapter.ACTION_STATE_CHANGED);
registerReceiver(mBluetoothReceiver, filter);
mRegistered = true;
mAlertBuilder.setTitle(R.string.enabling_progress_title);
mAlertBuilder.setView(createView());
setupAlert();
// Add timeout for enabling progress 这里超时是20s
mTimeoutHandler.sendMessageDelayed(mTimeoutHandler.obtainMessage(BT_ENABLING_TIMEOUT), BT_ENABLING_TIMEOUT_VALUE);
}
至此,我们就干了两件事:1. 把要发送的图片信息存下来,原因是如果蓝牙服务有问题,恢复了还可以继续;2. 打开蓝牙
上面我们看了蓝牙未打开的分支,现在看下打开后的分支,打开过就转到这里 BluetoothDevicePicker.ACTION_LAUNCH,注意,我们上面看到的都是转到什么class类里面,这里ACTION_LAUNCH是什么呢?我们需要看下代码里面的定义:http://aospxref.com/android-13.0.0_r3/xref/packages/modules/Bluetooth/framework/java/android/bluetooth/BluetoothDevicePicker.java#ACTION_LAUNCH
ACTION_LAUNCH = “android.bluetooth.devicepicker.action.LAUNCH”; 对应的声明在这里:
对应的 DevicePickerActivity 其实就是加载一个 Fragment,这个对应的就是:DevicePickerFragment.java
DevicePickerFragment 类,有选择设备发送的操作,然后发送BluetoothDevicePicker.ACTION_DEVICE_SELECTED广播
具体的可以自己看下:http://aospxref.com/android-13.0.0_r3/xref/packages/apps/Settings/src/com/android/settings/bluetooth/
DevicePickerFragment.java#onDevicePreferenceClick
处理这个广播的逻辑在:http://aospxref.com/android-13.0.0_r3/xref/packages/modules/Bluetooth/android/app/src/com/android
/bluetooth/opp/BluetoothOppReceiver.java
public void onReceive(Context context, Intent intent) {
String action = intent.getAction();
if (D) Log.d(TAG, " action :" + action);
if (action == null) return;
if (action.equals(BluetoothDevicePicker.ACTION_DEVICE_SELECTED)) { ---> 选中的蓝牙设备
BluetoothOppManager mOppManager = BluetoothOppManager.getInstance(context);
// 选中蓝牙的信息mac
BluetoothDevice remoteDevice = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);
Log.d(TAG, "Received BT device selected intent, bt device: " + remoteDevice);
if (remoteDevice == null) {
mOppManager.cleanUpSendingFileInfo();
return;
}
// Insert transfer session record to database
// 这里面注意最大是3个同时发,多了就会报错,具体的看代码
mOppManager.startTransfer(remoteDevice);
// Display toast message
String deviceName = mOppManager.getDeviceName(remoteDevice);
String toastMsg;
int batchSize = mOppManager.getBatchSize();
if (mOppManager.mMultipleFlag) {
toastMsg = context.getString(R.string.bt_toast_5, Integer.toString(batchSize), deviceName);
} else {
toastMsg = context.getString(R.string.bt_toast_4, deviceName);
}
Toast.makeText(context, toastMsg, Toast.LENGTH_SHORT).show();
}
mOppManager.startTransfer(remoteDevice); 这个方法里,我们为了追流程,只看和流程相关的:
http://aospxref.com/android-13.0.0_r3/xref/packages/modules/Bluetooth/android/app/src/com/android/bluetooth/opp/
BluetoothOppManager.java#414
这里是发送单个图片,直接看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);
}
// 主要看下这个往数据库写入的操作 CONTENT_URI = Uri.parse("content://com.android.bluetooth.opp/btopp")
final Uri contentUri = mContext.getContentResolver().insert(BluetoothShare.CONTENT_URI, values);
Log.v(TAG, "Insert contentUri: " + contentUri + " to device: " + getDeviceName(mRemoteDevice));
}
}
mContext.getContentResolver().insert(BluetoothShare.CONTENT_URI, values); 数据库的操作,最终操作的是BluetoothOppProvider,不清楚的可以去看下Android 数据库操作这块。
http://aospxref.com/android-13.0.0_r3/xref/packages/modules/Bluetooth/android/app/src/com/android/bluetooth/opp/
BluetoothOppProvider.java#insert
public Uri insert(Uri uri, ContentValues values) {
SQLiteDatabase db = mOpenHelper.getWritableDatabase();
if (sURIMatcher.match(uri) != SHARES) {
throw new IllegalArgumentException("insert: Unknown/Invalid URI " + uri);
}
ContentValues filteredValues = new ContentValues();
copyString(BluetoothShare.URI, values, filteredValues);
copyString(BluetoothShare.FILENAME_HINT, values, filteredValues);
copyString(BluetoothShare.MIMETYPE, values, filteredValues);
copyString(BluetoothShare.DESTINATION, values, filteredValues);
copyInteger(BluetoothShare.VISIBILITY, values, filteredValues);
copyLong(BluetoothShare.TOTAL_BYTES, values, filteredValues);
if (values.getAsInteger(BluetoothShare.VISIBILITY) == null) {
filteredValues.put(BluetoothShare.VISIBILITY, BluetoothShare.VISIBILITY_VISIBLE);
}
Integer dir = values.getAsInteger(BluetoothShare.DIRECTION);
Integer con = values.getAsInteger(BluetoothShare.USER_CONFIRMATION);
if (dir == null) {
dir = BluetoothShare.DIRECTION_OUTBOUND;
}
if (dir == BluetoothShare.DIRECTION_OUTBOUND && con == null) {
con = BluetoothShare.USER_CONFIRMATION_AUTO_CONFIRMED;
}
if (dir == BluetoothShare.DIRECTION_INBOUND && con == null) {
con = BluetoothShare.USER_CONFIRMATION_PENDING;
}
filteredValues.put(BluetoothShare.USER_CONFIRMATION, con);
filteredValues.put(BluetoothShare.DIRECTION, dir);
filteredValues.put(BluetoothShare.STATUS, BluetoothShare.STATUS_PENDING);
filteredValues.put(Constants.MEDIA_SCANNED, 0);
Long ts = values.getAsLong(BluetoothShare.TIMESTAMP);
if (ts == null) {
ts = System.currentTimeMillis();
}
filteredValues.put(BluetoothShare.TIMESTAMP, ts);
Context context = getContext();
long rowID = db.insert(DB_TABLE, null, filteredValues);
if (rowID == -1) {
Log.w(TAG, "couldn't insert " + uri + "into btopp database");
return null;
}
context.getContentResolver().notifyChange(uri, null);
return Uri.parse(BluetoothShare.CONTENT_URI + "/" + rowID);
}
现在数据库发生了变化,那监听数据库的地方就会收到,此时我们可以知道 BluetoothOppService 类中有监听的地方,下面代码看下:
// 注册 ContentObserver
mObserver = new BluetoothShareContentObserver();
getContentResolver().registerContentObserver(BluetoothShare.CONTENT_URI, true, mObserver);
// 变换后调用其 onChange 方法:
private class BluetoothShareContentObserver extends ContentObserver {
BluetoothShareContentObserver() {
super(new Handler());
}
@Override
public void onChange(boolean selfChange) {
if (V) {
Log.v(TAG, "ContentObserver received notification");
}
updateFromProvider(); ---> 执行这个
}
}
private void updateFromProvider() {
synchronized (BluetoothOppService.this) {
mPendingUpdate = true;
if (mUpdateThread == null) {
mUpdateThread = new UpdateThread();
mUpdateThread.start(); ---> 启动
mUpdateThreadRunning = true;
}
}
}
// 对应的线程run方法:
public void run() {
// 设置线程优先级为后台线程
Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);
// 检查是否存在多个 UpdateThread 实例,如果存在则抛出异常
while (!mIsInterrupted) {
synchronized (BluetoothOppService.this) {
if (mUpdateThread != this) {
mUpdateThreadRunning = false;
throw new IllegalStateException("multiple UpdateThreads in BluetoothOppService");
}
Log.v(TAG, "pendingUpdate is " + mPendingUpdate + " sListenStarted is "
+ mListenStarted + " isInterrupted :" + mIsInterrupted);
// 如果没有待处理的更新任务,则将 UpdateThread 置为 null,结束线程的执行
if (!mPendingUpdate) {
mUpdateThread = null;
mUpdateThreadRunning = false;
return;
}
mPendingUpdate = false;
}
// 从内容解析器查询蓝牙共享数据的游标,如果游标为空,则结束线程的执行
Cursor cursor = getContentResolver().query(BluetoothShare.CONTENT_URI, null, null, null, BluetoothShare._ID);
if (cursor == null) {
mUpdateThreadRunning = false;
return;
}
// 将游标移动到第一行
cursor.moveToFirst();
int arrayPos = 0;
// 检查游标是否已经移动到最后一行之后
boolean isAfterLast = cursor.isAfterLast();
// 获取游标中 ID 列的索引
int idColumn = cursor.getColumnIndexOrThrow(BluetoothShare._ID);
/*
* 遍历游标和本地数组,使它们保持同步。算法的关键在于 ID 在游标和数组中都是唯一且排序的,
* 因此可以同时按顺序处理这两个来源中的最小 ID:每一步,两个来源都指向尚未从该来源处理的最小 ID,
* 算法会处理这两个可能性中的最小 ID。每一步操作如下:
* - 如果数组中包含游标中不存在的条目,则删除该条目,移至数组的下一个条目。
* - 如果数组中包含游标中存在的条目,则无需操作,移至下一个游标行和下一个数组条目。
* - 如果游标中包含数组中不存在的条目,则在数组中插入一个新条目,移至下一个游标行和下一个数组条目。
*/
while (!isAfterLast || arrayPos < mShares.size() && mListenStarted) {
if (isAfterLast) {
// 当游标已经超出范围但本地数组中仍有一些内容时,这些内容只能是无用的
if (mShares.size() != 0) {
if (V) {
Log.v(TAG, "Array update: trimming " + mShares.get(arrayPos).mId
+ " @ " + arrayPos);
}
}
deleteShare(arrayPos); // 删除该位置的共享条目,继续下一个位置的处理
} else {
int id = cursor.getInt(idColumn);
if (arrayPos == mShares.size()) {
insertShare(cursor, arrayPos); ---> 执行这个
if (V) {
Log.v(TAG, "Array update: inserting " + id + " @ " + arrayPos);
}
++arrayPos;
cursor.moveToNext();
isAfterLast = cursor.isAfterLast();
} else {
int arrayId = 0;
if (mShares.size() != 0) {
arrayId = mShares.get(arrayPos).mId;
}
if (arrayId < id) {
if (V) {
Log.v(TAG, "Array update: removing " + arrayId + " @ " + arrayPos);
}
deleteShare(arrayPos);
} else if (arrayId == id) {
// 该游标行已存在于存储的数组中
updateShare(cursor, arrayPos);
scanFileIfNeeded(arrayPos);
++arrayPos;
cursor.moveToNext();
isAfterLast = cursor.isAfterLast();
} else {
// 该游标条目在存储的数组中不存在
if (V) {
Log.v(TAG, "Array update: appending " + id + " @ " + arrayPos);
}
insertShare(cursor, arrayPos);
++arrayPos;
cursor.moveToNext();
isAfterLast = cursor.isAfterLast();
}
}
}
}
mNotifier.updateNotification();
cursor.close();
}
mUpdateThreadRunning = false;
}
private void insertShare(Cursor cursor, int arrayPos) {
String uriString = cursor.getString(cursor.getColumnIndexOrThrow(BluetoothShare.URI));
Uri uri;
if (uriString != null) {
uri = Uri.parse(uriString);
Log.d(TAG, "insertShare parsed URI: " + uri);
} else {
uri = null;
Log.e(TAG, "insertShare found null URI at cursor!");
}
BluetoothOppShareInfo info = new BluetoothOppShareInfo(
cursor.getInt(cursor.getColumnIndexOrThrow(BluetoothShare._ID)), uri,
cursor.getString(cursor.getColumnIndexOrThrow(BluetoothShare.FILENAME_HINT)),
cursor.getString(cursor.getColumnIndexOrThrow(BluetoothShare._DATA)),
cursor.getString(cursor.getColumnIndexOrThrow(BluetoothShare.MIMETYPE)),
cursor.getInt(cursor.getColumnIndexOrThrow(BluetoothShare.DIRECTION)),
cursor.getString(cursor.getColumnIndexOrThrow(BluetoothShare.DESTINATION)),
cursor.getInt(cursor.getColumnIndexOrThrow(BluetoothShare.VISIBILITY)),
cursor.getInt(cursor.getColumnIndexOrThrow(BluetoothShare.USER_CONFIRMATION)),
cursor.getInt(cursor.getColumnIndexOrThrow(BluetoothShare.STATUS)),
cursor.getLong(cursor.getColumnIndexOrThrow(BluetoothShare.TOTAL_BYTES)),
cursor.getLong(cursor.getColumnIndexOrThrow(BluetoothShare.CURRENT_BYTES)),
cursor.getLong(cursor.getColumnIndexOrThrow(BluetoothShare.TIMESTAMP)),
cursor.getInt(cursor.getColumnIndexOrThrow(Constants.MEDIA_SCANNED))
!= Constants.MEDIA_SCANNED_NOT_SCANNED);
if (V) {
Log.v(TAG, "Service adding new entry");
Log.v(TAG, "ID : " + info.mId);
// Log.v(TAG, "URI : " + ((info.mUri != null) ? "yes" : "no"));
Log.v(TAG, "URI : " + info.mUri);
Log.v(TAG, "HINT : " + info.mHint);
Log.v(TAG, "FILENAME: " + info.mFilename);
Log.v(TAG, "MIMETYPE: " + info.mMimetype);
Log.v(TAG, "DIRECTION: " + info.mDirection);
Log.v(TAG, "DESTINAT: " + info.mDestination);
Log.v(TAG, "VISIBILI: " + info.mVisibility);
Log.v(TAG, "CONFIRM : " + info.mConfirm);
Log.v(TAG, "STATUS : " + info.mStatus);
Log.v(TAG, "TOTAL : " + info.mTotalBytes);
Log.v(TAG, "CURRENT : " + info.mCurrentBytes);
Log.v(TAG, "TIMESTAMP : " + info.mTimestamp);
Log.v(TAG, "SCANNED : " + info.mMediaScanned);
}
mShares.add(arrayPos, info);
/* Mark the info as failed if it's in invalid status */
if (info.isObsolete()) {
Constants.updateShareStatus(this, info.mId, BluetoothShare.STATUS_UNKNOWN_ERROR);
}
/*
* Add info into a batch. The logic is
* 1) Only add valid and readyToStart info
* 2) If there is no batch, create a batch and insert this transfer into batch,
* then run the batch
* 3) If there is existing batch and timestamp match, insert transfer into batch
* 4) If there is existing batch and timestamp does not match, create a new batch and
* put in queue
*/
// BluetoothShare.DIRECTION_OUTBOUND 指的是发送
// BluetoothShare.DIRECTION_INBOUND 指的是接收
if (info.isReadyToStart()) {
if (info.mDirection == BluetoothShare.DIRECTION_OUTBOUND) {
/* check if the file exists */
BluetoothOppSendFileInfo sendFileInfo = BluetoothOppUtility.getSendFileInfo(info.mUri);
if (sendFileInfo == null || sendFileInfo.mInputStream == null) {
Log.e(TAG, "Can't open file for OUTBOUND info " + info.mId);
Constants.updateShareStatus(this, info.mId, BluetoothShare.STATUS_BAD_REQUEST);
BluetoothOppUtility.closeSendFileInfo(info.mUri);
return;
}
}
if (mBatches.size() == 0) {
BluetoothOppBatch newBatch = new BluetoothOppBatch(this, info);
newBatch.mId = mBatchId;
mBatchId++;
mBatches.add(newBatch);
if (info.mDirection == BluetoothShare.DIRECTION_OUTBOUND) {
if (V) {
Log.v(TAG,
"Service create new Batch " + newBatch.mId + " for OUTBOUND info "
+ info.mId);
}
mTransfer = new BluetoothOppTransfer(this, newBatch);
} else if (info.mDirection == BluetoothShare.DIRECTION_INBOUND) {
if (V) {
Log.v(TAG, "Service create new Batch " + newBatch.mId + " for INBOUND info "
+ info.mId);
}
mServerTransfer = new BluetoothOppTransfer(this, newBatch, mServerSession);
}
if (info.mDirection == BluetoothShare.DIRECTION_OUTBOUND && mTransfer != null) {
if (V) {
Log.v(TAG, "Service start transfer new Batch " + newBatch.mId + " for info "
+ info.mId);
}
mTransfer.start(); ---> 主要看这个,处理连接对端设备,然后发送等操作
} else if (info.mDirection == BluetoothShare.DIRECTION_INBOUND
&& mServerTransfer != null) {
if (V) {
Log.v(TAG, "Service start server transfer new Batch " + newBatch.mId
+ " for info " + info.mId);
}
mServerTransfer.start();
}
} else {
int i = findBatchWithTimeStamp(info.mTimestamp);
if (i != -1) {
if (V) {
Log.v(TAG, "Service add info " + info.mId + " to existing batch " + mBatches
.get(i).mId);
}
mBatches.get(i).addShare(info);
} else {
// There is ongoing batch
BluetoothOppBatch newBatch = new BluetoothOppBatch(this, info);
newBatch.mId = mBatchId;
mBatchId++;
mBatches.add(newBatch);
if (V) {
Log.v(TAG,
"Service add new Batch " + newBatch.mId + " for info " + info.mId);
}
}
}
}
}
mTransfer.start(); 其实就是调用BluetoothOppTransfer里面的strart方法,下篇文章在继续记录吧,这篇到此为止,我们总结下这篇,首先在BluetoothOppLauncherActivity里面会判断是发送单文件还是多文件,然后调用sendFileInfo方法,这个方法里面做了两件事,一是保存发送的信息,二是根据蓝牙是否打开来执行相应的逻辑:
若蓝牙未打开,执行BluetoothOppBtEnableActivity弹框询问是否打开,用户同意就在到BluetoothOppBtEnablingActivity处理一些逻辑。
若蓝牙已经打开,执行BluetoothDevicePicker.ACTION_LAUNCH(android.bluetooth.devicepicker.action.LAUNCH)加载DevicePickerFragment显示扫描到的蓝牙设备供用户选择要发送给哪个
用户选择某个设备后,在BluetoothOppReceiver类里就处理上面用户选择某设备后发送的BluetoothDevicePicker.ACTION_DEVICE_SELECTED广播事件,主要就是调用BluetoothOppManager里面的 mContext.getContentResolver().insert(BluetoothShare.CONTENT_URI, values);往数据库里面写入,根据Android数据库的知识,我们就知道实际上是操作BluetoothOppProvider.java#insert方法,当数据库uri发生变化是,就会回调相应的ContentObserver,这里是BluetoothShareContentObserver,然后执行onChange,然后起一个UpdateThread查询并insertShare。
那么我们根据几个关键点的log就可以验证上面的这段总结:
// 发送的是图片
BluetoothOppLauncherActivity: Get ACTION_SEND intent: Uri = content://0@media/external/images/media/1125; mimetype = image/jpeg
// saveSendingFileInfo 对应的log
BluetoothOppSendFileInfo: generateFileInfo ++ info.mFilePath = /storage/emulated/0/DCIM/Camera/IMG_20220810_153600.jpg
BluetoothOppUtility: generateUri: content://0@media/external/images/media/1125@897e0f7
BluetoothOppUtility: putSendFileInfo: uri= content://0@media/external/images/media/1125@897e0f7, sendFileInfo= com.android.bluetooth.opp.BluetoothOppSendFileInfo@897e0f7, path= /storage/emulated/0/DCIM/Camera/IMG_20220810_153600.jpg
BluetoothOppUtility: putSendFileInfo: uri=content://0@media/external/images/media/1125@897e0f7, is a new uri, create ArrayList
BluetoothOppManager: Application data stored to SharedPreference!
// 处理发送的设备广播信息
BluetoothOppReceiver: action :android.bluetooth.devicepicker.action.DEVICE_SELECTED
// 数据库操作成功后回调相应的 ContentObserver,这里是BluetoothShareContentObserver
BtOppService: ContentObserver received notification
Ok,先到此为止,下篇继续……