Android4.42-Settings源代码分析之蓝牙模块Bluetooth(上)
繼上一篇 Android系統源代碼剖析(一)---Settings
接著來介紹一下設置中某個模塊的源代碼。本文依然是基于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" //由快捷方式進入所啟動的activityandroid: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>。展示兩張藍牙開啟和關閉時布局示意圖
<img src="http://img.blog.csdn.net/20160319103542719?
watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQv/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/Center" alt="" />
圈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);
//用來進行頂部導航欄的布局,頂部導航欄左邊顯示圖標和title
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));
}
}
//將開關控件傳給BluetoothEnabler.用來更新藍牙的開關狀態
mBluetoothEnabler = new BluetoothEnabler(activity, actionBarSwitch);
//告知options menu ,fragment要加入菜單項
setHasOptionsMenu(true);
}
那么開關控件的初始狀態是怎樣獲取的呢??進入到BluetoothEnabler.java類中能夠發現。在該類的resume方法中對該switch有一個設置
當中handleStateChanged方法就是傳入當前藍牙的狀態,并對開關的狀態進行設置。
在手機恢復出廠設置后能夠看到開關狀態的默認值,該默認值相應的是def_bluetooth_on,在開機過程中會將該默認值相應的boolean值通過藍牙服務BluetoothManagerService保存起來。并通過本地的藍牙適配器獲取到當前藍牙狀態傳給switch開關。
所以假設你想改動藍牙默認開關能夠在framework/base/packages/SettingsProvider/res/values/default.xml中改動相應字段。
圈2:ActionBar底部欄??蛇M行藍牙設備的搜索,檢測時間,已配對設備列表等一些除了配對之外的設置,Actionbar的相關布局在onCreateOptionsMenu方法中,利用例如以下代碼可自己定義actionbar
menu.add(groupId, itemId, order, title)
.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) {
//創建一個Preference
mMyDevicePreference = new Preference(getActivity());
}
//下面代碼用來設置Preference的標題。圖標,是否可點擊
mMyDevicePreference.setTitle(mLocalAdapter.getName());
if (getResources().getBoolean(com.android.internal.R.bool.config_voice_capable)) {
//假設是手機則顯示手機的圖標
mMyDevicePreference.setIcon(R.drawable.ic_bt_cellphone); // for phones
} else {
//假設不是手機諸如筆記本電腦,帶有藍牙模塊的單片機等,則顯示電腦的圖標
mMyDevicePreference.setIcon(R.drawable.ic_bt_laptop); // for tablets, etc.
}
//是否將該Preference的值寫入SharedPreference文件里,true代表寫入
mMyDevicePreference.setPersistent(false);
//是否可點擊
mMyDevicePreference.setEnabled(true);
//加入一個Preference
preferenceScreen.addPreference(mMyDevicePreference);
preferencescreen加入或者移除的代碼例如以下:
//加入一個種類的Preference getPreferenceScreen().addPreference(mAvailableDevicesCategory); .,.... //移除一個Preference 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) {
// Only paired device have an associated advanced settings screen
//假設設備已經配對,則加入監聽事件
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) {
// Bluetooth is not supported不支持藍牙此時藍牙本地適配器為null,開關狀態為未選中
mLocalAdapter = null;
mSwitch.setEnabled(false);
} else {
//假設支持藍牙獲取到本地藍牙適配器adapter,
mLocalAdapter = manager.getBluetoothAdapter();
}
//藍牙開關狀態的過濾器
mIntentFilter = new IntentFilter(BluetoothAdapter.ACTION_STATE_CHANGED);
}
緊接著在resume()中進行藍牙開關狀態的設置
public void resume() {
if (mLocalAdapter == null) {
//假設藍牙適配器為空,則將resume方法返回。不進行resume方法中的剩余操作
mSwitch.setEnabled(false);
return;
}
// Bluetooth state is not sticky, so set it manually
//必須手動的去監聽藍牙狀態的改變
//依據本地藍牙適配器獲取到此時藍牙的狀態。對switch進行設置
handleStateChanged(mLocalAdapter.getBluetoothState());
//注冊廣播監聽藍牙狀態的改變
mContext.registerReceiver(mReceiver, mIntentFilter);
//為switch設置監聽事件
mSwitch.setOnCheckedChangeListener(this);
mValidListener = true;
}
在resume方法中做了三件事,
i>,依據本地藍牙適配器獲取到此時的藍牙狀態對switch進行設置handleStateChanged(state)方法代碼非常easy,不再贅述
ii>,注冊廣播監聽藍牙狀態-----當系統藍牙狀態發生改變時須要更新switch狀態,廣播接收器中的代碼例如以下
private final BroadcastReceiver mReceiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
// Broadcast receiver is always running on the UI thread here,
// so we don't need consider thread synchronization.
int state = intent.getIntExtra(BluetoothAdapter.EXTRA_STATE, BluetoothAdapter.ERROR);
//針對不同的藍牙狀態對switch進行設置
handleStateChanged(state);
}
};
iii>。為switch設置監聽事件,當switch發生改變時,須要對系統的藍牙狀態進行行改變。
系統的藍牙開關狀態發生改變時,會發送狀態改變的廣播,對switch進行更改
public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
// shouldn't setBluetoothEnabled(true) in airplane mode.
//飛行模式下藍牙不可用
if (mLocalAdapter != null) {
if (isChecked && WifiSettings.needPrompt(mContext)) {
return;
}
//當switch開關狀態發生改變時,對系統本地藍牙狀態進行設置
mLocalAdapter.setBluetoothEnabled(isChecked);
}
//當switch狀態進行改變時,讓其不可點擊
mSwitch.setEnabled(false);
}
接下來看看對本地藍牙適配器更改的方法
public void setBluetoothEnabled(boolean enabled) {
//依據switch的enable來開啟或者關閉藍牙,success返回運行結果
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());
}
//設置preference的title標題,顯示的是藍牙名稱
mMyDevicePreference.setTitle(mLocalAdapter.getName());
//假設是手機。圖標設置為手機的圖標,假設是平板電腦或其它則設置為電腦圖標
if (getResources().getBoolean(com.android.internal.R.bool.config_voice_capable)) {
mMyDevicePreference.setIcon(R.drawable.ic_bt_cellphone); // for phones
} else {
mMyDevicePreference.setIcon(R.drawable.ic_bt_laptop); // for tablets, etc.
}
//是否將該preference的信息保存在sharedPreference中
mMyDevicePreference.setPersistent(false);
//設置preference可點擊
mMyDevicePreference.setEnabled(true);
//加入mMyDevicePreference
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"> // Paired devices category
//載入已經配對的設備列表
if (mPairedDevicesCategory == null) {
mPairedDevicesCategory = new PreferenceCategory(getActivity());
} else {
mPairedDevicesCategory.removeAll();
}
addDeviceCategory(mPairedDevicesCategory,
R.string.bluetooth_preference_paired_devices,
BluetoothDeviceFilter.BONDED_DEVICE_FILTER);
//獲取到已經配對的設備的數量。關系到mMyDevicePreference的summary顯示的文本
int numberOfPairedDevices = mPairedDevicesCategory.getPreferenceCount();
if (mDiscoverableEnabler != null) {
//依據已配對的數量對顯示的summary進行處理
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) {
//將標志位置為true,與該preference的點擊事件有關
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中的方法中會對時間進行推斷,假設時間結束了,就退出該方法。
3>。已配對設備列表
見下一篇 Android4.42-Setting源代碼分析之藍牙模塊Bluetooth(下)
總結
以上是生活随笔為你收集整理的Android4.42-Settings源代码分析之蓝牙模块Bluetooth(上)的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 罗克韦尔自动化发展简史
- 下一篇: python获取文本框里输入的值_如何从