蓝牙这块儿算是系统中的一个大块儿,刚开始分析确实很容易没有头绪,所以在进入庞大的源码之前先确定一个分析顺序,也好避免越学越乱。
对于源码的分析不外乎whw(what—how—why)
对于蓝牙各协议的功能以及如何演示都已经分析完了,具体可以参考
带你解锁蓝牙skill(一)以及带你解锁蓝牙skill(二)。
本文以Android7.0为例进行源码分析。开始分析源码之前,先来看看蓝牙相关的都有什么东西
在对一个新事物进行研究之前,我们已经了解了他是什么,那么接下来就是庖丁解牛了。但是目前还做不到目无全牛哈哈。 蓝牙代码实现不外乎包括以下三个方面
在学之前我们也先要明确目的是什么,即学完蓝牙后我们想要掌握什么样的技能?? 大致如下:
在确定了研究思路和研究目的之后,我们就可以开始对源码的研究了。
按照第四部分确定的大致方向来进行接下来的研究。不论是蓝牙开关默认值还是协议的开关的值,对这些值还好说,三下五除二就分析好了,但是蓝牙界面仍旧有一个大工程在。从驱动往应用层方向,蓝牙相关的代码位置如下
可以看出Bluetooth应用中的代码是按照各协议模块进行区分目录的,但是在各协议模块中并不包含对profile的具体定义,以A2DP为例
在packages/apps/Bluetooth/src/com/anddroid/bluetooth/a2dp文件夹中只有两个文件A2dpService和A2dpStateMachine,至于这两个文件是干什么,后续会介绍,暂时先了解一个大致的目录结构
在该目录下就是一些蓝牙profile的相关的配置了。
可以看到很多在开发过程中常见的类:BluetoothAdapter,BluetoothDevice,BluetoothSocket等等,蓝牙的核心代码和接口的具体实现都在这里!!!
对蓝牙的应用层的代码接口有了一个大致了解之后,我们开始进行分析
估计有着急的人会说,看什么界面啊,我就想知道功能是怎么实现的
但我想说,如果没有界面,你如何知道他到底实现了什么功能??
如果没有界面,你该如何下手??
界面大致包括两部分,设置中的蓝牙界面和蓝牙应用中的蓝牙界面
蓝牙协议开关这篇文章中讲述了作为系统开发人员如何禁止掉蓝牙某个协议(包括上层和底层)
对于蓝牙协议我只能是分析常见且我的测试机可以实现的,计划要分析的协议如下
也许后续计划会有所改变,先暂时确定这样。
以蓝牙传输图片为例,opp文件传输包括文件的传入和传出两方面,分别来考虑
我们就沿着分享图片这一条线去分析,在分享蓝牙图片时,选择蓝牙分享,当然如果蓝牙未开启的话会询问先要开启蓝牙。至于系统分享属于另一个系统的功能,在以后的文章中会介绍。
先介绍一个目前用到的,在选择蓝牙分享后,会启动BluetoothOppLauncherActivity,在该类中的launchDevicePicker()启动DevicePickerActivity,方法如下:
/**
如果蓝牙未开启就开启蓝牙,如果蓝牙已开启就启动选择蓝牙设备界面
* Turns on Bluetooth if not already on, or launches device picker if Bluetooth is on
* @return
*/
private final 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 {
//如果蓝牙已开启就启动DevicePickerActiivty,会传入一些参数,这个在以后会用到
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);
}
}
开启蓝牙之后,弹出选择蓝牙设备界面
那么该界面显示的蓝牙设备都包括什么呢?以及点击蓝牙设备后又会去做什么事儿呢?带着这些问题来继续接下来的分析
首先我们要知道该界面所加载的activity的名字,这个可以借助sdk的工具很明显的看出。在这里说明一下,sdk中有很多工具可以方便我们的分析,就在sdk\tools目录下,大家可以自己去尝试。
可以看到该在选择蓝牙分享后弹出的activity的界面为DevicePickerActiviy.java。借助谷歌源码网址AndroidXRef可以快速找到该java文件,进行分析。
该activity的所在目录如下:位于settings应用中(代码来自Android7.0.0_r1分支)
出乎意料。代码简直少的不能再少了
/**
*该activity是蓝牙设备选择时的dialog(这里说是dialog的意思
是该activity的主题是dialog形式的),设备选择的
逻辑实现在BluetoothSettings的fragment中
* Activity for Bluetooth device picker dialog. The device picker logic
* is implemented in the {@link BluetoothSettings} fragment.
*/
public final class DevicePickerActivity extends Activity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.bluetooth_device_picker);
}
}
看到这里你可能也许还会困惑,那这个界面怎么加载出来的?逻辑实现呢?人家说的很清楚了,设备选择的逻辑代码在fragment中,而且还给你指明了和BluetoothSettings相关,也就是说具体的你去BluetoothSettings中找去吧。
但是我们先不着急看BluetoothSettings,先看一下DevicePickerActivity中的代码。该类中就有一个需要分析的,那就是他的布局文件bluetooth_device_picker.xml,该文件内容也是很少
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent">
<fragment
android:id="@+id/bluetooth_device_picker_fragment"
android:name="com.android.settings.bluetooth.DevicePickerFragment"
android:layout_width="match_parent"
android:layout_height="0dip"
android:layout_weight="1" />
</LinearLayout>
看到这里,差不多就明白了,原来该activity是加载了一个fragment:DevicePickerFragment。是不是差一点就错过了呢。好了,接下来就是去分析该fragment了。
每次去分析一个文件时,首先要明白你想从该文件中明白什么?然后在分析结束后再看看你原先的疑问有没有解决,以及你有什么新的疑问。
所以,明确分析目的:
在继续接下来的阅读时我假设你是对settings源码已经有了一定的了解,如果没有建议你先看看我对源码的settings或者蓝牙的一些基础界面的分析。不论是哪个源码版本,大致都是相通的。
话不多说,进入正题,先来看看DevicePickerFragment类
/**
这句不用翻译了吧各位,,BluetoothSettings是在设置应用中蓝牙的配置和连接管理的界面
* BluetoothSettings is the Settings screen for Bluetooth configuration and
* connection management.
*/
public final class DevicePickerFragment extends DeviceListPreferenceFragment {
public DevicePickerFragment() {
super(null /* Not tied to any user restrictions. */);
}
private boolean mNeedAuth;
private String mLaunchPackage;
private String mLaunchClass;
private boolean mStartScanOnResume;
private ListView mListView;//zhaohaiyun add
private TextView mEmptyView;//zhaohaiyun add
@Override
void addPreferencesForActivity() {
addPreferencesFromResource(R.xml.device_picker);
Intent intent = getActivity().getIntent();
mNeedAuth = intent.getBooleanExtra(BluetoothDevicePicker.EXTRA_NEED_AUTH, false);
//调用父类方法设置过滤器,过滤蓝牙设备 setFilter(intent.getIntExtra(BluetoothDevicePicker.EXTRA_FILTER_TYPE,
BluetoothDevicePicker.FILTER_TYPE_ALL));
//mLaunchPackage 的取值为Constants.THIS_PACKAGE_NAME
//即取值为com.android.bluetooth
mLaunchPackage = intent.getStringExtra(BluetoothDevicePicker.EXTRA_LAUNCH_PACKAGE);
//mLaunchClass 取值为BluetoothOppReceiver.class.getName()
//即要启动的class为BluetoothOppReceiver
mLaunchClass = intent.getStringExtra(BluetoothDevicePicker.EXTRA_LAUNCH_CLASS);
}
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
//设置界面的标题
getActivity().setTitle(getString(R.string.device_picker));
UserManager um = (UserManager) getSystemService(Context.USER_SERVICE);
mStartScanOnResume = !um.hasUserRestriction(DISALLOW_CONFIG_BLUETOOTH)
&& (savedInstanceState == null); // don't start scan after rotation,在进行横竖屏切换时数据会保存,不要重新扫描
}
@Override
public void onResume() {
super.onResume();
//添加扫描到的蓝牙设备
addCachedDevices();
if (mStartScanOnResume) {
//如果满足扫描条件,则进行蓝牙扫描
mLocalAdapter.startScanning(true);
mStartScanOnResume = false;
}
}
@Override
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);
}
}
public void onDeviceBondStateChanged(CachedBluetoothDevice cachedDevice,
int bondState) {
if (bondState == BluetoothDevice.BOND_BONDED) {
BluetoothDevice device = cachedDevice.getDevice();
if (device.equals(mSelectedDevice)) {
sendDevicePickedIntent(device);
finish();
}
}
}
@Override
public void onBluetoothStateChanged(int bluetoothState) {
super.onBluetoothStateChanged(bluetoothState);
//如果蓝牙状态发生改变,且目前属于开启状态时也会进行扫描
if (bluetoothState == BluetoothAdapter.STATE_ON) {
mLocalAdapter.startScanning(false);
}
}
private void sendDevicePickedIntent(BluetoothDevice device) {
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);
}
}
以上是BluetoothDevicePicker中的一个代码片段,可以看出filter的类型包括
- FILTER\_TYPE\_ALL:没有什么限制条件,显示所有蓝牙设备
- FILTER\_TYPE\_AUDIO:只显示支持audio协议的蓝牙设备
- FILTER\_TYPE\_TRANSFER:只显示支持文件传输的蓝牙设备
- FILTER\_TYPE\_PANU:只显示支持个人局域网用户即可以使用个人局域网的蓝牙设备
- FILTER\_TYPE\_NAP:只显示支持开启个人局域网的蓝牙设备
所以我们说该界面是加载说有类型的蓝牙设备。
- 说明一下,在蓝牙扫描到设备后会缓存起来,通过addCachedDevices方法,就算不经过扫描也可以获取到曾经缓存起来的蓝牙设备。
- 在开启该activity时,如果满足扫描条件的话,也会进行蓝牙扫描。
- 在蓝牙状态发生改变并且当前蓝牙状态为开启时也会进行扫描
这种情况下,会调用sendDevicePickedIntent确认已经选择成功,并结束当前界面
- 第二种情况,远程设备未与本机设备配对,并且远程设备在接收文件时需要授权的。
在点击时,当前界面不会结束,会先去调用父类的方法进行配对,配对成功后发送广播,触发该类中的onDeviceBondStateChanged方法,在该方法中检测到所配对的设备就是所选择的设备后重复第一种情况的行为
所以总结下就是,在点击选择蓝牙设备时,如果设备已和本机设备完成配对,则finish掉该activity并且调用sendDevicePickerIntent方法。如果设备不需要授权即不需要配对就可以发送文件则也是调用sendDevicePickerIntent。如果设备需要授权且未配对的情况下就会去调用父类的方法进行配对操作,配对成功后依旧是调用sendDevicePickerIntent方法。所以就是在保证设备可以接受文件后就调用sendDevicePickerIntent方法。接下来看该方法的具体实现
private void sendDevicePickedIntent(BluetoothDevice device) {
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);
}
在该方法中会发送一个广播。所发送的广播的action为ACTION_DEVICE_SELECTED,携带的字段有EXTRA_DEVICE。LaunchPackage和launchclass是在创建activity时所携带过来的信息,具体参考DevicePickerActivity代码分析注释。归根结底,该方法就是去启动BlueoothOppReceiver。
好了,现在DevicePickerActivity这个界面和文件都分析完了,接下来要进去下一个界面文件BluetoothOppReceive分析了。
在选择蓝牙设备后发送的广播为BluetoothDevicePicker.ACTION_DEVICE_SELECTED,所以看receiver中个对于该广播的处理
/**
用于处理蓝牙文件传输:包括系统广播,来自其他应用的intents,来自OppService的Intents,来自Opp应用层其他模块的Intents
* Receives and handles: system broadcasts; Intents from other applications;
* Intents from OppService; Intents from modules in Opp application layer.
*/
public class BluetoothOppReceiver extends BroadcastReceiver {
.....
//接受到向其他蓝牙设备发送文件的广播
if (action.equals(BluetoothDevicePicker.ACTION_DEVICE_SELECTED)) {
BluetoothOppManager mOppManager = BluetoothOppManager.getInstance(context);
//获取到远程蓝牙设备信息,即获取到文件接收者
BluetoothDevice remoteDevice = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);
if (V) Log.v(TAG, "Received BT device selected intent, bt device: " + remoteDevice);
// Insert transfer session record to database
//开始传输文件。将要分享的文件插入到db中
mOppManager.startTransfer(remoteDevice);
// Display toast message
String deviceName = remoteDevice.getName();
String toastMsg;
//传输的文件数量
int batchSize = mOppManager.getBatchSize();
if (mOppManager.mMultipleFlag) {
//如果是发送多个文件,获取对应toast信息
toastMsg = context.getString(R.string.bt_toast_5, Integer.toString(batchSize),
deviceName);
} else {
//如果是发送单个文件,获取对应toast信息
toastMsg = context.getString(R.string.bt_toast_4, deviceName);
}
Toast.makeText(context, toastMsg, Toast.LENGTH_SHORT).show();
}
....
}
可以看到BlueoothOppReceiver其实是做了两件事儿
BlueoothOppReceier到这里结束了,紧接着去看mOppManager的startTransfer方法。方法定义在BluetoothOppManager
/**
* Fork a thread to insert share info to db.
*/
public void startTransfer(BluetoothDevice device) {
if (V) Log.v(TAG, "Active InsertShareThread number is : " + mInsertShareThreadNum);
InsertShareInfoThread insertThread;
synchronized (BluetoothOppManager.this) {
//ALLOWED_INSERT_SHARE_THREAD_NUMBER的取值为3,mInsertShareThreadNum 是在每次创建文件传输线程时值会++,在线程结束后值会--文件传输线程数量如果大于3,则报错
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, mNameOfSendingFile, mMimeTypeOfSendingFiles, mUrisOfSendingFiles,
mIsHandoverInitiated);
if (mMultipleFlag) {
//如果是多文件传输,把要传输的文件数量存在mfileNumInBatch字段中
mfileNumInBatch = mUrisOfSendingFiles.size();
}
}
//开启文件传输线程
insertThread.start();
}
可以看到,在startTransfer方法中,首先会去判断文件传输线程是否超过上限(最大值为3),注意,这里所说的不是说文件传输数量,而是文件传输线程,由上述分析可知每当选择一个蓝牙设备进行分享时就会去创建一个文件传输线程。所以这里的上限是说在同一时刻最多可以向3个设备发送文件。
判断之后当然会有两个结果,如果超过了最大值则会报错,并且结束本次传输。如果没有超过文件传输线程所限定的最大值,则会继续创建文件分享线程去分享文件。所以,接下来就是要分析文件分享线程,线程代码依旧位于BluetoothOppManager类中。
/**线程用于将传输的文件插入到db中,因为当传输多个文件时(以传输100个文件为例)会是一个耗时操作,所以需要开启线程来处理。可以创建多个线程来实现对多个设备的文件传输。
* Thread to insert share info to db. In multiple files (say 100 files)
* share case, the inserting share info to db operation would be a time
* consuming operation, so need a thread to handle it. This thread allows
* multiple instances to support below case: User select multiple files to
* share to one device (say device 1), and then right away share to second
* device (device 2), we need insert all these share info to db.
*/
private class InsertShareInfoThread extends Thread {
private final BluetoothDevice mRemoteDevice;
private final String mTypeOfSingleFile;
private final String mUri;
private final String mNameOfSingleFile;
private final String mTypeOfMultipleFiles;
private final ArrayList<Uri> mUris;
private final boolean mIsMultiple;
private final boolean mIsHandoverInitiated;
public InsertShareInfoThread(BluetoothDevice device, boolean multiple,
String typeOfSingleFile, String uri, String nameOfSingleFile,
String typeOfMultipleFiles, ArrayList<Uri> uris,
boolean handoverInitiated) {
super("Insert ShareInfo Thread");
//远程设备信息
this.mRemoteDevice = device;
//是否是要传输多个文件
this.mIsMultiple = multiple;
//传输的单个文件类型
this.mTypeOfSingleFile = typeOfSingleFile;
//传输单个文件的uri
this.mUri = uri;
//传输的单个文件的name
this.mNameOfSingleFile = nameOfSingleFile;
//传输多个文件的文件类型
this.mTypeOfMultipleFiles = typeOfMultipleFiles;
//传输多个文件的uris
this.mUris = uris;
//传输是否已经通过WiFi ,nfc等被初始化了
this.mIsHandoverInitiated = handoverInitiated;
synchronized (BluetoothOppManager.this) {
//同步锁,对线程数量进行一个增量计算
mInsertShareThreadNum++;
}
if (V) Log.v(TAG, "Thread id is: " + this.getId());
}
@Override
public void run() {
//设置线程优先级为后台Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);
if (mRemoteDevice == null) {
Log.e(TAG, "Target bt device is null!");
return;
}
if (mIsMultiple) {
//传输多个文件
insertMultipleShare();
} else {
//传输单个文件
insertSingleShare();
}
synchronized (BluetoothOppManager.this) {
//在线程完成文件插入到db的操作后,对线程数量进行减量计算
mInsertShareThreadNum--;
}
}
/**
插入多个文件到db,只能被OPP应用程序调用
* Insert multiple sending sessions to db, only used by Opp application.
*/
private void insertMultipleShare() {
int count = mUris.size();
Long ts = System.currentTimeMillis();
for (int i = 0; i < count; i++) {
Uri fileUri = mUris.get(i);
BluetoothOppSendFileInfo fileInfo = BluetoothOppUtility.getSendFileInfo(fileUri);
ContentValues values = new ContentValues();
values.put(BluetoothShare.URI, fileUri.toString());
ContentResolver contentResolver = mContext.getContentResolver();
fileUri = BluetoothOppUtility.originalUri(fileUri);
String contentType = contentResolver.getType(fileUri);
if (V) Log.v(TAG, "Got mimetype: " + contentType + " Got uri: " + fileUri);
if (TextUtils.isEmpty(contentType)) {
contentType = mTypeOfMultipleFiles;
}
values.put(BluetoothShare.MIMETYPE, contentType);
values.put(BluetoothShare.DESTINATION, mRemoteDevice.getAddress());
values.put(BluetoothShare.TIMESTAMP, ts);
values.put(BluetoothShare.FILENAME_HINT, fileInfo.mFileName);
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));
}
}
/**
插入单个文件到db,只能被Opp应用程序调用
* Insert single sending session to db, only used by Opp application.
*/
private void insertSingleShare() {
ContentValues values = new ContentValues();
//问价uri
values.put(BluetoothShare.URI, mUri);
//文件名
values.put(BluetoothShare.FILENAME_HINT, mNameOfSingleFile);
//文件类型
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));
}
}
第一,传入线程所需参数
第二,设置线程优先级为后台,这样可以减少对cpu的占用,当多个分享线程并发时减少对主线程的影响。
第三,记录文件分享线程数量值mInsertShareThreadNum
第四,将要传输的文件插入到db中,如果是多个文件就调用insertMultipleShare插入,如果是单个文件就调用insertSingleShare插入。
该线程也就这么点儿事儿,也许到现在你该奇怪了,文件传输到底在哪儿?怎么就把文件插入到db就结束了??
难道你以为insert就只是简简单单的插入db中吗??那你就大错特错了。到现在为止,你将你要传输的文件交给了db,接下来就是ContentProvider的处理了。在介绍provider中的处理之前,先总结下从开始分享到交给provider的流程。
大致流程如下:
第一步,BluetoothOppLauncherActivity文件,选择蓝牙分享后会启动该activity(但是该activity主题为透明的,所以相当于瞒着用户启动了一个界面),在启动之后会进行判断是否开启蓝牙,如果没有开启就去开启蓝牙,如果蓝牙已经开启就打开蓝牙选择界面
第二步,DevicePickerActiviy文件:蓝牙选择界面。首先会负责加载蓝牙设备,在点击选择蓝牙设备后会先去判断是否可以发送文件(本机设备是否和远程蓝牙设备已完成配对,或者是远程蓝牙设备在接受文件时是否要授权)。如果可以就发送广播触发BluetoothOppReceiver,如果不可以就去营造条件
第三步,BluetoothOppReceive文件:一是通知系统要发送文件二是通知用户
第四步,BluetoothOppManager文件:启动线程将要发送的文件插入到db中。
在插入db时,uri为:
/**
* The content:// URI for the data table in the provider
*/
public static final Uri CONTENT_URI = Uri.parse("content://com.android.bluetooth.opp/btopp");
根据uri的域名com.android.bluetooth.opp找到所对应的provider为BluetoothOppProvider。进入到该文件中看insert方法
sURIMatcher.addURI("com.android.bluetooth.opp", "btopp", SHARES);
sURIMatcher.addURI("com.android.bluetooth.opp", "btopp/#", SHARES_ID);
匹配BluetoothShare.CONTENT_URI的id为SHARES
BluetoothOppProvider中的insert方法起到两个作用
代码分析:
如果是传出文件,则用户无需手动确认,也正如平常所见,在使用蓝牙分享文件时不会去询问用户是否分享。在253-254行代码对con进行赋值
注,题外话,对于文件超时的判断机理如下,在开始发送一个文件时延时SESSION_TIMEOUT向一个handler发送message,在对方开始接受文件后就移除该messge。一般源码上的一些判断超时的操作的机制大抵如此:即在事件开始之时延时启动线程或者是handler之类,所延时的时间即为判断超时的时间,待事件开始处理后就移除刚才的所要延时触发的动作。这种设计方式运用到应用开发中也是极好的。所以在研究源码的过程中不仅要明白这段代码是什么意思,更要看这段代码的实现原理有什么值得学习的地方
ok,到现在为止,也差不多了,本以为传出文件代码会很好分析,没想到断断续续分析了这么多天,而且篇幅这么长,依旧没有完…….我也是很无语,不知不觉就罗嗦了一大堆,紧接着就该BluetoothOppService来分析了,也该进入下一篇了。太长的篇幅知道你们也没耐心看哈哈哈