继续蓝牙模块源码的研究
switch的分析以及本机蓝牙重命名和可见性的分析见上一篇,接下来进行第三章第三部分的介绍:关于蓝牙远程设备列表的加载。如果没有看过,建议看看上一篇关第一章蓝牙的布局,有助于理解
3>,设备列表的加载
因为这部分代码很多,所以在介绍时先说一下思路,程序首先通过底层的BluetoothAdapter的getBondedDevices()方法获取到已配对的设备列表,获取到列表后将数据缓存在List
i>,调用底层代码获取可用设备列表并进行缓存
这部分代码的书写在BluetoothEventManager.java文件中,获取已配对设备列表的代码定义如下,
boolean readPairedDevices() {
//mLocalAdapter是将BluetoothAdapter映射到本地,其内部代码不再书写,获取到已配对设备
Set bondedDevices = mLocalAdapter.getBondedDevices();
if (bondedDevices == null) {
return false;
}
boolean deviceAdded = false;
for (BluetoothDevice device : bondedDevices) {
//这一步调用的是设备缓存列表的管理类CachedBluetoothDeviceManager中的方法findDevice
//用于检查缓存列表中是否已经存在该device,若存在就将device返回,若不存在就返回null
CachedBluetoothDevice cachedDevice = mDeviceManager.findDevice(device);
if (cachedDevice == null) {
//如果缓存列表中没有该设备就调用管理类CachedBluetoothDeviceManager中的addDevice
//将设备添加到缓存列表中
cachedDevice = mDeviceManager.addDevice(mLocalAdapter, mProfileManager, device);
//将设备更新到屏幕上
dispatchDeviceAdded(cachedDevice);
deviceAdded = true;
}
}
return deviceAdded;
}
该方法在两个地方调用,一个是当本地蓝牙BluetoothAdapter开启后调用,一个就是当远程设备BluetoothDevice的状态发生改变时调用
如下,是在LocalBluetoothProfileManager.java文件中的代码,在蓝牙开启后会调用如下代码读取已配对的设备
void setBluetoothStateOn() {
ParcelUuid[] uuids = mLocalAdapter.getUuids();
if (uuids != null) {
updateLocalProfiles(uuids);
}
mEventManager.readPairedDevices();
}
当远程设备发生改变时会发送ACTION_BOND_STATE_CHANGED的广播,在注册的handler中调用readPairedDevices()方法读取配对设备。监听广播的代码在BluetoothEventManager.java中。
其实,在进行扫描后,获取的设备列表与可配对设备列表缓存在一起,这部分在介绍扫描处介绍
ii>,设备列表加载到屏幕
现在不论是已配对设备或是附近可用设备均缓存在同一列表,所以两个列表的加载类似,附近可用设备列表显示时会有一个progress,所以在构造preferenceGroup对象时有所区别,还有一个区别就是设备的状态,通过底层的BluetoothDevice类中的getBondState()来获取远程设备的配对状态来区分。
设备列表的加载为BluetoothSettings中,已配对设备列表为mPairedDevicesCategory,附近可用设备列表为mAvailableDevicesCategory,均为PreferenceCategory对象,加载时调用的是BluetoothSettings.java中的addDeviceCategory(PreferenceGroup preferenceGroup,int titleId,BluetoothDeviceFilter.Filter filter)方法。
已配对设备设置的过滤器为BluetoothDeviceFilter.BONDED_DEVICE_FILTER
附近可用设备设置的过滤器为BluetoothDeviceFilter.UNBONEDE_DEVICE_FILTER
private void addDeviceCategory(PreferenceGroup preferenceGroup, int titleId,
BluetoothDeviceFilter.Filter filter) {
//设置preferenceGroup的标题
preferenceGroup.setTitle(titleId);
//为PreferenceScreen添加preferenceGroup,注意此时preferenceGroup里为空没有任何的preference
getPreferenceScreen().addPreference(preferenceGroup);
//设置过滤器,调用的是DeviceListPreferenceFragment中方法
setFilter(filter);
//调用DeviceListPreferencFragment中的方法,讲preferenceGroup传过去,方便对其操作
setDeviceListGroup(preferenceGroup);
//调用DeviceListPreferenceFragment中的方法
addCachedDevices();
//将preference设置为可点击的状态
preferenceGroup.setEnabled(true);
}
void addCachedDevices() {
//用于获取到缓存列表的复制
Collection cachedDevices =
mLocalManager.getCachedDeviceManager().getCachedDevicesCopy();
for (CachedBluetoothDevice cachedDevice : cachedDevices) {
//该方法用于将设备显示出来
onDeviceAdded(cachedDevice);
}
}
onDeviceAdded(cachedDevice)代码如下
public void onDeviceAdded(CachedBluetoothDevice cachedDevice) {
if (mDevicePreferenceMap.get(cachedDevice) != null) {
return;
}
// Prevent updates while the list shows one of the state messages
if (mLocalAdapter.getBluetoothState() != BluetoothAdapter.STATE_ON) return;
//这就是过滤器的作用了,首先过滤出要求的设备,要求已配对或者是附近可用设备
//列表过滤后,就可以加载出来了
if (mFilter.matches(cachedDevice.getDevice())) {
createDevicePreference(cachedDevice);
}
}
//注,Filter为BluetoothDeviceFilter的内部接口
private static final class BondedDeviceFilter implements Filter {
public boolean matches(BluetoothDevice device) {
return device.getBondState() == BluetoothDevice.BOND_BONDED;
}
}
void createDevicePreference(CachedBluetoothDevice cachedDevice) {
//构造preference对象
BluetoothDevicePreference preference = new BluetoothDevicePreference(
getActivity(), cachedDevice);
//在该方法对preference进行初始化,可按需实现
initDevicePreference(preference);
//将preference显示出来
mDeviceListGroup.addPreference(preference);
mDevicePreferenceMap.put(cachedDevice, preference);
}
设备列表的加载就到这儿,总结一下就是,对preferenceGroup整体的管理,诸如preference的增删该查操作,位于DeviceListPreferenceFragment.java文件中,但是对于preferenceGroup内部的preference的显示UI状态诸如title、summary、icon等,不在该类中而是在BluetoothDevicePreference.java中进行处理,从构造的preference对象就可以看出。
iii>,设备列表的改变
当设备状态发生变化时设备列表的显示也要发生变化,诸如设备进行配对,取消配对等操作,在BluetoothEvenManager.java中对设备的状态进行监听并处理,在该类的构造方法中注册了许多的监听器,监听蓝牙相关的变化,比如蓝牙状态改变ACTION_STATE_CHANGED等等,有需要的可以看下。
在这里简单说一下各种广播
更多关于蓝牙广播的内容可以参考在线文档 http://www.android-doc.com/reference/android/bluetooth/BluetoothDevice.html
程序中已经为这些广播注册了监听器,当接收到广播后作出相应动作,对列表就行修改
首先是对缓存列表进行更改,然后再对显示列表进行更改。
4>,蓝牙搜索附近可用设备
搜索功能流程如下:首先检测蓝牙是否开启,如果开启检测是否正在搜索,如果正在搜索则不做处理,如果未开启搜索则开启搜索
程序中的设置是如果蓝牙未开启或者正在搜索的话搜索设备按钮不可用。如果强制搜索是否正在播放音乐等,直接搜索。程序中设置的SCAN_EXPIRATION_MS为5分钟,有一种情况是搜索已经结束,但是时间没有5分钟,如果是非强制搜索在这种情况下将不开启搜索。
void startScanning(boolean force) {
// Only start if we're not already scanning
if (!mAdapter.isDiscovering()) {
if (!force) {
// Don't scan more than frequently than SCAN_EXPIRATION_MS,
// unless forced
if (mLastScan + SCAN_EXPIRATION_MS > System.currentTimeMillis()) {
return;
}
// If we are playing music, don't scan unless forced.
A2dpProfile a2dp = mProfileManager.getA2dpProfile();
if (a2dp != null && a2dp.isA2dpPlaying()) {
return;
}
}
if (mAdapter.startDiscovery()) {
mLastScan = System.currentTimeMillis();
}
}
}
在搜索过程中发现设备会发送广播,程序会在广播处理代码中对缓存列表以及显示列表进行更新。
当开始扫描时发送扫描开始的广播,handler进行处理,当扫描接触时也是下列handler进行处理,只是started为false
private class ScanningStateChangedHandler implements Handler {
private final boolean mStarted;
//开始扫描时传入的为true
ScanningStateChangedHandler(boolean started) {
mStarted = started;
}
public void onReceive(Context context, Intent intent,
BluetoothDevice device) {
synchronized (mCallbacks) {
for (BluetoothCallback callback : mCallbacks) {
//调用DeviceListPreferenceFragment.java中的方法显示扫描指示progress
callback.onScanningStateChanged(mStarted);
}
}
//首先更新缓存列表,然后对显示列表进行排序更新显示。
//排序规则代码在CachedBluetoothDevice.java中
mDeviceManager.onScanningStateChanged(mStarted);
//该方法用于保存开始扫描的时间
LocalBluetoothPreferences.persistDiscoveringTimestamp(context);
}
}
当扫描的过程中发现远程设备时处理如下
private class DeviceFoundHandler implements Handler {
public void onReceive(Context context, Intent intent,
BluetoothDevice device) {
//获取到蓝牙的信号强度,默认为Short类型的最小值-2的15次方
short rssi = intent.getShortExtra(BluetoothDevice.EXTRA_RSSI, Short.MIN_VALUE);
//获取到远程设备的类型
BluetoothClass btClass = intent.getParcelableExtra(BluetoothDevice.EXTRA_CLASS);
//获取到远程设备的name
String name = intent.getStringExtra(BluetoothDevice.EXTRA_NAME);
//获取到远程设备后检测是否在缓存列表中,若有就返回设备,若没有返回null
CachedBluetoothDevice cachedDevice = mDeviceManager.findDevice(device);
if (cachedDevice == null) {
//将设备添加到缓存列表中
cachedDevice = mDeviceManager.addDevice(mLocalAdapter, mProfileManager, device);
// callback to UI to create Preference for new device
//将添加的设备更新到显示列表
dispatchDeviceAdded(cachedDevice);
}
//缓存device的信号强度,设备类型,name
cachedDevice.setRssi(rssi);
cachedDevice.setBtClass(btClass);
cachedDevice.setName(name);
//在这里不是设置可见性,与列表的排序相关
cachedDevice.setVisible(true);
}
}
5>,蓝牙配对
设备列表中包括已配对设备、未配对设备、已连接设备等,当点击preference时会首先判断处于哪个状态,然后去进行下一个状态。如果没有配对,就进行配对
配对程序如下,在进行配对时首先检查远程设备是否正在配对,如果是,就返回true,如果没有在配对就现将本机的蓝牙配对状态设为true表示正在配对,紧接着停止蓝牙的扫描操作,与远程设备进行配对,配对成功后进行自动连接
//该方法返回true代表正在进行配对操作,若返回false则表示配对操作失败弹出失败弹窗
boolean startPairing() {
//首先查看一下,远程设备是否正在配对,如果正在配对就返回true,
if(mLocalAdapter.checkPairingState() == true)
{
return true;
}
//将本机蓝牙适配器的配对状态设为true
mLocalAdapter.setPairingState(true);
// Pairing is unreliable while scanning, so cancel discovery
//如果本机蓝牙正在进行扫描蓝牙的操作,则停止该操作,因为该操作会阻塞
if (mLocalAdapter.isDiscovering()) {
mLocalAdapter.cancelDiscovery();
}
//调用framework层的方法,判断远程蓝牙设备是否可以配对以及请求配对是否超时,
//如果可以配对就把远程蓝牙设备的配对状态设置为正在配对
if (!mDevice.createBond()) {
//如果与远程蓝牙设备创建配对失败则将本机蓝牙配对状态设为false
mLocalAdapter.setPairingState(false);
return false;
}
//配对之后是否进行自动连接,true为自动进行连接
mConnectAfterPairing = true; // auto-connect after pairing
return true;
}
6>,蓝牙连接
在进行连接前首先判断是否已经配对了,如果没有配对就会进行配对,取消连接的操作,若已经配对了则进行设备连接
void connect(boolean connectAllProfiles) {
//如果没有配对,就进行配对,并且退出连接的方法
if (!ensurePaired()) {
return;
}
//获取到系统启动到现在的时间间隔
mConnectAttempted = SystemClock.elapsedRealtime();
//从英语中可以看出意思是在连接时不重置定时器
connectWithoutResettingTimer(connectAllProfiles);
}
接下来看一下connectWithoutResettingTimer(connectAllProfiles)方法的代码
private void connectWithoutResettingTimer(boolean connectAllProfiles) {
// Try to initialize the profiles if they were not.
//本机蓝牙与远程设备通信的配置规范,如果没有配置文件则不能进行通信
//配置规范指定所使用的蓝牙通信协议,用户界面格式等等
if (mProfiles.isEmpty()) {
Log.d(TAG, "No profiles. Maybe we will connect later");
return;
}
// Reset the only-show-one-error-dialog tracking variable
//当我们去连接多个设备发生错误时我们只想显示一个错误对话框,
mIsConnectingErrorPossible = true;
int preferredProfiles = 0;
for (LocalBluetoothProfile profile : mProfiles) {
if (connectAllProfiles ? profile.isConnectable() : profile.isAutoConnectable()) {
if (profile.isPreferred(mDevice)) {
++preferredProfiles;
//连接设备,具体的可以查看关于profile的内容
connectInt(profile);
}
}
}
if (DEBUG) Log.d(TAG, "Preferred profiles = " + preferredProfiles);
if (preferredProfiles == 0) {
connectAutoConnectableProfiles();
}
}
1>,首先总结一下一些常用的frameworks层的蓝牙相关方法
i>,本地蓝牙相关
获取本地蓝牙适配器:BluetoothAdapter.getDefaultAdapter();
开启蓝牙:BluetoothAdapter----enable().
关闭蓝牙:BluetoothAdapter----disable().
重命名蓝牙:BluetoothAdapter----setName().
获取蓝牙名称:BluetoothAdapter----getName().
开启可检测性:BluetoothAdapter----setScanMode(BluetoothAdapter.
SCAN_MODE_CONNECTABLE_DISCOVERABLE,timeout).//当timeout设为0时表示永不超时
获取蓝牙状态:BluetoothAdapter----getState().
获取蓝牙所支持的uuid数组:BluetoothAdapter----getUuids().
获取已配对设备:BluetoothAdapter----getBoneDevices().
开启扫描:BluetoothAdapter----startDiscovery().
停止扫描:BluetoothAdapter----cancelDiscovery().
判断是否正在扫描:BluetoothAdapter----isDiscovery().
扫描低功耗BLE蓝牙设备:BluetoothAdapter----startLeScan(mLeScanCallBack).
停止对BLE设备的扫描:BluetoothAdapter----stopLeScan(mLeScanCallBack).
ii>,各种广播相关参考网址,这是一个API在线文档,解释的很清楚
http://www.android-doc.com/reference/android/bluetooth/BluetoothDevice.html
2>,蓝牙模块源码中涉及到的类
i>,BluetoothSettings.java:蓝牙界面的显示布局fragment,只有布局相关,会对本机蓝牙的名字,可检测性进行实时更新,所有的点击事件的处理都在别处
ii>,DeviceListPreferenceFragment:远程设备列表的显示的更新,包括已配对列表和附近可用设备列表
iii>,BluetoothDevicePreference:列表中每个设备的title,summary,icon的修改,包括设备的点击事件
iv>,CachedBluetoothDevice:管理远程设备,配对、连接