接着来介绍一下设置中某个模块的源码,本文依旧是基于Android4.42源码进行分析,分析一下蓝牙模块的实现。建议大致看一下关于Settings的剖析。
ZERO,蓝牙模块的fragment及其配置
1>,首先由Settings_headers.xml文件可以知道,蓝牙对应的fragment为BluetoothSettings.Java,对应的id,icon,title,不再赘述,可自行查看xml文件即可
- <!-- Bluetooth -->
- <header
- .......
- android:fragment="com.android.settings.bluetooth.BluetoothSettings"
- ......./>
2>,所涉及到的清单配置文件中的属性详解,清单文件中介绍了蓝牙界面启动相关的一些设置,诸如有快捷方式入口,以及是否隐藏进程等等,在这里大致对一些不常见的属性进行说明,方便查阅
- <activity android:name="......"
- android:uiOptions="splitActionBarWhenNarrow"
- android:configChanges="orientation|keyboardHidden|screenSize"
- android:label="@string/bluetooth_settings_title"
- android:taskAffinity=""
- android:excludeFromRecents="true">
- <intent-filter>
- ......
- </intent-filter>
- <meta-data android:name="com.android.settings.FRAGMENT_CLASS"
- android:value="com.android.settings.bluetooth.BluetoothSettings" />
- <meta-data android:name="com.android.settings.TOP_LEVEL_HEADER_ID"
- android:resource="@id/bluetooth_settings" />
- </activity>
-
- <!-- Keep compatibility with old shortcuts. -->
- <activity-alias android:name=".bluetooth.BluetoothSettings"
-
- android:label="@string/bluetooth_settings_title"
- android:targetActivity="Settings$BluetoothSettingsActivity"
- android:exported="true"
- android:clearTaskOnLaunch="true">
- <meta-data android:name="com.android.settings.FRAGMENT_CLASS"
- android:value="com.android.settings.bluetooth.BluetoothSettings" />
- <meta-data android:name="com.android.settings.TOP_LEVEL_HEADER_ID"
- android:resource="@id/bluetooth_settings" />
- </activity-alias>
可以看到Bluetooth涉及到两个activity节点,一个是activity,还有一个是activity-alias(activity的别名,用于兼容旧版的快捷方式)
- android:uiOptions="splitActionBarWhenNarrow" // 关于导航栏actionbar的配置,在此配置为当屏幕宽度不够时控件自动显示在屏幕底部
- android:configChanges="orientation|keyboardHidden|screenSize" //用于禁止横竖屏切换,这个属性有几个问题需要好好说一下:第一,若不设置该属性,则切屏时会重新调用各个生命周期,切横屏调用一次,切竖屏则需要调用两次。第二,如果设置了该属性android:configChanges="orientation|keyboardHidden,则不会重新调用生命周期只会执行onConfigurationChanged方法。第三,第二条说法成立的条件是必须是Android3.2以下的版本,如果高于该版本,则必须在该属性后加上screensize(屏幕的size),才会起作用。
- android:taskAffinity="" //用于指定创建该activity后用于进入的栈,如果未指定该属性,则就照application节点下指定的栈,如果application也未显示的指定,则为默认的包下。
- android:excludeFromRecents="true" //是否显示在最近启动的程序列表中,设为true表示不显示。手机长按home键可以看到最近的程序列表,用此属性可以隐藏进程
- 可以看到有一个与activity并列的<activity-alias../>节点。该节点属于activity的别名,目标activity不会覆盖该节点下的属性,而且,针对目标activity设置的属性会自动添加到activity-alias节点下,也就是说蓝牙模块满足两个节点下的属性,之所以有别名进行属性设置,主要是为了兼容旧的快捷方式
- android:targetActivity="Settings$BluetoothSettingsActivity" //由快捷方式进入所启动的activity
- android:exported="true" //是否支持其他应用调用启动该activity,true为是。
还加入了关于蓝牙的两个权限,BLUETOOTH和BLUETOOTH_ADMIN,前者用于允许与已经配对的蓝牙设备进行连接主要是配对后的权限,后者用于允许发现和配对蓝牙设备,主要是配对前的权限。
好了,属性配置就介绍到这儿了,接下来要真正开始蓝牙模块的学习了,首先明确模块的布局,蓝牙模块的功能,蓝牙实现的有:开启蓝牙,蓝牙重命名,蓝牙检测性及检测时间设置,扫描附近可用蓝牙设备,加载已经配对的蓝牙设备,与设备配对,连接,通信。
ONE,蓝牙布局实现
- public final class BluetoothSettings extends DeviceListPreferenceFragment {
-
-
- .............
- @Override
- public View onCreateView(LayoutInflater inflater, ViewGroup container,
- Bundle savedInstanceState) {
- addPreferencesFromResource(R.xml.bluetooth_settings);
- }
-
- ............
- }
1>,可以看出BluetoothSettings属于PreferenceFragment,所要加载的布局文件为Bluetooth_settings.xml文件。以下是布局文件代码,总共四行,节点为PreferenceScreen,代表显示整个屏幕,内部可嵌套不同类型的标签,在这里内部未有任何标签,是在代码中动态添加的不同种类的布局。
- <PreferenceScreen
- xmlns:android="http://schemas.android.com/apk/res/android"
- android:title="@string/bluetooth_settings" >
- </PreferenceScreen>
2>,展示两张蓝牙开启和关闭时布局示意图
- 圈1:ActionBar顶部导航栏,显示title,以及蓝牙开关,开关的添加代码在addPreferencesForActivity方法中,
- @Override
- void addPreferencesForActivity() {
- Activity activity = getActivity();
-
-
- Switch actionBarSwitch = new Switch(activity);
-
- if (activity instanceof PreferenceActivity) {
- PreferenceActivity preferenceActivity = (PreferenceActivity) activity;
- if (preferenceActivity.onIsHidingHeaders() || !preferenceActivity.onIsMultiPane()) {
- final int padding = activity.getResources().getDimensionPixelSize(
- R.dimen.action_bar_switch_padding);
- actionBarSwitch.setPaddingRelative(0, 0, padding, 0);
-
- activity.getActionBar().setDisplayOptions(ActionBar.DISPLAY_SHOW_CUSTOM,
- ActionBar.DISPLAY_SHOW_CUSTOM);
-
- activity.getActionBar().setCustomView(actionBarSwitch, new ActionBar.LayoutParams(
- ActionBar.LayoutParams.WRAP_CONTENT,
- ActionBar.LayoutParams.WRAP_CONTENT,
- Gravity.CENTER_VERTICAL | Gravity.END));
- }
- }
-
- mBluetoothEnabler = new BluetoothEnabler(activity, actionBarSwitch);
-
- setHasOptionsMenu(true);
- }
- 圈2:ActionBar底部栏,可进行蓝牙设备的搜索,检测时间,已配对设备列表等一些除了配对之外的设置,Actionbar的相关布局在onCreateOptionsMenu方法中,利用如下代码可自定义actionbar
- menu.add(groupId, itemId, order, title)
- nbsp;.setEnabled(enabled)
- .setShowAsAction(actionEnum);
group_id:int 型数值,代表组的意思
item_id: int 型数值,每个菜单选项的唯一标识
order_id:int 型数值,菜单显示的顺序,如果为0表示按add顺序显示
title: charsequence型数字,菜单item的title
setEnabled(enable):用来设置是否可点击
setShowAsAction(actionEnum) : 用来设置屏幕宽度不同时item的显示,actionEnum有以下几个取值。
- 圈3:蓝牙未开启时preferencescreen没有任何类别,listview的emptyview
- getListView().setEmptyView(mEmptyView);
- 圈4:本机蓝牙设备的相关设置,包括本机蓝牙名称,蓝牙对附近可用设备的可见性,蓝牙对已经配对设备的可见性,当检测到蓝牙开启时会添加一个本机蓝牙信息的Preference,在方法updateContent中完成添加或者移除,添加代码如下:
- if (mMyDevicePreference == null) {
-
- mMyDevicePreference = new Preference(getActivity());
- }
-
- mMyDevicePreference.setTitle(mLocalAdapter.getName());
-
- if (getResources().getBoolean(com.android.internal.R.bool.config_voice_capable)) {
-
- mMyDevicePreference.setIcon(R.drawable.ic_bt_cellphone);
- } else {
-
- mMyDevicePreference.setIcon(R.drawable.ic_bt_laptop);
- }
-
- mMyDevicePreference.setPersistent(false);
-
- mMyDevicePreference.setEnabled(true);
-
- preferenceScreen.addPreference(mMyDevicePreference);
preferencescreen添加或者移除的代码如下:
-
- getPreferenceScreen().addPreference(mAvailableDevicesCategory);
- .,....
-
- preferenceScreen.removePreference(mPairedDevicesCategory);
- .....
-
- Preference preferenceScreen.removeAll();
- 圈5:已配对设备列表mPairedDevicesCategory
- 圈6:附近可用设备列表mAvailableDevicesCategory
总的来说,蓝牙布局的实现借助的是actionbar+Preference,均是在代码中动态的添加布局,Actionbar的添加操作在方法addPreferencesForActivity和onCreateOptionsMenu中实 现。不同Category的Preference的添加和修改与蓝牙开关状态、是否有已经配对的蓝牙设备以及附近是否有可用的蓝牙设备。
蓝牙界面的布局暂且介绍到这儿,有问题的可博文下留言,我再进行补充。
TWO,蓝牙模块方法简介
蓝牙模块打开后执行流程getHelpResource()---->addPreferencesForActivity()--->onCreateView()--->initDevicePreference()--->onAcitivityCreated()--->onResume()-->initDevicePreference()--->onCreateOptionsMenu()。
先介绍一下覆写的方法的作用
1>,getResource()方法,定义在SettingPreferenceFragment.java类中,默认返回的是0,方法的解释是如果想要在菜单栏上显示help item,可以覆写该方法,用于一些说明(Specified in product overlays)。
2>,addPreferencesForActivity()方法,用于添加actionbar上的switch,代码见蓝牙布局部分
3>,onCreateView()方法,fragment的生命周期方法,用于加载xml布局
- @Override
- public View onCreateView(LayoutInflater inflater, ViewGroup container,
- Bundle savedInstanceState) {
- addPreferencesFromResource(R.xml.bluetooth_settings);
- View v = inflater.inflate(R.layout.add_preference_list_fragment,null);
- mEmptyView = (TextView) v.findViewById(R.id.add_empty);
- }
4>,initDevicePreference()方法,获取到已经配对的蓝牙设备,设置监听事件
- @Override
- void initDevicePreference(BluetoothDevicePreference preference) {
- CachedBluetoothDevice cachedDevice = preference.getCachedDevice();
- if (cachedDevice.getBondState() == BluetoothDevice.BOND_BONDED) {
-
-
- preference.setOnSettingsClickListener(mDeviceProfilesListener);
- }
- }
5>,onDevicePreferenceClick()方法,远程蓝牙设备的点击事件
- @Override
- void onDevicePreferenceClick(BluetoothDevicePreference btPreference) {
-
- mLocalAdapter.stopScanning();
-
- super.onDevicePreferenceClick(btPreference);
- }
6>,onBluetoothStateChanged()方法,蓝牙开关状态改变时监听
7>,onScanningStateChanged()方法,监听扫描可用蓝牙设备时扫描的状态改变,开启扫描,正在扫描,扫描结束,并更新进度条
THREE,蓝牙功能实现流程
功能模块这块儿主要分析一下实现的流程,代码为辅,若在看源码时代码有什么问题,可在博文下咨询
1>,蓝牙开关switch相关,
蓝牙开关涉及到本地蓝牙状态的更改以及用户点击switch更改蓝牙状态,当本地蓝牙状态发生改变时需要更新switch的状态,当switch的状态发生改变时需要更新本地的蓝牙状态。这就涉及到了,注册广播监听本地蓝牙状态,为switch注册监听器监听switch的更改,以及对switch状态进行设置的方法。
首先执行addPreferencesForActivity加载switch,在该方法中构造BluetoothEnabler对象,对switch的状态进行初始化以及状态改变的监听。
接下来对BluetoothEnabler进行分析,先看一下BluetoothEnabler的构造方法
- public BluetoothEnabler(Context context, Switch switch_) {
- mContext = context;
- mSwitch = switch_;
- mValidListener = false;
-
-
- LocalBluetoothManager manager = LocalBluetoothManager.getInstance(context);
- if (manager == null) {
-
- mLocalAdapter = null;
- mSwitch.setEnabled(false);
- } else {
-
- mLocalAdapter = manager.getBluetoothAdapter();
- }
-
- mIntentFilter = new IntentFilter(BluetoothAdapter.ACTION_STATE_CHANGED);
-
- }
紧接着在resume()中进行蓝牙开关状态的设置
- public void resume() {
- if (mLocalAdapter == null) {
-
- mSwitch.setEnabled(false);
- return;
- }
-
-
-
-
- handleStateChanged(mLocalAdapter.getBluetoothState());
-
-
- mContext.registerReceiver(mReceiver, mIntentFilter);
-
- mSwitch.setOnCheckedChangeListener(this);
- mValidListener = true;
- }
在resume方法中做了三件事,
i>,根据本地蓝牙适配器获取到此时的蓝牙状态对switch进行设置handleStateChanged(state)方法代码很简单,不再赘述
ii>,注册广播监听蓝牙状态-----当系统蓝牙状态发生改变时需要更新switch状态,广播接收器中的代码如下
- private final BroadcastReceiver mReceiver = new BroadcastReceiver() {
- @Override
- public void onReceive(Context context, Intent intent) {
-
-
- int state = intent.getIntExtra(BluetoothAdapter.EXTRA_STATE, BluetoothAdapter.ERROR);
-
- handleStateChanged(state);
- }
- };
iii>,为switch设置监听事件,当switch发生改变时,需要对系统的蓝牙状态进行行改变。系统的蓝牙开关状态发生改变时,会发送状态改变的广播,对switch进行更改
- public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
-
-
-
- if (mLocalAdapter != null) {
- if (isChecked && WifiSettings.needPrompt(mContext)) {
- return;
- }
-
- mLocalAdapter.setBluetoothEnabled(isChecked);
- }
-
- mSwitch.setEnabled(false);
- }
接下来看看对本地蓝牙适配器更改的方法
- public void setBluetoothEnabled(boolean enabled) {
-
- boolean success = enabled
- ? mAdapter.enable()
- : mAdapter.disable();
- isPairing = false;
- if (success) {
-
- setBluetoothStateInt(enabled
- ? BluetoothAdapter.STATE_TURNING_ON
- : BluetoothAdapter.STATE_TURNING_OFF);
- } else {
-
- syncBluetoothState();
- }
- }
BluetoothAdapter的enable方法用于开启蓝牙,disable用于关闭蓝牙
2>,本机蓝牙设置,包括可检测性、蓝牙名称、可检测时间。
i>,加载本机蓝牙相关信息
在updateContent方法中进行动态的添加preference(单一控件,类似checkbox)或者preferencecategory(组合控件,类似linearlayout)。本机蓝牙的信息添加的是一个preference
- if (mMyDevicePreference == null) {
- mMyDevicePreference = new Preference(getActivity());
- }
-
- mMyDevicePreference.setTitle(mLocalAdapter.getName());
-
- if (getResources().getBoolean(com.android.internal.R.bool.config_voice_capable)) {
- mMyDevicePreference.setIcon(R.drawable.ic_bt_cellphone);
- } else {
- mMyDevicePreference.setIcon(R.drawable.ic_bt_laptop);
- }
-
- mMyDevicePreference.setPersistent(false);
-
- mMyDevicePreference.setEnabled(true);
-
- preferenceScreen.addPreference(mMyDevicePreference);
-
- if (!isRestrictedAndNotPinProtected()) {
- if (mDiscoverableEnabler == null) {
- mDiscoverableEnabler = new BluetoothDiscoverableEnabler(getActivity(),
- mLocalAdapter, mMyDevicePreference);
-
- mDiscoverableEnabler.resume();
- LocalBluetoothManager.getInstance(getActivity()).setDiscoverableEnabler(
- mDiscoverableEnabler);
- }
- }
- <pre name="code" class="java">
-
- if (mPairedDevicesCategory == null) {
- mPairedDevicesCategory = new PreferenceCategory(getActivity());
- } else {
- mPairedDevicesCategory.removeAll();
- }
- addDeviceCategory(mPairedDevicesCategory,
- R.string.bluetooth_preference_paired_devices,
- BluetoothDeviceFilter.BONDED_DEVICE_FILTER);
-
- int numberOfPairedDevices = mPairedDevicesCategory.getPreferenceCount();
- if (mDiscoverableEnabler != null) {
-
- mDiscoverableEnabler.setNumberOfPairedDevices(numberOfPairedDevices);
- }
ii>,修改蓝牙名称
修改蓝牙名称的按钮在菜单栏中id为MENU_ID_RENAME_DEVICE,过程是修改后将蓝牙名称赋给系统的蓝牙适配器,系统蓝牙适配发送广播通知蓝牙名称已经修改,在接受到蓝牙名称修改后的广播后更新preference的title。代码流程如下
- public boolean onOptionsItemSelected(MenuItem item) {
- ........
- case MENU_ID_RENAME_DEVICE:
-
-
- new BluetoothNameDialogFragment().show(
- getFragmentManager(), "rename device");
- return true;
- ......
- }
当蓝牙名称发生变化后,会发送广播通知蓝牙名称已变,对preference进行更新。在此进行强调,只要是对对话框中的编辑框进行了编辑,不论内容是否修改(比如删除之后又添加上一模一样的),均会发送蓝牙名称已经更改的广播。至此,蓝牙名称的修改已经结束
iii>,蓝牙可检测性的修改
先普及一个知识有助于理解蓝牙的可检测性,BluetoothAdapter的getScanMode有三个值,它们的含义分别是
SCAN_MODE_NONE,int型值,大小为20,表示对任何设备不可见,且无法进行扫描功能
SCAN_MODE_CONNECTABLE,int型值,大小为21,表示只对已经配对的设备可见,可以扫描其他设备
SCAN_MODE_CONNECTABLE_DISCOVERABLE,int型值,大小为23,表示对附近所有设备可见,可以扫描其他设备。
蓝牙的可检测性由本地蓝牙的扫描模式BluetoothAdapter的getScanMode()来决定,所以接下来首先将蓝牙的可检测性显示在mMyDevicePreference的summary副标题处,然后副标题的更新位于类BluetoothDiscoverableEnabler中,在该类的resume方法中首先需要注册广播监听本地蓝牙扫描模式的改变
- private final BroadcastReceiver mReceiver = new BroadcastReceiver() {
- @Override
- public void onReceive(Context context, Intent intent) {
-
- if (BluetoothAdapter.ACTION_SCAN_MODE_CHANGED.equals(intent.getAction())) {
- int mode = intent.getIntExtra(BluetoothAdapter.EXTRA_SCAN_MODE,
- BluetoothAdapter.ERROR);
- if (mode != BluetoothAdapter.ERROR) {
-
- handleModeChanged(mode);
- }
- }
- }
- };
更新副标题的方法如下,因为分三种模式,所以副标题也有三种情况
- void handleModeChanged(int mode) {
-
- if (mode == BluetoothAdapter.SCAN_MODE_CONNECTABLE_DISCOVERABLE) {
-
- mDiscoverable = true;
-
- updateCountdownSummary();
- } else {
-
- mDiscoverable = false;
-
- setSummaryNotDiscoverable();
- }
- }
然后为preference添加一个点击事件,当点击preference时将标志位取反,并且更新preference的summary以及蓝牙的扫描模式
- public boolean onPreferenceClick(Preference preference) {
- mDiscoverable = !mDiscoverable;
- setEnabled(mDiscoverable);
- return true;
- }
在更新summary的时候涉及到对可检测性时间的更新,说一下实现逻辑不贴代码了,有需要的再问吧
首先明确可检测性事件,然后在开启限时的可检测性后再更新summary的方法中开启一个线程,该线程中再次调用该更新summary的方法,在更新summary中的方法中会对时间进行判断,如果时间结束了,就退出该方法。
THREE,蓝牙模块功能实现
switch的分析以及本机蓝牙重命名和可见性的分析见上一篇,接下来进行第三章第三部分的介绍:关于蓝牙远程设备列表的加载。如果没有看过,建议看看上一篇关第一章蓝牙的布局,有助于理解
3>,设备列表的加载
因为这部分代码很多,所以在介绍时先说一下思路,程序首先通过底层的BluetoothAdapter的getBondedDevices()方法获取到已配对的设备列表,获取到列表后将数据缓存在List<CachedBluetoothDevice>中进行备份,当蓝牙界面启动后会从缓存中读取数据并显示已配对设备列表mPairedDevicesCategory,在扫描附近可用设备时会对缓存中的数据进行增加或者删除,并将数据显示在可用设备列表mAvailableDevicesCategory,并且程序会实时监听远程设备的状态变化,进行对设备列表的增加或删除。设备列表的加载基本上就是这些,接下来挨个介绍
i>,调用底层代码获取可用设备列表并进行缓存
这部分代码的书写在BluetoothEventManager.Java文件中,获取已配对设备列表的代码定义如下,
- boolean readPairedDevices() {
-
- Set<BluetoothDevice> bondedDevices = mLocalAdapter.getBondedDevices();
- if (bondedDevices == null) {
- return false;
- }
- boolean deviceAdded = false;
- for (BluetoothDevice device : bondedDevices) {
-
-
- CachedBluetoothDevice cachedDevice = mDeviceManager.findDevice(device);
- if (cachedDevice == null) {
-
-
- 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.setTitle(titleId);
-
- getPreferenceScreen().addPreference(preferenceGroup);
-
- setFilter(filter);
-
- setDeviceListGroup(preferenceGroup);
-
- addCachedDevices();
-
- 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;
- }
-
-
- if (mLocalAdapter.getBluetoothState() != BluetoothAdapter.STATE_ON) return;
-
-
- if (mFilter.matches(cachedDevice.getDevice())) {
- createDevicePreference(cachedDevice);
- }
- }
关于matches方法可以查看BluetoothDeviceFilter.java文件,不同的过滤器对应于不同的内部类,这些内部类实现了内部接口的matches方法,对BluetoothDevice的配对状态进行匹配,比如,过滤已经配对的蓝牙设备过滤器对应的内部类如下
-
- rivate static final class BondedDeviceFilter implements Filter {
- public boolean matches(BluetoothDevice device) {
- return device.getBondState() == BluetoothDevice.BOND_BONDED;
- }
- }
当对缓存列表进行过滤后,符合条件的就会调用createDevicePreference(cachedDevice)方法进行加载出来
- void createDevicePreference(CachedBluetoothDevice cachedDevice) {
-
- BluetoothDevicePreference preference = new BluetoothDevicePreference(
- getActivity(), cachedDevice);
-
- initDevicePreference(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>,蓝牙搜索附近可用设备
搜索功能流程如下:首先检测蓝牙是否开启,如果开启检测是否正在搜索,如果正在搜索则不做处理,如果未开启搜索则开启搜索
程序中的设置是如果蓝牙未开启或者正在搜索的话搜索设备按钮不可用。如果强制搜索是否正在播放音乐等,直接搜索。程序中设置的SCAN_EXPIRATION_MS为5分钟,有一种情况是搜索已经结束,但是时间没有5分钟,如果是非强制搜索在这种情况下将不开启搜索。
- void startScanning(boolean force) {
-
- if (!mAdapter.isDiscovering()) {
- if (!force) {
-
-
- if (mLastScan + SCAN_EXPIRATION_MS > System.currentTimeMillis()) {
- return;
- }
-
-
- 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;
-
- ScanningStateChangedHandler(boolean started) {
- mStarted = started;
- }
- public void onReceive(Context context, Intent intent,
- BluetoothDevice device) {
- synchronized (mCallbacks) {
- for (BluetoothCallback callback : mCallbacks) {
-
- callback.onScanningStateChanged(mStarted);
- }
- }
-
-
- mDeviceManager.onScanningStateChanged(mStarted);
-
- LocalBluetoothPreferences.persistDiscoveringTimestamp(context);
- }
- }
当扫描的过程中发现远程设备时处理如下
- private class DeviceFoundHandler implements Handler {
- public void onReceive(Context context, Intent intent,
- BluetoothDevice device) {
-
- short rssi = intent.getShortExtra(BluetoothDevice.EXTRA_RSSI, Short.MIN_VALUE);
-
- BluetoothClass btClass = intent.getParcelableExtra(BluetoothDevice.EXTRA_CLASS);
-
- String name = intent.getStringExtra(BluetoothDevice.EXTRA_NAME);
-
- CachedBluetoothDevice cachedDevice = mDeviceManager.findDevice(device);
- if (cachedDevice == null) {
-
- cachedDevice = mDeviceManager.addDevice(mLocalAdapter, mProfileManager, device);
-
-
- dispatchDeviceAdded(cachedDevice);
- }
-
- cachedDevice.setRssi(rssi);
- cachedDevice.setBtClass(btClass);
- cachedDevice.setName(name);
-
- cachedDevice.setVisible(true);
- }
- }
5>,蓝牙配对
设备列表中包括已配对设备、未配对设备、已连接设备等,当点击preference时会首先判断处于哪个状态,然后去进行下一个状态。如果没有配对,就进行配对
配对程序如下,在进行配对时首先检查远程设备是否正在配对,如果是,就返回true,如果没有在配对就现将本机的蓝牙配对状态设为true表示正在配对,紧接着停止蓝牙的扫描操作,与远程设备进行配对,配对成功后进行自动连接
-
- boolean startPairing() {
-
- if(mLocalAdapter.checkPairingState() == true)
- {
- return true;
- }
-
- mLocalAdapter.setPairingState(true);
-
-
- if (mLocalAdapter.isDiscovering()) {
- mLocalAdapter.cancelDiscovery();
- }
-
-
- if (!mDevice.createBond()) {
-
- mLocalAdapter.setPairingState(false);
- return false;
- }
-
- mConnectAfterPairing = true;
- return true;
- }
6>,蓝牙连接
在进行连接前首先判断是否已经配对了,如果没有配对就会进行配对,取消连接的操作,若已经配对了则进行设备连接
- void connect(boolean connectAllProfiles) {
-
- if (!ensurePaired()) {
- return;
- }
-
- mConnectAttempted = SystemClock.elapsedRealtime();
-
- connectWithoutResettingTimer(connectAllProfiles);
- }
接下来看一下connectWithoutResettingTimer(connectAllProfiles)方法的代码
- private void connectWithoutResettingTimer(boolean connectAllProfiles) {
-
-
-
- if (mProfiles.isEmpty()) {
- Log.d(TAG, "No profiles. Maybe we will connect later");
- return;
- }
-
-
-
- mIsConnectingErrorPossible = true;
-
- int preferredProfiles = 0;
- for (LocalBluetoothProfile profile : mProfiles) {
-
- if (connectAllProfiles ? profile.isConnectable() : profile.isAutoConnectable()) {
-
-
- if (profile.isPreferred(mDevice)) {
- ++preferredProfiles;
-
- 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:管理远程设备,配对、连接