接着上一篇Android4.42-Settings源代码分析之蓝牙模块Bluetooth(上)

继续蓝牙模块源代码的研究

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:管理远程设备,配对、连接

Android4.42-Setting源代码分析之蓝牙模块Bluetooth(下)的更多相关文章

  1. Android4.42-Settings源代码分析之蓝牙模块Bluetooth(上)

    继上一篇Android系统源代码剖析(一)---Settings 接着来介绍一下设置中某个模块的源代码.本文依然是基于Android4.42源代码进行分析,分析一下蓝牙模块的实现.建议大致看一下关于S ...

  2. Android4.42-Settings源代码分析之蓝牙模块Bluetooth总体实现(总)

    本文为博主原创,转载请注明出处:http://blog.csdn.net/zrf1335348191/article/details/50995466 蓝牙相关代码已在另两篇文章中介绍,有须要的能够查 ...

  3. MapReduce源代码分析之LocatedFileStatusFetcher

    LocatedFileStatusFetcher是MapReduce中一个针对给定输入路径数组,使用配置的线程数目来获取数据块位置的有用类. 它的主要作用就是利用多线程技术.每一个线程相应一个任务.每 ...

  4. Android使用BLE(低功耗蓝牙,Bluetooth Low Energy)

    背景 在学习BLE的过程中,积累了一些心得的DEMO,放到Github,形成本文.感兴趣的同学可以下载到源代码. github: https://github.com/vir56k/bluetooth ...

  5. 分析setting源代码获取sd卡大小

    分析setting源代码获取sd卡大小 android系统有一个特点,即开源,我们可以得到任何一个应用的源代码,比如我们不知道这样的android代码怎么写,我们可以打开模拟器里面的设置(settin ...

  6. nginx源代码分析--模块分类

    ngx-modules Nginx 基本的模块大致能够分为四类: handler – 协同完毕client请求的处理.产生响应数据.比方模块, ngx_http_rewrite_module, ngx ...

  7. [android] 分析setting源代码获取SD卡大小

    保存文件到sd卡需要判断sd卡的大小,通过查看android系统的自带应用的源代码,得到方法,sdk下面的source是sdk的源代码,包含的是android.Jar下面的所有class的源代码.在a ...

  8. Zepto核心模块源代码分析

    一.Zepto核心模块架构 Zepto核心模块架构图 该图展示了Zepto核心模块架构代码的组织方式.主要分为私有变量.函数和暴露给用户的所有api. Zepto核心模块架构代码 该图展示了Zepto ...

  9. 转:SDL2源代码分析

    1:初始化(SDL_Init()) SDL简介 有关SDL的简介在<最简单的视音频播放示例7:SDL2播放RGB/YUV>以及<最简单的视音频播放示例9:SDL2播放PCM>中 ...

随机推荐

  1. vamare下centos7.0 动态获取ip报错问题

    CentOS7 Failed to start LSB: Bring up/down解决方法 centos7.0中service network restart重启报错的问题 报错信息: /etc/i ...

  2. python 字典 key 和value 互换

    这里是一个可能有用的通过字典解析实现的小技巧: 交换字典的键和值. >>> a_dict = {'a': 1, 'b': 2, 'c': 3} >>> {value ...

  3. POJ——3159Candies(差分约束SPFA+前向星+各种优化)

    Candies Time Limit: 1500MS   Memory Limit: 131072K Total Submissions: 28071   Accepted: 7751 Descrip ...

  4. POJ 3581 Sequence ——后缀数组 最小表示法

    [题目分析] 一见到题目,就有了一个显而易见obviously的想法.只需要每次找到倒过来最小的那一个字符串翻转就可以了. 然而事情并不是这样的,比如说505023这样一个字符串,如果翻转了成为320 ...

  5. USACO Party Lamps

    题目大意:一排灯有n个,有4种开关,每种开关能改变一些灯现在的状态(亮的变暗,暗的变亮)现在已知一些灯的亮暗情况,问所以可能的情况是哪些 思路:同一种开关开两次显然是没效果的,那么枚举每个开关是否开就 ...

  6. 洛谷P3758 - [TJOI2017]可乐

    Portal Description 给出一张\(n(n\leq30)\)个点\(m(m\leq100)\)条边的无向图.初始时有一个可乐机器人在点\(1\),这个机器人每秒会做出以下三种行为之一:原 ...

  7. django-Ajax发送POST请求-csrf跨站请求的三种方式

    第一种 <script> $(".eq").on("click",function () { $.ajax({ url:"/eq/&quo ...

  8. 【THUSC2016】成绩单(bzoj4897)

    $f(i,j,x,y)$ 表示区间 $[i,j]$中,第 $j$ 个数在最后一次操作中才消去,最后一次操作的最大值为 $x$,最小值为 $y$ 时的最小代价: $g(i,j)$ 表示区间 $[i,j] ...

  9. cf493E Vasya and Polynomial

    Vasya is studying in the last class of school and soon he will take exams. He decided to study polyn ...

  10. javaScriptCore 实战与小结

      源码在这,看不懂的直接撸源码就行,转载声明出处 原生调用JS的大致流程,做了个思维简图 这是代码流程 // JS数据 func getJSVar() { let context: JSContex ...