场景:大家应该都使用过Android手机,通过蓝牙发送文件到另一个手机的场景。简单说Android分享文件使用的就是OPP协议。
所以接下来,就开始分析下主动分享文件流程。
主要分为两部分,第一部分是从Framework层的Bluetooth分析调用流程,第二部分重点表述下文件传输流程。
Bluetooth流程
从实际的操作来看,分享它经历的过程主要为,首先选择蓝牙分享,第二如果蓝牙是打开的状态,会跳转到蓝牙界面,选择一个设备进行分享。
所以本文章,开始的流程就是从点击了“bluetooth”选项之后发生的流程。
一、分享入口
但我们点击蓝牙分享后,会进入到Bluetooth模块的Bluetooth\app\src\main\java\com\android\bluetooth\opp\BluetoothOppLauncherActivity.java
它是一个没有UI的Activity,相当一个中转处理站,一言不合就看下源码:
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
Intent intent = getIntent();
String action = intent.getAction();
.....
// 1、分别表示的是单文件的分享和多文件的分享
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;
}
// 通过isReadyForFileSharing标识,来标志之前分享正在处理中
if (!BluetoothOppManager.isReadyForFileSharing) {
Log.i(TAG, " File share already in process, retrun with out any action ");
finish();
return;
}
/*
* SECURITY_EXCEPTION Google Photo grant-uri-permission
*/
BTOppUtils.grantPermissionToUri(getApplicationContext(),
intent.getClipData());
/*
* Other application is trying to share a file via Bluetooth,
* probably Pictures, videos, or vCards. The Intent should contain
* an EXTRA_STREAM with the data to attach.
*/
if (action.equals(Intent.ACTION_SEND)) {
// TODO: handle type == null case
final String type = intent.getType();
final Uri stream = (Uri) intent.getParcelableExtra(Intent.EXTRA_STREAM);
CharSequence extraText = intent.getCharSequenceExtra(Intent.EXTRA_TEXT);
if (stream != null && type != null) {
if (V) {
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;
} else if (extraText != null && type != null) {
if (V) {
Log.v(TAG,
"Get ACTION_SEND intent with Extra_text = " + extraText.toString()
+ "; mimetype = " + type);
}
final Uri fileUri = creatFileForSharedContent(
this.createCredentialProtectedStorageContext(), extraText);
if (fileUri != null) {
Thread t = new Thread(new Runnable() {
@Override
public void run() {
sendFileInfo(type, fileUri.toString(), false /* isHandover */,
false /* fromExternal */);
}
});
t.start();
return;
} else {
Log.w(TAG, "Error trying to do set text...File not created!");
finish();
return;
}
} else {
Log.e(TAG, "type is null; or sending file URI is null");
finish();
return;
}
} else if (action.equals(Intent.ACTION_SEND_MULTIPLE)) {
// 多文件分享逻辑省略
.......
return;
} else {
Log.e(TAG, "type is null; or sending files URIs are null");
finish();
return;
}
}
} else if (action.equals(Constants.ACTION_OPEN)) {
Uri uri = getIntent().getData();
if (V) {
Log.v(TAG, "Get ACTION_OPEN intent: Uri = " + uri);
}
Intent intent1 = new Intent();
intent1.setAction(action);
intent1.setClassName(Constants.THIS_PACKAGE_NAME, BluetoothOppReceiver.class.getName());
intent1.setDataAndNormalize(uri);
this.sendBroadcast(intent1);
finish();
} else {
Log.w(TAG, "Unsupported action: " + action);
finish();
}
}
上面的代码,省略了一些不重要的逻辑。下面看下分享的逻辑。
- 1、第一步是判断是不是分享,它包括单文件和多文件
因为流程都差不多,所以下面只已单文件分享为例。
- 2、isBluetoothAllowed() 检查是否允许蓝牙传输,比如在飞行模式下是否允许等,这是第一步判断
private boolean isBluetoothAllowed() {
final ContentResolver resolver = this.getContentResolver();
// Check if airplane mode is on
// 1、如果飞行模式关闭,直接返回TRUE
final boolean isAirplaneModeOn =
Settings.System.getInt(resolver, Settings.Global.AIRPLANE_MODE_ON, 0) == 1;
if (!isAirplaneModeOn) {
return true;
}
// Check if airplane mode matters
// 2、如果飞行模式打开,但飞行模式不重要。返回TRUE
final String airplaneModeRadios =
Settings.System.getString(resolver, Settings.Global.AIRPLANE_MODE_RADIOS);
final boolean isAirplaneSensitive =
airplaneModeRadios == null || airplaneModeRadios.contains(
Settings.Global.RADIO_BLUETOOTH);
if (!isAirplaneSensitive) {
return true;
}
// Check if Bluetooth may be enabled in airplane mode
// 3、如果飞行模式打开,但允许蓝牙操作。返回TRUE
final String airplaneModeToggleableRadios = Settings.System.getString(resolver,
Settings.Global.AIRPLANE_MODE_TOGGLEABLE_RADIOS);
final boolean isAirplaneToggleable =
airplaneModeToggleableRadios != null && airplaneModeToggleableRadios.contains(
Settings.Global.RADIO_BLUETOOTH);
if (isAirplaneToggleable) {
return true;
}
// If we get here we're not allowed to use Bluetooth right now
return false;
}
- 3、单个文件下面分为了两类:
- ①、stream != null && type != null : 文件类型
- ②、extraText != null && type != null : 文本类型
从这可以看到,蓝牙传输可以是文件类型或者文本类型,对于传输来说,它都只是一个二进制。
- 4、至此,我们可以看到,所有的前期判断准备都已做好了。最后就会走到真实的处理逻辑 sendFileInfo(type, stream.toString(), false /* isHandover /, true / fromExternal */);
二、打开蓝牙
前面流程都是一些准备工作,sendFileInfo才算是要准备要发送了。
但这里也不能是真实的发送。因为有一个问题:蓝牙有没有打开?,如果蓝牙都没有打开,那是不可能进行下一步处理的。
private void sendFileInfo(String mimeType, String uriString, boolean isHandover,
boolean fromExternal) {
BluetoothOppManager manager = BluetoothOppManager.getInstance(getApplicationContext());
try {
// 首先改变标识,这样其它的分享暂时就进不来了
BluetoothOppManager.isReadyForFileSharing = false;
// 再将当前的任务保存到文件中,这样当蓝牙服务重新启动的时候,可以恢复
manager.saveSendingFileInfo(mimeType, uriString, isHandover, fromExternal);
// 核心逻辑:如果尚未打开蓝牙,则打开蓝牙,如果蓝牙打开,则启动设备选择器
launchDevicePicker();
finish();
} catch (IllegalArgumentException exception) {
Log.e(TAG, "sendFileInfo :" + exception.getMessage());
finish();
BluetoothOppManager.isReadyForFileSharing = true;
}
}
这里只需要看下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!! ");
}
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!! ");
}
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());
if (V) {
Log.d(TAG, "Launching " + BluetoothDevicePicker.ACTION_LAUNCH);
}
startActivity(in1);
}
BluetoothOppManager.isReadyForFileSharing = true;
}
到了这里,又分成了两个流程:
- 蓝牙未打开时候,跳转到BluetoothOppBtEnableActivity界面打开蓝牙
- 蓝牙打开了,跳转到蓝牙设备选择界面去处理发送逻辑。
然后再将isReadyForFileSharing变量制成TRUE,这样在蓝牙进程的流程已经走完了。
(一)、BluetoothOppBtEnableActivity界面打开蓝牙
一图胜千言,这个就是BluetoothOppBtEnableActivity的界面,提供了两个按钮,这里只需要关系“TURN ON”键。
看下点击事件对应逻辑:
@Override
public void onClick(DialogInterface dialog, int which) {
switch (which) {
case DialogInterface.BUTTON_POSITIVE:
// 打开蓝牙开关
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();
break;
case DialogInterface.BUTTON_NEGATIVE:
finish();
break;
}
}
分成了两部分:
- mOppManager.enableBluetooth(); 打开蓝牙
/**
* Enable Bluetooth hardware.
*/
public void enableBluetooth() {
if (mAdapter != null) {
mAdapter.enable();
}
}
- 跳转到BluetoothOppBtEnablingActivity等待蓝牙打开,再做处理
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
// If BT is already enabled jus return.
BluetoothAdapter adapter = BluetoothAdapter.getDefaultAdapter();
if (adapter.isEnabled()) {
finish();
return;
}
IntentFilter filter = new IntentFilter(BluetoothAdapter.ACTION_STATE_CHANGED);
registerReceiver(mBluetoothReceiver, filter);
mRegistered = true;
// Set up the "dialog"
final AlertController.AlertParams p = mAlertParams;
p.mTitle = getString(R.string.enabling_progress_title);
p.mView = createView();
setupAlert();
// Add timeout for enabling progress
mTimeoutHandler.sendMessageDelayed(mTimeoutHandler.obtainMessage(BT_ENABLING_TIMEOUT),
BT_ENABLING_TIMEOUT_VALUE);
}
private final BroadcastReceiver mBluetoothReceiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
String action = intent.getAction();
if (V) {
Log.v(TAG, "Received intent: " + action);
}
if (action.equals(BluetoothAdapter.ACTION_STATE_CHANGED)) {
switch (intent.getIntExtra(BluetoothAdapter.EXTRA_STATE, BluetoothAdapter.ERROR)) {
case BluetoothAdapter.STATE_ON:
mTimeoutHandler.removeMessages(BT_ENABLING_TIMEOUT);
finish();
break;
default:
break;
}
}
}
};
这里只分析下主要流程:
- 如果蓝牙打开了,直接finish
- 注册一个监听蓝牙打开的广播,如果蓝牙打开了,finish界面
- 显示progress UI
- 发送一个20S的超时事件,如果超时也取消
看下界面:
猜想下:上面进入的时候,如果蓝牙是打开的,则会直接进入到BluetoothDevicePicker.ACTION_LAUNCH蓝牙设备选择界面。那么没打开,进入到BluetoothOppBtEnableActivity界面打开蓝牙后,是不是也是进入到BluetoothDevicePicker.ACTION_LAUNCH蓝牙设备选择界面去传输数据了,如果是跳入的入口在哪了?
BluetoothOppService中,会注册一个状态
private final BroadcastReceiver mBluetoothReceiver = new BroadcastReceiver() {
@Override
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(BluetoothAdapter.ACTION_STATE_CHANGED)) {
switch (intent.getIntExtra(BluetoothAdapter.EXTRA_STATE, BluetoothAdapter.ERROR)) {
case BluetoothAdapter.STATE_ON:
if (V) {
Log.v(TAG, "Bluetooth state changed: STATE_ON");
}
startListener();
// If this is within a sending process, continue the handle
// logic to display device picker dialog.
synchronized (this) {
// 判断是否有发送的任务,没有则不启动
if (BluetoothOppManager.getInstance(context).mSendingFlag) {
// reset the flags
BluetoothOppManager.getInstance(context).mSendingFlag = false;
Intent in1 = new Intent(BluetoothDevicePicker.ACTION_LAUNCH);
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());
in1.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
context.startActivity(in1);
}
}
break;
case BluetoothAdapter.STATE_TURNING_OFF:
if (V) {
Log.v(TAG, "Bluetooth state changed: STATE_TURNING_OFF");
}
mHandler.sendMessage(mHandler.obtainMessage(STOP_LISTENER));
break;
}
} else {
BTOppUtils.checkAction(intent);
}
}
};
service启动的时候,会注册广播监听蓝牙状态,蓝牙打开的时候,也会进入到蓝牙选择界面。
三、文件传输
(一)、选择目标传输Device
蓝牙打开后,此刻肯定是需要确定一个设备来充当目标端,这样我们可以创建连接了。
BLuetooth进程通过隐式启动framework/base/core/java/android/bluetooth/BluetoothDevicePicker.java,BluetoothDevicePicker其实是Setting中的界面。
看下package/apps/Settings/AndroidManifest.xml Setting的清单文件
<activity android:name=".bluetooth.DevicePickerActivity"
android:label="@string/device_picker"
android:configChanges="orientation|keyboardHidden|screenSize"
android:clearTaskOnLaunch="true">
<intent-filter>
<action android:name="android.bluetooth.devicepicker.action.LAUNCH" />
<category android:name="android.intent.category.DEFAULT" />
</intent-filter>
</activity>
其实DevicePickerActivity只是一个载体,真实的处理是放在DevicePickerFragment中。DevicePickerFragment是显示扫描到的蓝牙。
@Override
void onDevicePreferenceClick(BluetoothDevicePreference btPreference) {
disableScanning();
LocalBluetoothPreferences.persistSelectedDeviceInPicker(
getActivity(), mSelectedDevice.getAddress());
if ((btPreference.getCachedDevice().getBondState() ==
BluetoothDevice.BOND_BONDED) || !mNeedAuth) {
sendDevicePickedIntent(mSelectedDevice);
finish();
} else {
super.onDevicePreferenceClick(btPreference);
}
}
private void sendDevicePickedIntent(BluetoothDevice device) {
android.util.Log.d("Devicepicker", "sendDevicePickedIntent");
Intent intent = new Intent(BluetoothDevicePicker.ACTION_DEVICE_SELECTED);
intent.putExtra(BluetoothDevice.EXTRA_DEVICE, device);
if (mLaunchPackage != null && mLaunchClass != null) {
intent.setClassName(mLaunchPackage, mLaunchClass);
}
intent.addFlags(Intent.FLAG_RECEIVER_FOREGROUND);
getActivity().sendBroadcast(intent);
}
当选中一个设备后,会执行onDevicePreferenceClick方法,因为mNeedAuth=FALSE,所以会走到sendDevicePickedIntent方法体。
sendDevicePickedIntent可以看到是发送了BluetoothDevicePicker.ACTION_DEVICE_SELECTED广播出去。看来Setting中也不是一个处理者,而是确定远端的一个中间体。
(二)、将Session插入的DB中
再回到Bluetooth应用中看下BluetoothOppReceiver接受端
public class BluetoothOppReceiver extends BroadcastReceiver {
private static final String TAG = "BluetoothOppReceiver";
private static final boolean D = Constants.DEBUG;
private static final boolean V = Constants.VERBOSE;
@Override
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);
BluetoothOppManager.isReadyForFileSharing = false;
BluetoothDevice remoteDevice = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);
if (D) {
Log.d(TAG, "Received BT device selected intent, bt device: " + remoteDevice);
}
if (remoteDevice == null) {
mOppManager.cleanUpSendingFileInfo();
BluetoothOppManager.isReadyForFileSharing = true;
return;
}
// Insert transfer session record to database
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);
}
BluetoothOppManager.isReadyForFileSharing = true;
Toast.makeText(context, toastMsg, Toast.LENGTH_SHORT).show();
}
从上面流程可以看出,主要就调用mOppManager.startTransfer(remoteDevice),传入Setting中传入的remoteDevice;并Toast相关Tip。
再看下startTransfer方法,它主要是将传输会话记录插入数据库,之后它的任务就完成了。
public void startTransfer方法,(BluetoothDevice device) {
if (V) {
Log.v(TAG, "Active InsertShareThread number is : " + mInsertShareThreadNum);
}
InsertShareInfoThread insertThread;
synchronized (BluetoothOppManager.this) {
if (mInsertShareThreadNum > ALLOWED_INSERT_SHARE_THREAD_NUMBER) {
Log.e(TAG, "Too many shares user triggered concurrently!");
// Notice user
Intent in = new Intent(mContext, BluetoothOppBtErrorActivity.class);
in.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
in.putExtra("title", mContext.getString(R.string.enabling_progress_title));
in.putExtra("content", mContext.getString(R.string.ErrorTooManyRequests));
mContext.startActivity(in);
return;
}
insertThread = new InsertShareInfoThread(device, mMultipleFlag, mMimeTypeOfSendingFile,
mUriOfSendingFile, mMimeTypeOfSendingFiles, mUrisOfSendingFiles,
mIsHandoverInitiated);
if (mMultipleFlag) {
mFileNumInBatch = mUrisOfSendingFiles.size();
}
}
insertThread.start();
}
因为操作DB是一个耗时的工作,所以是要开启一个线程去处理。然后是要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);
if (V) {
Log.v(TAG, "Insert contentUri: " + contentUri + " to device: " + getDeviceName(
mRemoteDevice));
}
}
这里组装成ContentValues对象,然后再通过ContentProvider来插入到数据库中。到此还处在消息的生产端,那么消息什么时候会被消费了?
ContentProvider的实现类为BluetoothOppProvider,所以看下BluetoothOppProvider中的insert方法
@Override
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);
}
- 1、通过SQLiteDatabase插入的DB_TABLE表中
- 2、然后再通知notifyChange监听了BluetoothShare.CONTENT_URI数据有变化
(三)、Session的消费
在BluetoothOppService中被创建的时候,会注册BluetoothShareContentObserver,以监听BluetoothShare.CONTENT_URI,当有变化的时候,在mObserver处理
mObserver = new BluetoothShareContentObserver();
getContentResolver().registerContentObserver(BluetoothShare.CONTENT_URI,
true, mObserver);
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;
}
}
}
监听数据库有变化之后,会在调用UpdateThread开启个线程来处理新插入的数据。在看下UpdateThread的处理逻辑。
UpdateThread中的run方法,核心的就是insertShare
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);
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
*/
if (info.isReadyToStart()) {
....
if (mBatches.size() == 0) {
BluetoothOppBatch newBatch = new BluetoothOppBatch(this, info);
newBatch.mId = mBatchId;
mBatchId++;
mBatches.add(newBatch);
// 发送文件
if (info.mDirection == BluetoothShare.DIRECTION_OUTBOUND) {
mTransfer = new BluetoothOppTransfer(this, newBatch);
} else if (info.mDirection == BluetoothShare.DIRECTION_INBOUND) { // 接收文件
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);
}
}
}
}
}
调用start方法,创建BlueSocket连接,用于传输文件。
public void start() {
/* check Bluetooth enable status */
/*
* normally it's impossible to reach here if BT is disabled. Just check
* for safety
*/
if (!mAdapter.isEnabled()) {
Log.e(TAG, "Can't start transfer when Bluetooth is disabled for " + mBatch.mId);
markBatchFailed(BluetoothShare.STATUS_UNKNOWN_ERROR);
mBatch.mStatus = Constants.BATCH_STATUS_FAILED;
return;
}
if (mHandlerThread == null) {
if (V) {
Log.v(TAG, "Create handler thread for batch " + mBatch.mId);
}
mHandlerThread =
new HandlerThread("BtOpp Transfer Handler", Process.THREAD_PRIORITY_BACKGROUND);
mHandlerThread.start();
mSessionHandler = new EventHandler(mHandlerThread.getLooper());
if (mBatch.mDirection == BluetoothShare.DIRECTION_OUTBOUND) {
/* for outbound transfer, we do connect first */
startConnectSession();
} else if (mBatch.mDirection == BluetoothShare.DIRECTION_INBOUND) {
/*
* for inbound transfer, it's already connected, so we start
* OBEX session directly
*/
startObexSession();
}
}
// 监听连接的状态变化
registerConnectionreceiver();
}
因为第一次mHandlerThread = null,以及mBatch.mDirection = BluetoothShare.DIRECTION_OUTBOUND,所以会走到startConnectSession中,即创建一个连接
然后registerConnectionreceiver来监听连接状态,是不是连接成功了,连接成功了就可以传输操作了。
再看下startConnectSession方法
private void startConnectSession() {
mDevice = mBatch.mDestination;
// 主要的方法是查询刚传入的设备
if (!mBatch.mDestination.sdpSearch(BluetoothUuid.ObexObjectPush)) {
if (D) {
Log.d(TAG, "SDP failed, start rfcomm connect directly");
}
/* update bd address as sdp could not be started */
mDevice = null;
/* SDP failed, start rfcomm connect directly */
mConnectThread = new SocketConnectThread(mBatch.mDestination, false, false, -1);
mConnectThread.start();
}
}
sdpSearch的逻辑,从字段理解就是通过sdp协议查询设备。它的流程就是通过Binder进入到AdapterService服务中开启查询,但还是委托给SdpManager类做处理。
下面列出的是AdapterService的逻辑,可见返回的是true,所以上列逻辑不会继续执行了。
boolean sdpSearch(BluetoothDevice device, ParcelUuid uuid) {
enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
if (mSdpManager != null) {
mSdpManager.sdpSearch(device, uuid);
return true;
} else {
return false;
}
}
再看下SdpManager中的逻辑 mSdpManager.sdpSearch(device, uuid);
/* Caller must hold the mTrackerLock */
// 开始寻找
private void startSearch() {
SdpSearchInstance inst = sSdpSearchTracker.getNext();
if ((inst != null) && (!sSearchInProgress)) {
if (D) {
Log.d(TAG, "Starting search for UUID: " + inst.getUuid());
}
sSearchInProgress = true;
inst.startSearch(); // Trigger timeout message
// 进入到JNI方法中
sdpSearchNative(Utils.getBytesFromAddress(inst.getDevice().getAddress()),
Utils.uuidToByteArray(inst.getUuid()));
} else { // Else queue is empty.
if (D) {
Log.d(TAG, "startSearch(): nextInst = " + inst + " mSearchInProgress = "
+ sSearchInProgress + " - search busy or queue empty.");
}
}
}
// 如果有查询到,则C层会调用该java方法,已通知有找到
void sdpOppOpsRecordFoundCallback(int status, byte[] address, byte[] uuid, int l2capPsm,
int rfcommCannelNumber, int profileVersion, String serviceName, byte[] formatsList,
boolean moreResults) {
synchronized (TRACKER_LOCK) {
SdpSearchInstance inst = sSdpSearchTracker.getSearchInstance(address, uuid);
SdpOppOpsRecord sdpRecord = null;
if (inst == null) {
Log.e(TAG, "sdpOppOpsRecordFoundCallback: Search instance is NULL");
return;
}
inst.setStatus(status);
if (status == AbstractionLayer.BT_STATUS_SUCCESS) {
sdpRecord = new SdpOppOpsRecord(serviceName, rfcommCannelNumber, l2capPsm,
profileVersion, formatsList);
}
if (D) {
Log.d(TAG, "UUID: " + Arrays.toString(uuid));
}
if (D) {
Log.d(TAG, "UUID in parcel: " + ((Utils.byteArrayToUuid(uuid))[0]).toString());
}
// 这里面会发送广播给到我们的之前注册的地方
sendSdpIntent(inst, sdpRecord, moreResults);
}
}
SdpManager中会做两件事,第一件事是开启一个查询超时Message,如果超时则会发送FALSE的状态。如果找到了则会通过广播将数据发送出去。
所以到这里,再回到之前注册的广播这里。这里只截取一些核心的逻辑:
if (uuid.equals(BluetoothUuid.ObexObjectPush)) {
int status = intent.getIntExtra(BluetoothDevice.EXTRA_SDP_SEARCH_STATUS, -1);
Log.d(TAG, " -> status: " + status);
BluetoothDevice device =
intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);
if (mDevice == null) {
Log.w(TAG, "OPP SDP search, target device is null, ignoring result");
return;
}
if (!device.getAddress().equalsIgnoreCase(mDevice.getAddress())) {
Log.w(TAG, " OPP SDP search for wrong device, ignoring!!");
return;
}
SdpOppOpsRecord record =
intent.getParcelableExtra(BluetoothDevice.EXTRA_SDP_RECORD);
if (record == null) {
Log.w(TAG, " Invalid SDP , ignoring !!");
markConnectionFailed(null);
return;
}
// 启动一个线程去连接
mConnectThread =
new SocketConnectThread(mDevice, false, true, record.getL2capPsm());
mConnectThread.start();
mDevice = null;
}
我们可以看到将SdpManager中获取的数据传入,并开启一个线程来连接。所以再看下SocketConnectThread再做了什么,只截取主要代码
/* Use BluetoothSocket to connect */
// SocketConnectThread中的run方法中
try {
if (mIsInterrupted) {
Log.e(TAG, "btSocket connect interrupted ");
markConnectionFailed(mBtSocket);
return;
} else {
// 1、创建一个socket
mBtSocket = mDevice.createInsecureL2capSocket(mL2cChannel);
}
} catch (IOException e1) {
Log.e(TAG, "L2cap socket create error", e1);
connectRfcommSocket();
return;
}
try {
// 2、开始连接
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());
}
// 3、通过Handler发送一个消息,来开始发送
mSessionHandler.obtainMessage(TRANSPORT_CONNECTED, transport).sendToTarget();
} catch (IOException e) {
Log.e(TAG, "L2cap socket connect exception", e);
try {
mBtSocket.close();
} catch (IOException e3) {
Log.e(TAG, "Bluetooth socket close error ", e3);
}
connectRfcommSocket();
return;
}
- 1、创建一个socket, mDevice.createInsecureL2capSocket(mL2cChannel);
- 2、开始连接 mBtSocket.connect();
- 3、通过Handler发送一个消息,来开始发送
连接的Socket分为两种,一种是Rfcomm,一种是L2CAP。这里使用的是L2CAP,如果SdpManager不存在,则是Rfcomm方式。
(四)、发送数据
接着上面流程,BluetoothOppTransfer中通过startObexSession来处理创建的socket。
private void startObexSession() {
mBatch.mStatus = Constants.BATCH_STATUS_RUNNING;
......
if (mBatch.mDirection == BluetoothShare.DIRECTION_OUTBOUND) {
if (V) {
Log.v(TAG, "Create Client session with transport " + mTransport.toString());
}
mSession = new BluetoothOppObexClientSession(mContext, mTransport);
} else if (mBatch.mDirection == BluetoothShare.DIRECTION_INBOUND) {
......
}
mSession.start(mSessionHandler, mBatch.getNumShares());
processCurrentShare();
}
因为是作为发送端,所以会走到BluetoothOppObexClientSession中的start方法
@Override
public void start(Handler handler, int numShares) {
if (D) {
Log.d(TAG, "Start!");
}
mCallback = handler;
mThread = new ClientThread(mContext, mTransport, numShares);
mThread.start();
}
// BluetoothOppObexClientSession中的run方法
@Override
public void run() {
.....
if (!mInterrupted) {
// 发起obex连接
connect(mNumShares);
}
mNumFilesAttemptedToSend = 0;
while (!mInterrupted) {
if (!mWaitingForShare) {
// 开始发送
doSend();
} else {
try {
if (D) {
Log.d(TAG, "Client thread waiting for next share, sleep for "
+ SLEEP_TIME);
}
Thread.sleep(SLEEP_TIME);
} catch (InterruptedException e) {
}
}
}
disconnect();
if (mWakeLock.isHeld()) {
if (V) {
Log.v(TAG, "release partial WakeLock");
}
mWakeLock.release();
}
if (mNumFilesAttemptedToSend > 0) {
// Log outgoing OPP transfer if more than one file is accepted by remote
MetricsLogger.logProfileConnectionEvent(BluetoothMetricsProto.ProfileId.OPP);
}
Message msg = Message.obtain(mCallback);
msg.what = BluetoothOppObexSession.MSG_SESSION_COMPLETE;
msg.obj = mInfo;
msg.sendToTarget();
}
再看下doSend中的逻辑,主要是检查文件等工作,在sendFile做真实的处理。
private int sendFile(BluetoothOppSendFileInfo fileInfo) {
boolean error = false;
int responseCode = -1;
long position = 0;
int status = BluetoothShare.STATUS_SUCCESS;
Uri contentUri = Uri.parse(BluetoothShare.CONTENT_URI + "/" + mInfo.mId);
ContentValues updateValues;
HeaderSet request = new HeaderSet();
ClientOperation putOperation = null;
OutputStream outputStream = null;
InputStream inputStream = null;
try {
synchronized (this) {
mWaitingForRemote = true;
}
try {
if (V) {
Log.v(TAG, "Set header items for " + fileInfo.mFileName);
}
// 1、发送文件name、type等文件信息给远端
request.setHeader(HeaderSet.NAME, fileInfo.mFileName);
request.setHeader(HeaderSet.TYPE, fileInfo.mMimetype);
applyRemoteDeviceQuirks(request, mInfo.mDestination, fileInfo.mFileName);
Constants.updateShareStatus(mContext1, mInfo.mId,
BluetoothShare.STATUS_RUNNING);
request.setHeader(HeaderSet.LENGTH, fileInfo.mLength);
if (V) {
Log.v(TAG, "put headerset for " + fileInfo.mFileName);
}
putOperation = (ClientOperation) mCs.put(request);
} catch (IllegalArgumentException e) {
.......
error = true;
}
synchronized (this) {
mWaitingForRemote = false;
}
.......
if (!error) {
int readLength = 0;
long percent = 0;
long prevPercent = 0;
boolean okToProceed = false;
long timestamp = 0;
long currentTime = 0;
long prevTimestamp = SystemClock.elapsedRealtime();
int outputBufferSize = putOperation.getMaxPacketSize();
byte[] buffer = new byte[outputBufferSize];
BufferedInputStream a = new BufferedInputStream(fileInfo.mInputStream, 0x4000);
if (!mInterrupted && (position != fileInfo.mLength)) {
readLength = readFully(a, buffer, outputBufferSize);
mCallback.sendMessageDelayed(mCallback.obtainMessage(
BluetoothOppObexSession.MSG_CONNECT_TIMEOUT),
BluetoothOppObexSession.SESSION_TIMEOUT);
synchronized (this) {
mWaitingForRemote = true;
}
// first packet will block here
// 2、发送第一个包给对方,
outputStream.write(buffer, 0, readLength);
position += readLength;
if (position == fileInfo.mLength) {
// if file length is smaller than buffer size, only one packet
// so block point is here
outputStream.close();
outputStream = null;
}
/* check remote accept or reject */
// 3、看对方是否接收,如果接收才能有接下来的数据发送
responseCode = putOperation.getResponseCode();
mCallback.removeMessages(BluetoothOppObexSession.MSG_CONNECT_TIMEOUT);
synchronized (this) {
mWaitingForRemote = false;
}
if (responseCode == ResponseCodes.OBEX_HTTP_CONTINUE
|| responseCode == ResponseCodes.OBEX_HTTP_OK) {
if (D) {
Log.v(TAG, "Remote accept");
}
okToProceed = true;
updateValues = new ContentValues();
updateValues.put(BluetoothShare.CURRENT_BYTES, position);
mContext1.getContentResolver()
.update(contentUri, updateValues, null, null);
mNumFilesAttemptedToSend++;
} else {
Log.i(TAG, "Remote reject, Response code is " + responseCode);
}
}
long beginTime = System.currentTimeMillis();
/// 4、循环的发送数据,直到完成
while (!mInterrupted && okToProceed && (position < fileInfo.mLength)) {
if (V) {
timestamp = SystemClock.elapsedRealtime();
}
readLength = a.read(buffer, 0, outputBufferSize);
outputStream.write(buffer, 0, readLength);
/* check remote abort */
responseCode = putOperation.getResponseCode();
if (V) {
Log.v(TAG, "Response code is " + responseCode);
}
if (responseCode != ResponseCodes.OBEX_HTTP_CONTINUE
&& responseCode != ResponseCodes.OBEX_HTTP_OK) {
/* abort happens */
// 每次包发送完成都要检查传输是否是终止了
okToProceed = false;
} else {
position += readLength;
currentTime = SystemClock.elapsedRealtime();
if (V) {
Log.v(TAG, "Sending file position = " + position
+ " readLength " + readLength + " bytes took "
+ (currentTime - timestamp) + " ms");
}
// Update the Progress Bar only if there is change in percentage
// or once per a period to notify NFC of this transfer is still alive
percent = position * 100 / fileInfo.mLength;
if (percent > prevPercent
|| currentTime - prevTimestamp > Constants.NFC_ALIVE_CHECK_MS) {
updateValues = new ContentValues();
updateValues.put(BluetoothShare.CURRENT_BYTES, position);
mContext1.getContentResolver()
.update(contentUri, updateValues, null, null);
prevPercent = percent;
prevTimestamp = currentTime;
}
}
}
if (responseCode == ResponseCodes.OBEX_HTTP_FORBIDDEN
|| responseCode == ResponseCodes.OBEX_HTTP_NOT_ACCEPTABLE) {
Log.i(TAG, "Remote reject file " + fileInfo.mFileName + " length "
+ fileInfo.mLength);
status = BluetoothShare.STATUS_FORBIDDEN;
} else if (responseCode == ResponseCodes.OBEX_HTTP_UNSUPPORTED_TYPE) {
Log.i(TAG, "Remote reject file type " + fileInfo.mMimetype);
status = BluetoothShare.STATUS_NOT_ACCEPTABLE;
} else if (!mInterrupted && position == fileInfo.mLength) {
Log.i(TAG,
"SendFile finished send out file " + fileInfo.mFileName + " length "
+ fileInfo.mLength);
BTOppUtils.throughputInKbps(fileInfo.mLength, beginTime);
} else {
error = true;
status = BluetoothShare.STATUS_CANCELED;
putOperation.abort();
/* interrupted */
Log.i(TAG, "SendFile interrupted when send out file " + fileInfo.mFileName
+ " at " + position + " of " + fileInfo.mLength);
}
}
} catch (IOException e) {
handleSendException(e.toString());
} catch (NullPointerException e) {
handleSendException(e.toString());
} catch (IndexOutOfBoundsException e) {
handleSendException(e.toString());
} finally {
.........
}
BluetoothOppUtility.cancelNotification(mContext);
return status;
}
- 1、发送文件name、type等文件信息给远端,供对方显示
- 2、发送第一个包给对方,
- 3、看对方是否接收第一个包,如果接收才能有接下来的数据发送
- 4、循环的发送数据,直到完成或被终止