Android系统 蓝牙模块

时间:2021-04-15 22:24:43



接着来介绍一下设置中某个模块的源码,本文依旧是基于Android4.42源码进行分析,分析一下蓝牙模块的实现。建议大致看一下关于Settings的剖析。


ZERO,蓝牙模块的fragment及其配置

     1>,首先由Settings_headers.xml文件可以知道,蓝牙对应的fragment为BluetoothSettings.Java,对应的id,icon,title,不再赘述,可自行查看xml文件即可


[java]  view plain  copy   Android系统 蓝牙模块 Android系统 蓝牙模块
  1. <!-- Bluetooth -->  
  2.     <header  
  3.         .......  
  4.         android:fragment="com.android.settings.bluetooth.BluetoothSettings"  
  5.         ......./>  
       2>,所涉及到的清单配置文件中的属性详解,清单文件中介绍了蓝牙界面启动相关的一些设置,诸如有快捷方式入口,以及是否隐藏进程等等,在这里大致对一些不常见的属性进行说明,方便查阅

[java]  view plain  copy   Android系统 蓝牙模块 Android系统 蓝牙模块
  1. <activity android:name="......"  
  2.                 android:uiOptions="splitActionBarWhenNarrow"  
  3.                 android:configChanges="orientation|keyboardHidden|screenSize"  
  4.                 android:label="@string/bluetooth_settings_title"  
  5.                 android:taskAffinity=""  
  6.                 android:excludeFromRecents="true">  
  7.             <intent-filter>  
  8.                ......  
  9.             </intent-filter>  
  10.             <meta-data android:name="com.android.settings.FRAGMENT_CLASS"  
  11.                 android:value="com.android.settings.bluetooth.BluetoothSettings" />  
  12.             <meta-data android:name="com.android.settings.TOP_LEVEL_HEADER_ID"  
  13.                 android:resource="@id/bluetooth_settings" />  
  14.         </activity>  
  15.   
  16.         <!-- Keep compatibility with old shortcuts. -->  
  17.         <activity-alias android:name=".bluetooth.BluetoothSettings"  
  18.                   
  19.                 android:label="@string/bluetooth_settings_title"  
  20.                 android:targetActivity="Settings$BluetoothSettingsActivity"  
  21.                 android:exported="true"  
  22.                 android:clearTaskOnLaunch="true">  
  23.             <meta-data android:name="com.android.settings.FRAGMENT_CLASS"  
  24.                 android:value="com.android.settings.bluetooth.BluetoothSettings" />  
  25.             <meta-data android:name="com.android.settings.TOP_LEVEL_HEADER_ID"  
  26.                 android:resource="@id/bluetooth_settings" />  
  27.         </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,蓝牙布局实现

[java]  view plain  copy   Android系统 蓝牙模块 Android系统 蓝牙模块
  1. public final class BluetoothSettings extends DeviceListPreferenceFragment {  
  2.   
  3.   
  4. .............  
  5.         @Override  
  6.     public View onCreateView(LayoutInflater inflater, ViewGroup container,  
  7.                 Bundle savedInstanceState) {  
  8.         addPreferencesFromResource(R.xml.bluetooth_settings);  
  9.       }  
  10.   
  11. ............  
  12. }  

      1>,可以看出BluetoothSettings属于PreferenceFragment,所要加载的布局文件为Bluetooth_settings.xml文件。以下是布局文件代码,总共四行,节点为PreferenceScreen,代表显示整个屏幕,内部可嵌套不同类型的标签,在这里内部未有任何标签,是在代码中动态添加的不同种类的布局。

[java]  view plain  copy   Android系统 蓝牙模块 Android系统 蓝牙模块
  1. <PreferenceScreen  
  2.     xmlns:android="http://schemas.android.com/apk/res/android"  
  3.     android:title="@string/bluetooth_settings" >  
  4. </PreferenceScreen>  
    

    2>,展示两张蓝牙开启和关闭时布局示意图


       Android系统 蓝牙模块                        Android系统 蓝牙模块


  • 圈1:ActionBar顶部导航栏,显示title,以及蓝牙开关,开关的添加代码在addPreferencesForActivity方法中,

[java]  view plain  copy   Android系统 蓝牙模块 Android系统 蓝牙模块
  1. @Override  
  2.     void addPreferencesForActivity() {  
  3.         Activity activity = getActivity();  
  4.   
  5.         //创建蓝牙开关控件  
  6.         Switch actionBarSwitch = new Switch(activity);  
  7.   
  8.         if (activity instanceof PreferenceActivity) {  
  9.             PreferenceActivity preferenceActivity = (PreferenceActivity) activity;  
  10.             if (preferenceActivity.onIsHidingHeaders() || !preferenceActivity.onIsMultiPane()) {  
  11.                 final int padding = activity.getResources().getDimensionPixelSize(  
  12.                         R.dimen.action_bar_switch_padding);  
  13.                 actionBarSwitch.setPaddingRelative(00, padding, 0);  
  14.                 //用来进行顶部导航栏的布局,顶部导航栏左边显示图标和title  
  15.                activity.getActionBar().setDisplayOptions(ActionBar.DISPLAY_SHOW_CUSTOM,  
  16.                         ActionBar.DISPLAY_SHOW_CUSTOM);  
  17.                 //顶部导航栏右边显示开关,控件宽高自适应,垂直居中  
  18.                activity.getActionBar().setCustomView(actionBarSwitch, new ActionBar.LayoutParams(  
  19.                         ActionBar.LayoutParams.WRAP_CONTENT,  
  20.                         ActionBar.LayoutParams.WRAP_CONTENT,  
  21.                         Gravity.CENTER_VERTICAL | Gravity.END));  
  22.             }  
  23.         }  
  24.          //将开关控件传给BluetoothEnabler.用来更新蓝牙的开关状态  
  25.         mBluetoothEnabler = new BluetoothEnabler(activity, actionBarSwitch);  
  26.          //告知options menu ,fragment要添加菜单项  
  27.         setHasOptionsMenu(true);  
  28.     }  


  • 圈2:ActionBar底部栏,可进行蓝牙设备的搜索,检测时间,已配对设备列表等一些除了配对之外的设置,Actionbar的相关布局在onCreateOptionsMenu方法中,利用如下代码可自定义actionbar

[java]  view plain  copy   Android系统 蓝牙模块 Android系统 蓝牙模块
  1. menu.add(groupId, itemId, order, title)  
  2. nbsp;.setEnabled(enabled)  
  3.  .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

[java]  view plain  copy   Android系统 蓝牙模块 Android系统 蓝牙模块
  1. getListView().setEmptyView(mEmptyView);  

  • 圈4:本机蓝牙设备的相关设置,包括本机蓝牙名称,蓝牙对附近可用设备的可见性,蓝牙对已经配对设备的可见性,当检测到蓝牙开启时会添加一个本机蓝牙信息的Preference,在方法updateContent中完成添加或者移除,添加代码如下:

[java]  view plain  copy   Android系统 蓝牙模块 Android系统 蓝牙模块
  1. if (mMyDevicePreference == null) {  
  2.               //创建一个Preference  
  3.               mMyDevicePreference = new Preference(getActivity());  
  4.                }  
  5.                 //下面代码用来设置Preference的标题,图标,是否可点击  
  6.              mMyDevicePreference.setTitle(mLocalAdapter.getName());  
  7.               
  8.                if (getResources().getBoolean(com.android.internal.R.bool.config_voice_capable)) {  
  9.                  //如果是手机则显示手机的图标  
  10.                    mMyDevicePreference.setIcon(R.drawable.ic_bt_cellphone);    // for phones  
  11.                } else {  
  12.                 //如果不是手机诸如笔记本电脑,带有蓝牙模块的单片机等,则显示电脑的图标  
  13.                  mMyDevicePreference.setIcon(R.drawable.ic_bt_laptop);   // for tablets, etc.  
  14.                }  
  15.                //是否将该Preference的值写入SharedPreference文件中,true代表写入  
  16.                mMyDevicePreference.setPersistent(false);  
  17.                //是否可点击  
  18.               mMyDevicePreference.setEnabled(true);  
  19.              //添加一个Preference  
  20.               preferenceScreen.addPreference(mMyDevicePreference);  

                 

                    preferencescreen添加或者移除的代码如下:

[java]  view plain  copy   Android系统 蓝牙模块 Android系统 蓝牙模块
  1. //添加一个种类的Preference  
  2. getPreferenceScreen().addPreference(mAvailableDevicesCategory);  
  3. .,....  
  4. //移除一个Preference  
  5. preferenceScreen.removePreference(mPairedDevicesCategory);  
  6. .....  
  7. //移除所有  
  8. 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布局

[java]  view plain  copy   Android系统 蓝牙模块 Android系统 蓝牙模块
  1. @Override  
  2.     public View onCreateView(LayoutInflater inflater, ViewGroup container,  
  3.                 Bundle savedInstanceState) {  
  4.         addPreferencesFromResource(R.xml.bluetooth_settings);  
  5.         View v = inflater.inflate(R.layout.add_preference_list_fragment,null);  
  6.         mEmptyView = (TextView) v.findViewById(R.id.add_empty);  
  7. }  


4>,initDevicePreference()方法,获取到已经配对的蓝牙设备,设置监听事件

[java]  view plain  copy   Android系统 蓝牙模块 Android系统 蓝牙模块
  1. @Override  
  2.     void initDevicePreference(BluetoothDevicePreference preference) {  
  3.         CachedBluetoothDevice cachedDevice = preference.getCachedDevice();  
  4.         if (cachedDevice.getBondState() == BluetoothDevice.BOND_BONDED) {  
  5.             // Only paired device have an associated advanced settings screen  
  6.            //如果设备已经配对,则添加监听事件  
  7.            preference.setOnSettingsClickListener(mDeviceProfilesListener);  
  8.         }  
  9.     }  

5>,onDevicePreferenceClick()方法,远程蓝牙设备的点击事件

[java]  view plain  copy   Android系统 蓝牙模块 Android系统 蓝牙模块
  1. @Override  
  2.    void onDevicePreferenceClick(BluetoothDevicePreference btPreference) {  
  3.       //停止扫描   
  4.      mLocalAdapter.stopScanning();  
  5.       //调用父类的点击事件  
  6.       super.onDevicePreferenceClick(btPreference);  
  7.    }  

6>,onBluetoothStateChanged()方法,蓝牙开关状态改变时监听

7>,onScanningStateChanged()方法,监听扫描可用蓝牙设备时扫描的状态改变,开启扫描,正在扫描,扫描结束,并更新进度条


THREE,蓝牙功能实现流程

功能模块这块儿主要分析一下实现的流程,代码为辅,若在看源码时代码有什么问题,可在博文下咨询

1>,蓝牙开关switch相关,

蓝牙开关涉及到本地蓝牙状态的更改以及用户点击switch更改蓝牙状态,当本地蓝牙状态发生改变时需要更新switch的状态,当switch的状态发生改变时需要更新本地的蓝牙状态。这就涉及到了,注册广播监听本地蓝牙状态,为switch注册监听器监听switch的更改,以及对switch状态进行设置的方法。

首先执行addPreferencesForActivity加载switch,在该方法中构造BluetoothEnabler对象,对switch的状态进行初始化以及状态改变的监听。

接下来对BluetoothEnabler进行分析,先看一下BluetoothEnabler的构造方法

[java]  view plain  copy   Android系统 蓝牙模块 Android系统 蓝牙模块
  1. public BluetoothEnabler(Context context, Switch switch_) {  
  2.        mContext = context;  
  3.        mSwitch = switch_;  
  4.        mValidListener = false;  
  5.         
  6.      //首先判断是否支持蓝牙  
  7.      LocalBluetoothManager manager = LocalBluetoothManager.getInstance(context);  
  8.        if (manager == null) {  
  9.            // Bluetooth is not supported不支持蓝牙此时蓝牙本地适配器为null,开关状态为未选中  
  10.          mLocalAdapter = null;  
  11.            mSwitch.setEnabled(false);  
  12.        } else {  
  13.            //如果支持蓝牙获取到本地蓝牙适配器adapter,  
  14.           mLocalAdapter = manager.getBluetoothAdapter();  
  15.        }  
  16.           //蓝牙开关状态的过滤器  
  17.         mIntentFilter = new IntentFilter(BluetoothAdapter.ACTION_STATE_CHANGED);  
  18.       
  19.    }  
紧接着在resume()中进行蓝牙开关状态的设置

[java]  view plain  copy   Android系统 蓝牙模块 Android系统 蓝牙模块
  1. public void resume() {  
  2.         if (mLocalAdapter == null) {  
  3.             //如果蓝牙适配器为空,则将resume方法返回,不进行resume方法中的剩余操作  
  4.            mSwitch.setEnabled(false);  
  5.             return;  
  6.         }  
  7.   
  8.         // Bluetooth state is not sticky, so set it manually  
  9.        //必须手动的去监听蓝牙状态的改变  
  10.         //根据本地蓝牙适配器获取到此时蓝牙的状态,对switch进行设置  
  11.        handleStateChanged(mLocalAdapter.getBluetoothState());  
  12.            
  13.         //注册广播监听蓝牙状态的改变  
  14.         mContext.registerReceiver(mReceiver, mIntentFilter);  
  15.         //为switch设置监听事件  
  16.        mSwitch.setOnCheckedChangeListener(this);  
  17.         mValidListener = true;  
  18.     }  

在resume方法中做了三件事,

i>,根据本地蓝牙适配器获取到此时的蓝牙状态对switch进行设置handleStateChanged(state)方法代码很简单,不再赘述

ii>,注册广播监听蓝牙状态-----当系统蓝牙状态发生改变时需要更新switch状态,广播接收器中的代码如下

[java]  view plain  copy   Android系统 蓝牙模块 Android系统 蓝牙模块
  1. private final BroadcastReceiver mReceiver = new BroadcastReceiver() {  
  2.         @Override  
  3.         public void onReceive(Context context, Intent intent) {  
  4.             // Broadcast receiver is always running on the UI thread here,  
  5.             // so we don't need consider thread synchronization.  
  6.             int state = intent.getIntExtra(BluetoothAdapter.EXTRA_STATE, BluetoothAdapter.ERROR);  
  7.              //针对不同的蓝牙状态对switch进行设置  
  8.             handleStateChanged(state);  
  9.         }  
  10.     };  

iii>,为switch设置监听事件,当switch发生改变时,需要对系统的蓝牙状态进行行改变。系统的蓝牙开关状态发生改变时,会发送状态改变的广播,对switch进行更改

[java]  view plain  copy   Android系统 蓝牙模块 Android系统 蓝牙模块
  1. public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {  
  2.   
  3.        // shouldn't setBluetoothEnabled(true) in airplane mode.  
  4.      //飞行模式下蓝牙不可用  
  5.     if (mLocalAdapter != null) {  
  6.            if (isChecked && WifiSettings.needPrompt(mContext)) {  
  7.                return;  
  8.            }  
  9.          //当switch开关状态发生改变时,对系统本地蓝牙状态进行设置  
  10.          mLocalAdapter.setBluetoothEnabled(isChecked);  
  11.        }  
  12.    //当switch状态进行改变时,让其不可点击  
  13.     mSwitch.setEnabled(false);  
  14.    }  

接下来看看对本地蓝牙适配器更改的方法

[java]  view plain  copy   Android系统 蓝牙模块 Android系统 蓝牙模块
  1. public void setBluetoothEnabled(boolean enabled) {  
  2.        //根据switch的enable来开启或者关闭蓝牙,success返回执行结果  
  3.         boolean success = enabled  
  4.                 ? mAdapter.enable()  
  5.                 : mAdapter.disable();  
  6.         isPairing = false;  
  7.         if (success) {  
  8.            //如果系统蓝牙开启或者关闭操作成功,将状态更新  
  9.             setBluetoothStateInt(enabled  
  10.                 ? BluetoothAdapter.STATE_TURNING_ON  
  11.                 : BluetoothAdapter.STATE_TURNING_OFF);  
  12.         } else {  
  13.            //如果系统蓝牙没有开启或者关闭成功,则将蓝牙状态进行更新保存为当前系统蓝牙的状态  
  14.             syncBluetoothState();  
  15.         }  
  16.     }  
BluetoothAdapter的enable方法用于开启蓝牙,disable用于关闭蓝牙

2>,本机蓝牙设置,包括可检测性、蓝牙名称、可检测时间。

i>,加载本机蓝牙相关信息

在updateContent方法中进行动态的添加preference(单一控件,类似checkbox)或者preferencecategory(组合控件,类似linearlayout)。本机蓝牙的信息添加的是一个preference

[java]  view plain  copy   Android系统 蓝牙模块 Android系统 蓝牙模块
  1. if (mMyDevicePreference == null) {  
  2.                     mMyDevicePreference = new Preference(getActivity());  
  3.                 }  
  4.                //设置preference的title标题,显示的是蓝牙名称  
  5.                mMyDevicePreference.setTitle(mLocalAdapter.getName());  
  6.         //如果是手机,图标设置为手机的图标,如果是平板电脑或其他则设置为电脑图标         
  7.                 if (getResources().getBoolean(com.android.internal.R.bool.config_voice_capable)) {  
  8.                     mMyDevicePreference.setIcon(R.drawable.ic_bt_cellphone);    // for phones  
  9.                 } else {  
  10.                     mMyDevicePreference.setIcon(R.drawable.ic_bt_laptop);   // for tablets, etc.  
  11.                 }  
  12.                 //是否将该preference的信息保存在sharedPreference中  
  13.                mMyDevicePreference.setPersistent(false);  
  14.                 //设置preference可点击  
  15.                mMyDevicePreference.setEnabled(true);  
  16.                 //添加mMyDevicePreference  
  17.                 preferenceScreen.addPreference(mMyDevicePreference);  
  18.   
  19.                 if (!isRestrictedAndNotPinProtected()) {  
  20.                     if (mDiscoverableEnabler == null) {  
  21.                         mDiscoverableEnabler = new BluetoothDiscoverableEnabler(getActivity(),  
  22.                                 mLocalAdapter, mMyDevicePreference);  
  23.                         //进行蓝牙可检测性的设置  
  24.                          mDiscoverableEnabler.resume();  
  25.                         LocalBluetoothManager.getInstance(getActivity()).setDiscoverableEnabler(  
  26.                                 mDiscoverableEnabler);  
  27.                     }  
  28.                 }  
  29.                   <pre name="code" class="java">                // Paired devices category  
  30.                 //加载已经配对的设备列表  
  31.               if (mPairedDevicesCategory == null) {  
  32.                     mPairedDevicesCategory = new PreferenceCategory(getActivity());  
  33.                 } else {  
  34.                     mPairedDevicesCategory.removeAll();  
  35.                 }  
  36.                 addDeviceCategory(mPairedDevicesCategory,  
  37.                         R.string.bluetooth_preference_paired_devices,  
  38.                         BluetoothDeviceFilter.BONDED_DEVICE_FILTER);  
  39.                 //获取到已经配对的设备的数量,关系到mMyDevicePreference的summary显示的文本  
  40.                int numberOfPairedDevices = mPairedDevicesCategory.getPreferenceCount();  
  41.                 if (mDiscoverableEnabler != null) {  
  42.                          //根据已配对的数量对显示的summary进行处理  
  43.                          mDiscoverableEnabler.setNumberOfPairedDevices(numberOfPairedDevices);  
  44.                 }  
 

ii>,修改蓝牙名称

修改蓝牙名称的按钮在菜单栏中id为MENU_ID_RENAME_DEVICE,过程是修改后将蓝牙名称赋给系统的蓝牙适配器,系统蓝牙适配发送广播通知蓝牙名称已经修改,在接受到蓝牙名称修改后的广播后更新preference的title。代码流程如下

[java]  view plain  copy   Android系统 蓝牙模块 Android系统 蓝牙模块
  1.  public boolean onOptionsItemSelected(MenuItem item) {  
  2.           ........  
  3.             case MENU_ID_RENAME_DEVICE:  
  4.               //弹出修改的对话框,在点击确定时会调用修改蓝牙名称的方法  
  5.                
  6.                 new BluetoothNameDialogFragment().show(  
  7.                         getFragmentManager(), "rename device");  
  8.                 return true;  
  9.                 ......  
  10. }  

当蓝牙名称发生变化后,会发送广播通知蓝牙名称已变,对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方法中首先需要注册广播监听本地蓝牙扫描模式的改变

[java]  view plain  copy   Android系统 蓝牙模块 Android系统 蓝牙模块
  1. private final BroadcastReceiver mReceiver = new BroadcastReceiver() {  
  2.        @Override  
  3.        public void onReceive(Context context, Intent intent) {  
  4.            //监听蓝牙的扫描模式的改变  
  5.             if (BluetoothAdapter.ACTION_SCAN_MODE_CHANGED.equals(intent.getAction())) {  
  6.                int mode = intent.getIntExtra(BluetoothAdapter.EXTRA_SCAN_MODE,  
  7.                        BluetoothAdapter.ERROR);  
  8.                if (mode != BluetoothAdapter.ERROR) {  
  9.                  //如果扫描模式发生了改变且没有发生错误,就去更新副标题  
  10.                   handleModeChanged(mode);  
  11.                }  
  12.            }  
  13.        }  
  14.    };  

更新副标题的方法如下,因为分三种模式,所以副标题也有三种情况

[java]  view plain  copy   Android系统 蓝牙模块 Android系统 蓝牙模块
  1. void handleModeChanged(int mode) {  
  2.         //对附近所有设备可见,且可扫描  
  3.         if (mode == BluetoothAdapter.SCAN_MODE_CONNECTABLE_DISCOVERABLE) {  
  4.             //将标志位置为true,与该preference的点击事件有关  
  5.             mDiscoverable = true;  
  6.           //根据时间更新副标题,此时副标题显示的是对附近所有设备可见以及可见时长  
  7.            updateCountdownSummary();  
  8.         } else {  
  9.              
  10.           mDiscoverable = false;  
  11.             //更新副标题,如果已配对设备列表为空,则为对所有设备不可见,如果已配对设备列表不为空,则为对已配对设备可见  
  12.             setSummaryNotDiscoverable();  
  13.         }  
  14.     }  

然后为preference添加一个点击事件,当点击preference时将标志位取反,并且更新preference的summary以及蓝牙的扫描模式

[java]  view plain  copy   Android系统 蓝牙模块 Android系统 蓝牙模块
  1. public boolean onPreferenceClick(Preference preference) {  
  2.         mDiscoverable = !mDiscoverable;  
  3.         setEnabled(mDiscoverable);  
  4.         return true;  
  5.     }  

在更新summary的时候涉及到对可检测性时间的更新,说一下实现逻辑不贴代码了,有需要的再问吧

首先明确可检测性事件,然后在开启限时的可检测性后再更新summary的方法中开启一个线程,该线程中再次调用该更新summary的方法,在更新summary中的方法中会对时间进行判断,如果时间结束了,就退出该方法。



THREE,蓝牙模块功能实现

switch的分析以及本机蓝牙重命名和可见性的分析见上一篇,接下来进行第三章第三部分的介绍:关于蓝牙远程设备列表的加载。如果没有看过,建议看看上一篇关第一章蓝牙的布局,有助于理解

3>,设备列表的加载

因为这部分代码很多,所以在介绍时先说一下思路,程序首先通过底层的BluetoothAdapter的getBondedDevices()方法获取到已配对的设备列表,获取到列表后将数据缓存在List<CachedBluetoothDevice>中进行备份,当蓝牙界面启动后会从缓存中读取数据并显示已配对设备列表mPairedDevicesCategory,在扫描附近可用设备时会对缓存中的数据进行增加或者删除,并将数据显示在可用设备列表mAvailableDevicesCategory,并且程序会实时监听远程设备的状态变化,进行对设备列表的增加或删除。设备列表的加载基本上就是这些,接下来挨个介绍

i>,调用底层代码获取可用设备列表并进行缓存

这部分代码的书写在BluetoothEventManager.Java文件中,获取已配对设备列表的代码定义如下,

[java]  view plain  copy   Android系统 蓝牙模块 Android系统 蓝牙模块
  1. boolean readPairedDevices() {  
  2.        //mLocalAdapter是将BluetoothAdapter映射到本地,其内部代码不再书写,获取到已配对设备  
  3.        Set<BluetoothDevice> bondedDevices = mLocalAdapter.getBondedDevices();  
  4.         if (bondedDevices == null) {  
  5.             return false;  
  6.         }  
  7.         boolean deviceAdded = false;  
  8.         for (BluetoothDevice device : bondedDevices) {  
  9.             //这一步调用的是设备缓存列表的管理类CachedBluetoothDeviceManager中的方法findDevice  
  10.             //用于检查缓存列表中是否已经存在该device,若存在就将device返回,若不存在就返回null  
  11.             CachedBluetoothDevice cachedDevice = mDeviceManager.findDevice(device);  
  12.             if (cachedDevice == null) {  
  13.                 //如果缓存列表中没有该设备就调用管理类CachedBluetoothDeviceManager中的addDevice  
  14.                 //将设备添加到缓存列表中  
  15.                cachedDevice = mDeviceManager.addDevice(mLocalAdapter, mProfileManager, device);  
  16.               //将设备更新到屏幕上  
  17.                dispatchDeviceAdded(cachedDevice);  
  18.                 deviceAdded = true;  
  19.             }  
  20.         }  
  21.         return deviceAdded;  
  22.     }  

该方法在两个地方调用,一个是当本地蓝牙BluetoothAdapter开启后调用,一个就是当远程设备BluetoothDevice的状态发生改变时调用

如下,是在LocalBluetoothProfileManager.java文件中的代码,在蓝牙开启后会调用如下代码读取已配对的设备

[java]  view plain  copy   Android系统 蓝牙模块 Android系统 蓝牙模块
  1. void setBluetoothStateOn() {  
  2.        ParcelUuid[] uuids = mLocalAdapter.getUuids();  
  3.        if (uuids != null) {  
  4.       
  5.            updateLocalProfiles(uuids);  
  6.        }  
  7.        mEventManager.readPairedDevices();  
  8.    }  
当远程设备发生改变时会发送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


[java]  view plain  copy   Android系统 蓝牙模块 Android系统 蓝牙模块
  1. private void addDeviceCategory(PreferenceGroup preferenceGroup, int titleId,  
  2.             BluetoothDeviceFilter.Filter filter) {  
  3.         //设置preferenceGroup的标题  
  4.        preferenceGroup.setTitle(titleId);  
  5.       //为PreferenceScreen添加preferenceGroup,注意此时preferenceGroup里为空没有任何的preference  
  6.       getPreferenceScreen().addPreference(preferenceGroup);  
  7.         //设置过滤器,调用的是DeviceListPreferenceFragment中方法  
  8.         setFilter(filter);  
  9.        //调用DeviceListPreferencFragment中的方法,讲preferenceGroup传过去,方便对其操作  
  10.         setDeviceListGroup(preferenceGroup);  
  11.         //调用DeviceListPreferenceFragment中的方法  
  12.         addCachedDevices();  
  13.        //将preference设置为可点击的状态  
  14.         preferenceGroup.setEnabled(true);  
  15.     }  

addCachedDevices()代码如下

[java]  view plain  copy   Android系统 蓝牙模块 Android系统 蓝牙模块
  1. void addCachedDevices() {  
  2.      //用于获取到缓存列表的复制  
  3.      Collection<CachedBluetoothDevice> cachedDevices =  
  4.                 mLocalManager.getCachedDeviceManager().getCachedDevicesCopy();  
  5.         for (CachedBluetoothDevice cachedDevice : cachedDevices) {  
  6.             //该方法用于将设备显示出来  
  7.            onDeviceAdded(cachedDevice);  
  8.         }  
  9.     }  


onDeviceAdded(cachedDevice)代码如下

[java]  view plain  copy   Android系统 蓝牙模块 Android系统 蓝牙模块
  1. public void onDeviceAdded(CachedBluetoothDevice cachedDevice) {  
  2.         if (mDevicePreferenceMap.get(cachedDevice) != null) {  
  3.             return;  
  4.         }  
  5.   
  6.         // Prevent updates while the list shows one of the state messages  
  7.         if (mLocalAdapter.getBluetoothState() != BluetoothAdapter.STATE_ON) return;  
  8.           //这就是过滤器的作用了,首先过滤出要求的设备,要求已配对或者是附近可用设备  
  9.           //列表过滤后,就可以加载出来了  
  10.         if (mFilter.matches(cachedDevice.getDevice())) {  
  11.             createDevicePreference(cachedDevice);  
  12.         }  
  13.      }  

关于matches方法可以查看BluetoothDeviceFilter.java文件,不同的过滤器对应于不同的内部类,这些内部类实现了内部接口的matches方法,对BluetoothDevice的配对状态进行匹配,比如,过滤已经配对的蓝牙设备过滤器对应的内部类如下

[java]  view plain  copy   Android系统 蓝牙模块 Android系统 蓝牙模块
  1. //注,Filter为BluetoothDeviceFilter的内部接口  
  2. rivate static final class BondedDeviceFilter implements Filter {  
  3.        public boolean matches(BluetoothDevice device) {  
  4.            return device.getBondState() == BluetoothDevice.BOND_BONDED;  
  5.        }  
  6.    }  

当对缓存列表进行过滤后,符合条件的就会调用createDevicePreference(cachedDevice)方法进行加载出来

[java]  view plain  copy   Android系统 蓝牙模块 Android系统 蓝牙模块
  1. void createDevicePreference(CachedBluetoothDevice cachedDevice) {  
  2.        //构造preference对象  
  3.      BluetoothDevicePreference preference = new BluetoothDevicePreference(  
  4.                getActivity(), cachedDevice);  
  5.         //在该方法对preference进行初始化,可按需实现  
  6.        initDevicePreference(preference);  
  7.        //将preference显示出来  
  8.       mDeviceListGroup.addPreference(preference);  
  9.        mDevicePreferenceMap.put(cachedDevice, preference);  
  10.    }  


设备列表的加载就到这儿,总结一下就是,对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分钟,如果是非强制搜索在这种情况下将不开启搜索。

[java]  view plain  copy   Android系统 蓝牙模块 Android系统 蓝牙模块
  1. void startScanning(boolean force) {  
  2.         // Only start if we're not already scanning  
  3.         if (!mAdapter.isDiscovering()) {  
  4.             if (!force) {  
  5.                 // Don't scan more than frequently than SCAN_EXPIRATION_MS,  
  6.                 // unless forced  
  7.                 if (mLastScan + SCAN_EXPIRATION_MS > System.currentTimeMillis()) {  
  8.                     return;  
  9.                 }  
  10.   
  11.                 // If we are playing music, don't scan unless forced.  
  12.                 A2dpProfile a2dp = mProfileManager.getA2dpProfile();  
  13.                 if (a2dp != null && a2dp.isA2dpPlaying()) {  
  14.                     return;  
  15.                 }  
  16.             }  
  17.   
  18.             if (mAdapter.startDiscovery()) {  
  19.                 mLastScan = System.currentTimeMillis();  
  20.             }  
  21.         }  
  22.     }  
在搜索过程中发现设备会发送广播,程序会在广播处理代码中对缓存列表以及显示列表进行更新。

当开始扫描时发送扫描开始的广播,handler进行处理,当扫描接触时也是下列handler进行处理,只是started为false

[java]  view plain  copy   Android系统 蓝牙模块 Android系统 蓝牙模块
  1. private class ScanningStateChangedHandler implements Handler {  
  2.        private final boolean mStarted;  
  3.       //开始扫描时传入的为true  
  4.        ScanningStateChangedHandler(boolean started) {  
  5.            mStarted = started;  
  6.        }  
  7.        public void onReceive(Context context, Intent intent,  
  8.                BluetoothDevice device) {  
  9.            synchronized (mCallbacks) {  
  10.                for (BluetoothCallback callback : mCallbacks) {  
  11.                         //调用DeviceListPreferenceFragment.java中的方法显示扫描指示progress  
  12.                        callback.onScanningStateChanged(mStarted);  
  13.                }  
  14.            }  
  15.            //首先更新缓存列表,然后对显示列表进行排序更新显示。  
  16.            //排序规则代码在CachedBluetoothDevice.java中  
  17.          mDeviceManager.onScanningStateChanged(mStarted);  
  18.           //该方法用于保存开始扫描的时间  
  19.             LocalBluetoothPreferences.persistDiscoveringTimestamp(context);  
  20.        }  
  21.    }  


当扫描的过程中发现远程设备时处理如下

[java]  view plain  copy   Android系统 蓝牙模块 Android系统 蓝牙模块
  1. private class DeviceFoundHandler implements Handler {  
  2.        public void onReceive(Context context, Intent intent,  
  3.                BluetoothDevice device) {  
  4.            //获取到蓝牙的信号强度,默认为Short类型的最小值-2的15次方  
  5.            short rssi = intent.getShortExtra(BluetoothDevice.EXTRA_RSSI, Short.MIN_VALUE);  
  6.            //获取到远程设备的类型  
  7.             BluetoothClass btClass = intent.getParcelableExtra(BluetoothDevice.EXTRA_CLASS);  
  8.           //获取到远程设备的name  
  9.            String name = intent.getStringExtra(BluetoothDevice.EXTRA_NAME);  
  10.            //获取到远程设备后检测是否在缓存列表中,若有就返回设备,若没有返回null  
  11.            CachedBluetoothDevice cachedDevice = mDeviceManager.findDevice(device);  
  12.            if (cachedDevice == null) {  
  13.               //将设备添加到缓存列表中  
  14.               cachedDevice = mDeviceManager.addDevice(mLocalAdapter, mProfileManager, device);  
  15.                // callback to UI to create Preference for new device  
  16.               //将添加的设备更新到显示列表  
  17.              dispatchDeviceAdded(cachedDevice);  
  18.            }  
  19.            //缓存device的信号强度,设备类型,name  
  20.            cachedDevice.setRssi(rssi);  
  21.            cachedDevice.setBtClass(btClass);  
  22.            cachedDevice.setName(name);  
  23.            //在这里不是设置可见性,与列表的排序相关  
  24.           cachedDevice.setVisible(true);  
  25.        }  
  26.    }  


5>,蓝牙配对

设备列表中包括已配对设备、未配对设备、已连接设备等,当点击preference时会首先判断处于哪个状态,然后去进行下一个状态。如果没有配对,就进行配对

配对程序如下,在进行配对时首先检查远程设备是否正在配对,如果是,就返回true,如果没有在配对就现将本机的蓝牙配对状态设为true表示正在配对,紧接着停止蓝牙的扫描操作,与远程设备进行配对,配对成功后进行自动连接

[java]  view plain  copy   Android系统 蓝牙模块 Android系统 蓝牙模块
  1. //该方法返回true代表正在进行配对操作,若返回false则表示配对操作失败弹出失败弹窗  
  2.  boolean startPairing() {  
  3.           //首先查看一下,远程设备是否正在配对,如果正在配对就返回true,  
  4.           if(mLocalAdapter.checkPairingState() == true)  
  5.         {  
  6.             return true;  
  7.         }  
  8.           //将本机蓝牙适配器的配对状态设为true  
  9.         mLocalAdapter.setPairingState(true);  
  10.         // Pairing is unreliable while scanning, so cancel discovery  
  11.         //如果本机蓝牙正在进行扫描蓝牙的操作,则停止该操作,因为该操作会阻塞  
  12.        if (mLocalAdapter.isDiscovering()) {  
  13.             mLocalAdapter.cancelDiscovery();  
  14.         }  
  15.         //调用framework层的方法,判断远程蓝牙设备是否可以配对以及请求配对是否超时,  
  16.        //如果可以配对就把远程蓝牙设备的配对状态设置为正在配对  
  17.       if (!mDevice.createBond()) {  
  18.            //如果与远程蓝牙设备创建配对失败则将本机蓝牙配对状态设为false  
  19.            mLocalAdapter.setPairingState(false);  
  20.             return false;  
  21.         }  
  22.        //配对之后是否进行自动连接,true为自动进行连接  
  23.        mConnectAfterPairing = true;  // auto-connect after pairing  
  24.         return true;  
  25.     }  


6>,蓝牙连接

在进行连接前首先判断是否已经配对了,如果没有配对就会进行配对,取消连接的操作,若已经配对了则进行设备连接

[java]  view plain  copy   Android系统 蓝牙模块 Android系统 蓝牙模块
  1. void connect(boolean connectAllProfiles) {  
  2.        //如果没有配对,就进行配对,并且退出连接的方法  
  3.       if (!ensurePaired()) {  
  4.            return;  
  5.        }  
  6. //获取到系统启动到现在的时间间隔  
  7.        mConnectAttempted = SystemClock.elapsedRealtime();  
  8.       //从英语中可以看出意思是在连接时不重置定时器  
  9.        connectWithoutResettingTimer(connectAllProfiles);  
  10.    }  


接下来看一下connectWithoutResettingTimer(connectAllProfiles)方法的代码

[java]  view plain  copy   Android系统 蓝牙模块 Android系统 蓝牙模块
  1. private void connectWithoutResettingTimer(boolean connectAllProfiles) {  
  2.         // Try to initialize the profiles if they were not.  
  3.         //本机蓝牙与远程设备通信的配置规范,如果没有配置文件则不能进行通信  
  4.         //配置规范指定所使用的蓝牙通信协议,用户界面格式等等  
  5.         if (mProfiles.isEmpty()) {  
  6.             Log.d(TAG, "No profiles. Maybe we will connect later");   
  7.             return;  
  8.         }  
  9.   
  10.         // Reset the only-show-one-error-dialog tracking variable  
  11.         //当我们去连接多个设备发生错误时我们只想显示一个错误对话框,  
  12.        mIsConnectingErrorPossible = true;  
  13.       
  14.         int preferredProfiles = 0;  
  15.         for (LocalBluetoothProfile profile : mProfiles) {  
  16.           
  17.             if (connectAllProfiles ? profile.isConnectable() : profile.isAutoConnectable()) {  
  18.                   
  19.   
  20.                 if (profile.isPreferred(mDevice)) {  
  21.                     ++preferredProfiles;  
  22.             //连接设备,具体的可以查看关于profile的内容    
  23.                     connectInt(profile);  
  24.                       
  25.                 }  
  26.             }  
  27.         }  
  28.         if (DEBUG) Log.d(TAG, "Preferred profiles = " + preferredProfiles);  
  29.   
  30.         if (preferredProfiles == 0) {  
  31.             connectAutoConnectableProfiles();  
  32.         }  
  33.     }  


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