接着上一篇 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>,藍牙搜尋附近可用裝置
搜尋功能流程如下:首先檢測藍牙是否開啟,如果開啟檢測是否正在搜尋,如果正在搜尋則不做處理,如果未開啟搜尋則開啟搜尋
程式中的設定是如果藍牙未開啟或者正在搜尋的話搜尋裝置按鈕不可用。如果強制搜尋是否正在播放音樂等,直接搜尋。程式中設定的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:管理遠端裝置,配對、連接配接