android 蓝牙分析(一)
最近公司想要使蓝牙a2dp source和a2dp sink动态切换。
于是决定进行相应的源码调整。现在将一些分析结果整理一下
因为从来没有android 蓝牙的工作经验,所以先从android的蓝牙架构开始
注意:本次分析使用了msm8996 android 8.1 平台
一,android蓝牙架构
查看android 官网,可以获得架构相关的知识,如下图
二,从上到下的源码分析第一步Settings 应用
从架构图中,先找到蓝牙架构的应用app,然后逐渐往下。首先是Settings应用中的蓝牙。
从Settings的AndroidManifest.xml文件中,可以看到下面的蓝牙入口代码:
<activity android:name="Settings$BluetoothSettingsActivity"
android:label="@string/bluetooth_settings_title"
android:icon="@drawable/ic_settings_bluetooth"
android:configChanges="orientation|keyboardHidden|screenSize"
android:taskAffinity="">
<intent-filter android:priority="1">
<action android:name="android.settings.BLUETOOTH_SETTINGS" />
<category android:name="android.intent.category.DEFAULT" />
</intent-filter>
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.VOICE_LAUNCH" />
<category android:name="com.android.settings.SHORTCUT" />
<category android:name="android.intent.category.DEFAULT" />
</intent-filter>
<meta-data android:name="com.android.settings.FRAGMENT_CLASS"
android:value="com.android.settings.bluetooth.BluetoothSettings" />
</activity>
上面的intent-filter标签,可以通过intent唤出蓝牙设置界面。meta-data标签,由Settingslib包进行解析。表示,该设置界面使用的是下面的Fragment
com.android.settings.bluetooth.BluetoothSettings
上面的作用机制和原理,应该属于Settings应用,此处不做过多解释。
进入BluetoothSettings.java文件中,查看整个蓝牙的应用。
BluetooghSettings.java为Fragment,查看这个可以查看它的几个关键点,有以下几个:
- onActivityCreated______表示当前activity创建之后的动作
- onStart_____表示Fragment的开始阶段应该做的动作
- 相应的相对的关键点有:onDestroyView(),onStop().
- 因为是讨论蓝牙的问题,此处是Settings的细节,不再做过多的赘述
如果查看Fragment的生命周期,会发现,下面的生命周期
onAttach();
onCreate();
onCreateView();
onActivityCreated();
onStart();
onResume();
onPause();
onStop();
onDestroyView();
onDestroy();
onDetach();
BluetoothSettings.java中没有实现的生命关键节点,由它的父类帮忙完成了。等以后遇到要改Settings的时候,再来写篇文章详细记录。此处不再过多讨论。直接接入BluetoothSettings.java的onActivityCreated()里面看看
@Override
public void onActivityCreated(Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
/* Don't auto start scan if screen reconstructs due to frozen screen*/
//mInitialScanStarted = (savedInstanceState != null);
//mInitiateDiscoverable = true;
final SettingsActivity activity = (SettingsActivity) getActivity();
mSwitchBar = activity.getSwitchBar();
mBluetoothEnabler = new BluetoothEnabler(activity, new SwitchBarController(mSwitchBar),
mMetricsFeatureProvider, Utils.getLocalBtManager(activity),
MetricsEvent.ACTION_BLUETOOTH_TOGGLE);
mBluetoothEnabler.setupSwitchController();
if (mLocalAdapter != null) {
mAlwaysDiscoverable = new AlwaysDiscoverable(getContext(), mLocalAdapter);
}
}
第一步:获取Activity,在Settings应用中,将很多重复的操作,都集中在了父类SettingsActivity中了。此处蓝牙使用的Activity为
public static class BluetoothSettingsActivity extends SettingsActivity { /* empty */ }
第二步:获取Activity的SwitchBar. SettingsActivity会在创建的使用,使用一些默认的界面,这些界面就包含一个称为SwitchBar的组件。使用者只要注册一个SwitchBar.OnSwitchChangeListener监听器,就能根据SwitchBar的状态,做相应的改变了。比如在蓝牙界面,监听SwitchBar的状态打开或关闭蓝牙,在wifi界面监听SwitchBar的状态打开或者关闭蓝牙。
第三步:创建一个称为BluetoothEnable的对象,从它的参数我们可以知道,这个对象是根据SwitchBar的状态,做相应的操作,可以认为是SwitchBar的蓝牙开关封装对象(命名说明:蓝牙表示了,是跟蓝牙相关;开关,表示了是对蓝牙打开关闭的一种逻辑表示;封装表示了,对switchbar组件的一种再次包装)
第四步:蓝牙开关封装对象的初始化
第五步:创建AlwaysDiscoverable对象,用于控制蓝牙可被发现。
那么接下来,解决一个疑问:上面的代码好像,并没有界面相关的操作唉,设置中蓝牙的打开和关闭的默认界面是什么?
从Fragment的声明周期可以知道,Fragment在onActivityCreated之前还有一个onAttach();onCreate();onCreateView();几乎上个关键节点可用。
onAttach();//当Fragment第一次被加载到它的上下文时,该函数被调用
onCreate();//Fragment这个逻辑对象创建的关键节点,可以在这个函数中,初始化Fragment需要使用的一些对象。
//思考:如果我将同一个Fragment从一个Activity放到另外一个Activity,是否意味着,这个Fragment的onCreate()会被再次调用????
onCreateView();//嘿嘿,创建Fragment使用的UI的关键位置了。
按照这个步骤,分别翻越源码。从onCreateView()开始。
BluetoothSettings.java的onCreateView()的地方有:DashboardFragment.java,PreferenceFragment.java
其中DashboardFragment.java为Settings应用中自己实现的Fragment,他调用的是父类的同名方法。因此实现依然是在PreferenceFragment.java中。
Settings应用中使用的PreferenceFragment来自于android.support.v14.preference包。
接下来简单的看看PreferenceFragment.java中是怎么创建根UI的。
定位到onCreateView代码如下:
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
TypedArray a = mStyledContext.obtainStyledAttributes(null,
R.styleable.PreferenceFragment,
TypedArrayUtils.getAttr(mStyledContext,
android.support.v7.preference.R.attr.preferenceFragmentStyle,
AndroidResources.ANDROID_R_PREFERENCE_FRAGMENT_STYLE),
0);
mLayoutResId = a.getResourceId(R.styleable.PreferenceFragment_android_layout, mLayoutResId);
final Drawable divider = a.getDrawable(R.styleable.PreferenceFragment_android_divider);
final int dividerHeight = a.getDimensionPixelSize(
R.styleable.PreferenceFragment_android_dividerHeight, -1);
final boolean allowDividerAfterLastItem = a.getBoolean(
R.styleable.PreferenceFragment_allowDividerAfterLastItem, true);
a.recycle();
// Need to theme the inflater to pick up the preferenceFragmentListStyle
final TypedValue tv = new TypedValue();
getActivity().getTheme().resolveAttribute(
android.support.v7.preference.R.attr.preferenceTheme, tv, true);
final int theme = tv.resourceId;
final Context themedContext = new ContextThemeWrapper(inflater.getContext(), theme);
final LayoutInflater themedInflater = inflater.cloneInContext(themedContext);
final View view = themedInflater.inflate(mLayoutResId, container, false);
final View rawListContainer = view.findViewById(AndroidResources.ANDROID_R_LIST_CONTAINER);
if (!(rawListContainer instanceof ViewGroup)) {
throw new RuntimeException("Content has view with id attribute "
+ "'android.R.id.list_container' that is not a ViewGroup class");
}
final ViewGroup listContainer = (ViewGroup) rawListContainer;
final RecyclerView listView = onCreateRecyclerView(themedInflater, listContainer,
savedInstanceState);
if (listView == null) {
throw new RuntimeException("Could not create RecyclerView");
}
mList = listView;
listView.addItemDecoration(mDividerDecoration);
setDivider(divider);
if (dividerHeight != -1) {
setDividerHeight(dividerHeight);
}
mDividerDecoration.setAllowDividerAfterLastItem(allowDividerAfterLastItem);
listContainer.addView(mList);
mHandler.post(mRequestFocus);
return view;
}
应用开发者应该非常熟悉的一些常见操作,默认加载的界面为:preference_list_fragment.xml文件。此处就是PreferenceFragment的实现细节了,不再次讨论,我们的目标为蓝牙。
使用UI工具,可以看到,当关闭蓝牙时,Fragment里面的“@android:id/empty”被设置为:“开启蓝牙后,你的设备可以与附近的其他蓝牙设备通信”
可是我们的设置界面,还有一个蓝牙的开关,即上文提到的SwitchBar.显然蓝牙设置界面的UI还有Activity的UI在。那么进入Activity的UI的创建过程,以解决上文的疑问——蓝牙的界面是怎么形成的?
BluetoothSettings使用的Activity为
public static class BluetoothSettingsActivity extends SettingsActivity { /* empty */ }
真正的实现在SettingsActivity.java中。这是一个Activity,那么直接定位到onCreate函数中,如下:
@Override
protected void onCreate(Bundle savedState) {
super.onCreate(savedState);
//省略一部分
mIsShowingDashboard = className.equals(Settings.class.getName());
//省略一部分
setContentView(mIsShowingDashboard ?
R.layout.settings_main_dashboard : R.layout.settings_main_prefs);
//省略一部分
mContent = findViewById(R.id.main_content);
上面的代码只保留了,跟ui相关的部分,用于说明Settings中各个UI的关系。细节的话,建议参考Settings的源码分析,有空了我也给整一个Settings的源码分析。
setContentView根据mIsShowingDashboard来选择不同的UI界面。mIsShowingDashboard表示的意义是:是否是Settings的最顶层界面,这个界面被称作:DashBoard(暂且译做设置主界面)。
如果不是DashBoard界面,则使用R.layout.settings_main_prefs.
使用UI工具,依然可以看到,最上层的被称作actionbar的区域,并没有这Activity使用的xml中。那么接下来就是查看,actionbar是怎么被加载出来的了。
首先从SettingsActivity的父类中翻阅,他的父类为SettingsDrawerActivity。在
./frameworks/base/packages/SettingsLib/src/com/android/settingslib/drawer/SettingsDrawerActivity.java
进入这个文件的onCreate中查看。如下:
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
//省略部分
super.setContentView(R.layout.settings_with_drawer);
mContentHeaderContainer = (FrameLayout) findViewById(R.id.content_header_container);
Toolbar toolbar = (Toolbar) findViewById(R.id.action_bar);
if (theme.getBoolean(android.R.styleable.Theme_windowNoTitle, false)) {
toolbar.setVisibility(View.GONE);
return;
}
setActionBar(toolbar);
if (DEBUG_TIMING) {
Log.d(TAG, "onCreate took " + (System.currentTimeMillis() - startTime)
+ " ms");
}
}
上面的代码,将settings_with_drawer中的Toolbar,设置为当前界面的actionbar。
settings_with_drawer.xml.继续查看该文件。
<android.support.v4.widget.DrawerLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/drawer_layout"
android:layout_width="match_parent"
android:layout_height="match_parent">
<!-- The main content view -->
<LinearLayout
android:id="@+id/content_parent"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
android:fitsSystemWindows="true">
<Toolbar
android:id="@+id/action_bar"
style="?android:attr/actionBarStyle"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:theme="?android:attr/actionBarTheme"
android:navigationContentDescription="@*android:string/action_bar_up_description"/>
<FrameLayout
android:id="@+id/content_header_container"
style="?android:attr/actionBarStyle"
android:layout_width="match_parent"
android:layout_height="wrap_content"/>
<FrameLayout
android:id="@+id/content_frame"
android:layout_width="match_parent"
android:layout_height="fill_parent"
android:background="?android:attr/windowBackground" />
</LinearLayout>
</android.support.v4.widget.DrawerLayout>
Nice!!!,上面的Toolbar,即为整个界面的action bar区域。
现在回到最开始的地方PreferenceFragment的内容,是在什么时候,被设置的,即“开启蓝牙后,你的设备可以与附近的其他蓝牙设备通信”是在什么时候设置的。
按照逻辑划分——谁要使用,谁来负责这里面的内容。这部分的内容设置,一定会落在BluetootSettings,或者它的组成类中。
BluetoothSettings的onActivityCreated已经查看完毕。接下来查看onStart()如下:
@Override
public void onStart() {
// resume BluetoothEnabler before calling super.onStart() so we don't get
// any onDeviceAdded() callbacks before setting up view in updateContent()
if (mBluetoothEnabler != null) {
mBluetoothEnabler.resume(getActivity());
}
super.onStart();
// Always show paired devices regardless whether user-friendly name exists
mShowDevicesWithoutNames = true;
if (isUiRestricted()) {
getPreferenceScreen().removeAll();
if (!isUiRestrictedByOnlyAdmin()) {
getEmptyTextView().setText(R.string.bluetooth_empty_list_user_restricted);
}
return;
}
if (mLocalAdapter != null) {
updateContent(mLocalAdapter.getBluetoothState());
}
}
第一步:对BluetoothEnabler进行一个调用,作用在后面介绍,主要就是初始化SwitchBar的各种状态,并监听
第二步:是否是限制的UI界面,如果是则显示其他的内容,并不进一步操作。
第三步:根据蓝牙适配器的状态,跟新内容。
进入updateContent(),如下:
private void updateContent(int bluetoothState) {
int messageId = 0;
switch (bluetoothState) {
//省略部分
case BluetoothAdapter.STATE_OFF:
setOffMessage();
if (isUiRestricted()) {
messageId = R.string.bluetooth_empty_list_user_restricted;
}
break;
//省略部分
}
displayEmptyMessage(true);
if (messageId != 0) {
getEmptyTextView().setText(messageId);
}
}
加入蓝牙默认为关闭状态,那么就,进入setOffMessage().这回好了,几乎可以肯定,setOffMessage就是我们要寻找的最终位置了,后续就不再继续了,毕竟本篇文章的主题是蓝牙。而不是Settings。
自此,整个蓝牙界面的默认UI界面,有了一个大概的认识。
总结如下:
- actionbar的区域来自于BluetoothSettingsActivity的父类SettingsDrawerActivity的onCreate函数里面的设置。
- 带有SwitchBar的区域来自于BluetoothSettingsActivity的父类SettingsActivity的onCreate函数的设置。
- 剩下的区域,为BluetoothSettings的父类PreferenceFragment的默认界面。
但是蓝牙界面的形成,还有一半,即当点击SwitchBar时,蓝牙的界面会更改,那么它的整个界面是怎么形成的呢??
由上文的蓝牙开关封装对象即BluetoothEnabler对象,可以作为入手点。也可以将SwitchBar.OnSwitchChangeListener作为入手点。
我现在先看到了BluetoothEnabler对象,那么就以它作为入手点吧。它在onActivityCreated中被创建,在onStart中被调用resume。
先看看创建,如下
public BluetoothEnabler(Context context, SwitchWidgetController switchWidget,
MetricsFeatureProvider metricsFeatureProvider, LocalBluetoothManager manager,
int metricsEvent, RestrictionUtils restrictionUtils) {
mContext = context;
mMetricsFeatureProvider = metricsFeatureProvider;
mSwitchWidget = switchWidget;
mSwitch = mSwitchWidget.getSwitch();
mSwitchWidget.setListener(this);
mValidListener = false;
mMetricsEvent = metricsEvent;
if (manager == null) {
// Bluetooth is not supported
mLocalAdapter = null;
mSwitchWidget.setEnabled(false);
} else {
mLocalAdapter = manager.getBluetoothAdapter();
}
mIntentFilter = new IntentFilter(BluetoothAdapter.ACTION_STATE_CHANGED);
mRestrictionUtils = restrictionUtils;
}
上面全都是对象的赋值,实在没有必要多少,需要注意一下几点:
- mSwitchWidget,为SwitchBar的封装,他管理SwitchBar的显示与隐藏,并监听SwitchBar的状态,并把SwitchBar的状态转发给
BluetoothEnabler对象。(阅读是,可注意三个对象的接口对象,画图更好理解,因为太懒了,不想画图) - IntentFilter对象监听的是适配器的状态改变。
接下来,看看resume函数
public void resume(Context context) {
if (mContext != context) {
mContext = context;
}
final boolean restricted = maybeEnforceRestrictions();
if (mLocalAdapter == null) {
mSwitchWidget.setEnabled(false);
return;
}
// Bluetooth state is not sticky, so set it manually
if (!restricted) {
handleStateChanged(mLocalAdapter.getBluetoothState());
}
mSwitchWidget.startListening();
mContext.registerReceiver(mReceiver, mIntentFilter);
mValidListener = true;
}
resume就做了两件事:
- 根据蓝牙适配器的状态,手动更新一下SwitchBar的状态
- 注册一个蓝牙状态的广播,用于监听,蓝牙状态的改变。
当我们点击SwitchBar时,会将状态改变传递给mSwitchWidget对象,这个对象,将状态传递给BluetoothEnabler
前者传递状态使用的接口为:SwitchBar.OnSwitchChangeListener。后两者传递状态使用的是:SwitchWidgetController.OnSwitchChangeListener
最终,会调用BluetoothEnabler的onSwitchToggled函数,如下
@Override
public boolean onSwitchToggled(boolean isChecked) {
if (maybeEnforceRestrictions()) {
return true;
}
// Show toast message if Bluetooth is not allowed in airplane mode
if (isChecked &&
!WirelessUtils.isRadioAllowed(mContext, Settings.Global.RADIO_BLUETOOTH)) {
Toast.makeText(mContext, R.string.wifi_in_airplane_mode, Toast.LENGTH_SHORT).show();
// Reset switch to off
mSwitch.setChecked(false);
return false;
}
mMetricsFeatureProvider.action(mContext, mMetricsEvent, isChecked);
if (mLocalAdapter != null) {
boolean status = mLocalAdapter.setBluetoothEnabled(isChecked);
// If we cannot toggle it ON then reset the UI assets:
// a) The switch should be OFF but it should still be togglable (enabled = True)
// b) The switch bar should have OFF text.
if (isChecked && !status) {
mSwitch.setChecked(false);
mSwitch.setEnabled(true);
mSwitchWidget.updateTitle(false);
return false;
}
}
mSwitchWidget.setEnabled(false);
return true;
}
这个函数也非常的简单,做了如下的工作:
- 判断飞行模式是否打开,如果打开了,将会弹出一个Toast,并不允许打开蓝牙。
- 调用蓝牙适配器,并设置蓝牙状态。
一旦蓝牙适配器做出了各种状态变化,将会被BluetoothEnabler.mReceiver对象接受到,并根据实际的蓝牙状态,
更新SwitchBar的状态。
可知,BluetoothEnabler里面并没有我们要找的UI相关的东西。但是它却操作了蓝牙适配器的打开和关闭,按照BluetoothEnabler
接受蓝牙状态的方法。可以猜测,BluetoothSettings.java中也是使用同样的方法来接收蓝牙状态的改变,并据此来更新UI的。
如果不是,那么也有可能是BluetoothSettings的组合类中使用了类似的方法,直接全局搜索,BluetoothAdapter.ACTION_STATE_CHANGED。
从搜索结果中,可以看到Settings使用的SettingsLib库有这个的监听,监听的位置为:BluetoothEventManager.java
位置如下:
./frameworks/base/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothEventManager.java
查看代码如下:
BluetoothEventManager(LocalBluetoothAdapter adapter,
CachedBluetoothDeviceManager deviceManager, Context context) {
//省略部分
// Bluetooth on/off broadcasts
addHandler(BluetoothAdapter.ACTION_STATE_CHANGED, new AdapterStateChangedHandler());
// Generic connected/not broadcast
//省略部分
}
可以看到,蓝牙状态的改变,会交由AdapterStateChangedHandler对象来处理。
注意:在此处直接给出了蓝牙状态会交给AdapterStateChangedHandler对象处理,这中间的细节没有过多的交代,
下面简述之:addHandler函数将一个action和Handler对象进行关联(关联的形式是一个HashMap),并监听这个action
一旦,广播收到某一个action,就从HashMap中直接取出Handler,并调用Handler的onReceive方法。交由Handler来处理
接下来看看AdapterStateChangedHandler的处理过程,如下:
private class AdapterStateChangedHandler implements Handler {
public void onReceive(Context context, Intent intent,
BluetoothDevice device) {
int state = intent.getIntExtra(BluetoothAdapter.EXTRA_STATE,
BluetoothAdapter.ERROR);
// Reregister Profile Broadcast Receiver as part of TURN OFF
if (state == BluetoothAdapter.STATE_OFF)
{
context.unregisterReceiver(mProfileBroadcastReceiver);
registerProfileIntentReceiver();
}
// update local profiles and get paired devices
mLocalAdapter.setBluetoothStateInt(state);
// send callback to update UI and possibly start scanning
synchronized (mCallbacks) {
for (BluetoothCallback callback : mCallbacks) {
callback.onBluetoothStateChanged(state);
}
}
// Inform CachedDeviceManager that the adapter state has changed
mDeviceManager.onBluetoothStateChanged(state);
}
}
- 如果是关闭了蓝牙,重新注册一下mProfileBroadcastReceiver
- Android的标准API 使用的蓝牙适配器对象为BluetoothAdapter.而Settings应用中对这个适配器做了一层封装
名字叫做LocalBluetoothAdapter.该步骤就是更新这个LocalBluetoothAdapter的蓝牙状态 - 调用BluetoothCallback回调,通知蓝牙的状态已经改变
- 通知CachedDeviceManager,蓝牙状态改变。Settings应用会将已经配对的设备进行缓冲,并创建CachedDeviceManager对象来管理
那么接下来可以90%的猜测,BluetoothSettings使用了BluetoothEventManager的BluetoothCallback。
在BluetoothSettings和其父类中进行搜索BluetoothCallback。可以发现,BluetoothSettings
的父类DeviceListPreferenceFragment,在onStart中讲自己作为了BluetoothCallback并注册给
BluetoothEventManager
BluetoothEventManager中的AdapterStateChangedHandler,将会在状态改变之后,调用到BluetoothSettings的
onBluetoothStateChanged(因为BluetoothSettings覆写了DeviceListPreferenceFragmentonBluetoothStateChanged)
因此,进入BluetoothSettings的onBluetoothStateChanged方法如下:
@Override
public void onBluetoothStateChanged(int bluetoothState) {
super.onBluetoothStateChanged(bluetoothState);
updateContent(bluetoothState);
}
啊,这再简单不过了。看到updateContent,就知道是去更新BluetoothSettings的界面去了。如下
前面已经大致浏览了蓝牙关闭的状态,现在进入蓝牙打开的状态
private void updateContent(int bluetoothState) {
int messageId = 0;
switch (bluetoothState) {
case BluetoothAdapter.STATE_ON:
displayEmptyMessage(false);
mDevicePreferenceMap.clear();
if (isUiRestricted()) {
messageId = R.string.bluetooth_empty_list_user_restricted;
break;
}
addDeviceCategory(mPairedDevicesCategory,
R.string.bluetooth_preference_paired_devices,
BluetoothDeviceFilter.BONDED_DEVICE_FILTER, true);
mPairedDevicesCategory.addPreference(mPairingPreference);
updateFooterPreference(mFooterPreference);
if (mAlwaysDiscoverable != null) {
mAlwaysDiscoverable.start();
}
return; // not break
//省略部分
}
displayEmptyMessage(true);
if (messageId != 0) {
getEmptyTextView().setText(messageId);
}
}
- displayEmptyMessage根据不同的状态,显示不同的UI部分
- addDeviceCategory 显示已经配对的设备
- updateFooterPreference显示剩下的UI界面
进入displayEmptyMessage如下:
@VisibleForTesting
void displayEmptyMessage(boolean display) {
final Activity activity = getActivity();
activity.findViewById(android.R.id.list_container).setVisibility(
display ? View.INVISIBLE : View.VISIBLE);
activity.findViewById(android.R.id.empty).setVisibility(
display ? View.VISIBLE : View.GONE);
}
即显示android.R.id.list_container,隐藏android.R.id.empty。
在前面的PreferenceFragemen的默认界面(R.layout.preference_list_fragment),就有android.R.id.list_container
的声明。
可是这里面出现了另外一个问题,android.R.id.list_container里面的内容可是空的呀,Settings界面是如何将自己要用的UI
添加到里面去的呢?
对于PreferenceFragement来说,onCreatePreferences函数,用于创建preferen,并调用addPreferencesFromResource将
创建的Preference加入PreferenceFragment中。
接下来查看BluetoothSettings的onCreatePreferences。在BluetoothSettings的父类DashboardFragment中有onCreatePreferences
如下:
@Override
public void onCreatePreferences(Bundle savedInstanceState, String rootKey) {
super.onCreatePreferences(savedInstanceState, rootKey);
refreshAllPreferences(getLogTag());
}
调用refreshAllPreferences函数,如下
private void refreshAllPreferences(final String TAG) {
//省略部分
// Add resource based tiles.
displayResourceTiles();
//省略部分
}
进入displayResourceTiles,如下:
private void displayResourceTiles() {
final int resId = getPreferenceScreenResId();
if (resId <= 0) {
return;
}
addPreferencesFromResource(resId);
final PreferenceScreen screen = getPreferenceScreen();
Collection<AbstractPreferenceController> controllers = mPreferenceControllers.values();
for (AbstractPreferenceController controller : controllers) {
controller.displayPreference(screen);
}
}
- 从具体的子类中获取资源ID
- 调用addPreferencesFromResource,将整个PreferenceScreen加入PreferenceFragment中
- 通知所有的PreferenceController,更改了PreferenceScreen
而BluetoothSettings.java的getPreferenceScreenResId(),返回的是R.xml.bluetooth_settings。
因此接下来调用addPreferencesFromResource(R.xml.bluetooth_settings)将xml中的描述加入PreferenceFragment中。
现在进入addPreferencesFromResource,如下:
public void addPreferencesFromResource(@XmlRes int preferencesResId) {
//省略部分
setPreferenceScreen(mPreferenceManager.inflateFromResource(mStyledContext,
preferencesResId, getPreferenceScreen()));
}
关键部分为setPreferenceScreen函数,如下
public void setPreferenceScreen(PreferenceScreen preferenceScreen) {
if (mPreferenceManager.setPreferences(preferenceScreen) && preferenceScreen != null) {
onUnbindPreferences();
mHavePrefs = true;
if (mInitDone) {
postBindPreferences();
}
}
}
- 将PreferenceScreen放入mPreferenceManager中,进行保存
- 把以前的PreferenceScreen去掉,即onUnbindPreferences()函数的执行结果
- postBindPreferences()通知UI线程绘制整个PreferenceScreen
关键在postBindPreferences中,如下:
private void postBindPreferences() {
if (mHandler.hasMessages(MSG_BIND_PREFERENCES)) return;
mHandler.obtainMessage(MSG_BIND_PREFERENCES).sendToTarget();
}
private Handler mHandler = new Handler() {
@Override
public void handleMessage(Message msg) {
switch (msg.what) {
case MSG_BIND_PREFERENCES:
bindPreferences();
break;
}
}
};
private void bindPreferences() {
final PreferenceScreen preferenceScreen = getPreferenceScreen();
if (preferenceScreen != null) {
getListView().setAdapter(onCreateAdapter(preferenceScreen));
preferenceScreen.onAttached();
}
onBindPreferences();
}
最后会掉到bindPreferences()函数
该函数首先,获取mPreferenceManager中保存的PreferenceScreen,然后调用onCreateAdapter,遍历PreferenceScreen
中的层级,创建对应的Adapter,并将其设置给getListView()的返回值。
我们需要寻找的关键点就在getListView()的返回值上面。看看它是不是android.R.id.list_container或者是其子View
在PreferenceFragement的onCreateView中可看到它的赋值,前面已经有其代码,现在再次摘录关键部分如下:
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
final RecyclerView listView = onCreateRecyclerView(themedInflater, listContainer,
savedInstanceState);
mList = listView;
listContainer.addView(mList);
return view;
}
首先,调动onCreateRecyclerView,创建一个RecyclerView,并将其添加进listContainer。这里listContainer,即是android.R.id.list_container。mList即getListView()的返回值。
自此,蓝牙界面的所有UI都正式梳理完成。总结如下:
- 在BluetoothSettings的创建之初,会调用其父类PreferenceFragment的onCreateView,将一个默认的UI界面添加到Windos中
- 默认界面有两个重要的组件一个是R.id.list_container,一个是R.id.empty.他们分别存放蓝牙打开是的界面。和蓝牙关闭时的界面.在蓝牙打开和关闭的情况下,就分别显示和隐藏不同的组件。
- R.id.lis_container中的蓝牙打开时的具体的细节,则在PreferenceFragment中的onCreate中调用onCreatePreferences,将相应的PreferenceScreen加入进去。
- 而蓝牙界面的整体SwitchBar相关的UI,来自于SettingsActivity中的渲染。
- ActionBar的UI,则来自于SettingsDrawerActivity的设置。
上面只是列出了整个UI界面的创建过程,对于其销毁过程,几乎具有对称作用,因此不再做过多讨论。
这里面的内容,其实应该属于Settings部分,权且当做蓝牙的开篇吧。
下一篇就进入真正的蓝牙的扫描,配对,以及连接的分析。