继续蓝牙模块源代码的研究
THREE。蓝牙模块功能实现
switch的分析以及本机蓝牙重命名和可见性的分析见上一篇,接下来进行第三章第三部分的介绍:关于蓝牙远程设备列表的载入。
假设没有看过,建议看看上一篇关第一章蓝牙的布局,有助于理解
3>,设备列表的载入
由于这部分代码非常多。所以在介绍时先说一下思路。程序首先通过底层的BluetoothAdapter的getBondedDevices()方法获取到已配对的设备列表,获取到列表后将数据缓存在List<CachedBluetoothDevice>中进行备份,当蓝牙界面启动后会从缓存中读取数据并显示已配对设备列表mPairedDevicesCategory,在扫描附近可用设备时会对缓存中的数据进行添加或者删除,并将数据显示在可用设备列表mAvailableDevicesCategory。而且程序会实时监听远程设备的状态变化,进行对设备列表的添加或删除。
设备列表的载入基本上就是这些,接下来挨个介绍
i>。调用底层代码获取可用设备列表并进行缓存
这部分代码的书写在BluetoothEventManager.java文件里,获取已配对设备列表的代码定义例如以下。
boolean readPairedDevices() { //mLocalAdapter是将BluetoothAdapter映射到本地,其内部代码不再书写,获取到已配对设备 Set<BluetoothDevice> 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); }
addCachedDevices()代码例如以下
void addCachedDevices() { //用于获取到缓存列表的复制 Collection<CachedBluetoothDevice> 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); } }
关于matches方法能够查看BluetoothDeviceFilter.java文件,不同的过滤器相应于不同的内部类。这些内部类实现了内部接口的matches方法。对BluetoothDevice的配对状态进行匹配,比方,过滤已经配对的蓝牙设备过滤器相应的内部类例如以下
//注。Filter为BluetoothDeviceFilter的内部接口 private static final class BondedDeviceFilter implements Filter { public boolean matches(BluetoothDevice device) { return device.getBondState() == BluetoothDevice.BOND_BONDED; } }
当对缓存列表进行过滤后,符合条件的就会调用createDevicePreference(cachedDevice)方法进行载入出来
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等等。有须要的能够看下。
在这里简单说一下各种广播
- BluetoothAdpater.ACTION_STATE_CHANGED :本机蓝牙状态发生了改变
- BluetoothAdpater.ACTION_DISCOVERY_STARTED:開始扫描
- BluetoothAdpater.ACTION_DISCOVERY_FINISHED:扫描结束
- BluetoothDevice.ACTION_FOUND:发现远程蓝牙设备
- BluetoothDevice.ACTION_DISAPPEARED:远程设备消失
- BluetoothDevice.ACTION_NAME_CHANGED:远程设备蓝牙名称改变
- BluetoothDevice.ACTION_BOND_STATE_CHANGED:远程设备连接状态改变
- BluetoothDevice.ACTION_PAIRING_CANCLE:远程设备取消配对
- BluetoothDevice.ACTION_CLASS_CHANGED:远程设备的蓝牙类已经改变
- BluetoothDevice.ACTION_UUID:
很多其它关于蓝牙广播的内容能够參考在线文档 http://www.android-doc.com/reference/android/bluetooth/BluetoothDevice.html
程序中已经为这些广播注冊了监听器,当接收到广播后作出对应动作,对列表即可改动
首先是对缓存列表进行更改。然后再对显示列表进行更改。
4>,蓝牙搜索附近可用设备
搜索功能流程例如以下:首先检測蓝牙是否开启,假设开启检測是否正在搜索。假设正在搜索则不做处理,假设未开启搜索则开启搜索
程序中的设置是假设蓝牙未开启或者正在搜索的话搜索设备button不可用。假设强制搜索是否正在播放音乐等。直接搜索。程序中设置的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(); } }
FOUR,总结
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:管理远程设备,配对、连接