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,查看这个可以查看它的几个关键点,有以下几个:

  1. onActivityCreated______表示当前activity创建之后的动作
  2. onStart_____表示Fragment的开始阶段应该做的动作
  3. 相应的相对的关键点有:onDestroyView(),onStop().
  4. 因为是讨论蓝牙的问题,此处是Settings的细节,不再做过多的赘述

如果查看Fragment的生命周期,会发现,下面的生命周期

onAttach();
onCreate();
onCreateView();
onActivityCreated();
onStart();
onResume();
onPause();
onStop();
onDestroyView();
onDestroy();
onDetach();

BluetoothSettings.java中没有实现的生命关键节点,由它的父类帮忙完成了。等以后遇到要改Settings的时候,再来写篇文章详细记录。此处不再过多讨论。直接接入BluetoothSettings.java的onActivityCreated()里面看看

@Overridepublic 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代码如下:

    @Overridepublic 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 preferenceFragmentListStylefinal 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函数中,如下:

   @Overrideprotected 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中查看。如下:

@Overrideprotected 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.DrawerLayoutxmlns: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 --><LinearLayoutandroid:id="@+id/content_parent"android:layout_width="match_parent"android:layout_height="match_parent"android:orientation="vertical"android:fitsSystemWindows="true"><Toolbarandroid: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"/><FrameLayoutandroid:id="@+id/content_header_container"style="?android:attr/actionBarStyle"android:layout_width="match_parent"android:layout_height="wrap_content"/><FrameLayoutandroid: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()如下:

    @Overridepublic 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 existsmShowDevicesWithoutNames = 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界面,有了一个大概的认识。

总结如下:

  1. actionbar的区域来自于BluetoothSettingsActivity的父类SettingsDrawerActivity的onCreate函数里面的设置。

  2. 带有SwitchBar的区域来自于BluetoothSettingsActivity的父类SettingsActivity的onCreate函数的设置。

  3. 剩下的区域,为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 supportedmLocalAdapter = null;mSwitchWidget.setEnabled(false);} else {mLocalAdapter = manager.getBluetoothAdapter();}mIntentFilter = new IntentFilter(BluetoothAdapter.ACTION_STATE_CHANGED);mRestrictionUtils = restrictionUtils;}

上面全都是对象的赋值,实在没有必要多少,需要注意一下几点:

  1. mSwitchWidget,为SwitchBar的封装,他管理SwitchBar的显示与隐藏,并监听SwitchBar的状态,并把SwitchBar的状态转发给
    BluetoothEnabler对象。(阅读是,可注意三个对象的接口对象,画图更好理解,因为太懒了,不想画图)
  2. 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 manuallyif (!restricted) {handleStateChanged(mLocalAdapter.getBluetoothState());}mSwitchWidget.startListening();mContext.registerReceiver(mReceiver, mIntentFilter);mValidListener = true;}

resume就做了两件事:

  1. 根据蓝牙适配器的状态,手动更新一下SwitchBar的状态
  2. 注册一个蓝牙状态的广播,用于监听,蓝牙状态的改变。

当我们点击SwitchBar时,会将状态改变传递给mSwitchWidget对象,这个对象,将状态传递给BluetoothEnabler
前者传递状态使用的接口为:SwitchBar.OnSwitchChangeListener。后两者传递状态使用的是:SwitchWidgetController.OnSwitchChangeListener

最终,会调用BluetoothEnabler的onSwitchToggled函数,如下

    @Overridepublic boolean onSwitchToggled(boolean isChecked) {if (maybeEnforceRestrictions()) {return true;}// Show toast message if Bluetooth is not allowed in airplane modeif (isChecked &&!WirelessUtils.isRadioAllowed(mContext, Settings.Global.RADIO_BLUETOOTH)) {Toast.makeText(mContext, R.string.wifi_in_airplane_mode, Toast.LENGTH_SHORT).show();// Reset switch to offmSwitch.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;}

这个函数也非常的简单,做了如下的工作:

  1. 判断飞行模式是否打开,如果打开了,将会弹出一个Toast,并不允许打开蓝牙。
  2. 调用蓝牙适配器,并设置蓝牙状态。

一旦蓝牙适配器做出了各种状态变化,将会被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 broadcastsaddHandler(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 OFFif (state == BluetoothAdapter.STATE_OFF){context.unregisterReceiver(mProfileBroadcastReceiver);registerProfileIntentReceiver();}// update local profiles and get paired devicesmLocalAdapter.setBluetoothStateInt(state);// send callback to update UI and possibly start scanningsynchronized (mCallbacks) {for (BluetoothCallback callback : mCallbacks) {callback.onBluetoothStateChanged(state);}}// Inform CachedDeviceManager that the adapter state has changedmDeviceManager.onBluetoothStateChanged(state);}}
  1. 如果是关闭了蓝牙,重新注册一下mProfileBroadcastReceiver
  2. Android的标准API 使用的蓝牙适配器对象为BluetoothAdapter.而Settings应用中对这个适配器做了一层封装
    名字叫做LocalBluetoothAdapter.该步骤就是更新这个LocalBluetoothAdapter的蓝牙状态
  3. 调用BluetoothCallback回调,通知蓝牙的状态已经改变
  4. 通知CachedDeviceManager,蓝牙状态改变。Settings应用会将已经配对的设备进行缓冲,并创建CachedDeviceManager对象来管理

那么接下来可以90%的猜测,BluetoothSettings使用了BluetoothEventManager的BluetoothCallback。

在BluetoothSettings和其父类中进行搜索BluetoothCallback。可以发现,BluetoothSettings
的父类DeviceListPreferenceFragment,在onStart中讲自己作为了BluetoothCallback并注册给
BluetoothEventManager

BluetoothEventManager中的AdapterStateChangedHandler,将会在状态改变之后,调用到BluetoothSettings的
onBluetoothStateChanged(因为BluetoothSettings覆写了DeviceListPreferenceFragmentonBluetoothStateChanged)

因此,进入BluetoothSettings的onBluetoothStateChanged方法如下:

@Overridepublic 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);}}
  1. displayEmptyMessage根据不同的状态,显示不同的UI部分
  2. addDeviceCategory 显示已经配对的设备
  3. updateFooterPreference显示剩下的UI界面

进入displayEmptyMessage如下:


@VisibleForTestingvoid 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
如下:

@Overridepublic 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);}}
  1. 从具体的子类中获取资源ID
  2. 调用addPreferencesFromResource,将整个PreferenceScreen加入PreferenceFragment中
  3. 通知所有的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();}}}
  1. 将PreferenceScreen放入mPreferenceManager中,进行保存
  2. 把以前的PreferenceScreen去掉,即onUnbindPreferences()函数的执行结果
  3. 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() {@Overridepublic 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中可看到它的赋值,前面已经有其代码,现在再次摘录关键部分如下:

@Overridepublic 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都正式梳理完成。总结如下:

  1. 在BluetoothSettings的创建之初,会调用其父类PreferenceFragment的onCreateView,将一个默认的UI界面添加到Windos中
  2. 默认界面有两个重要的组件一个是R.id.list_container,一个是R.id.empty.他们分别存放蓝牙打开是的界面。和蓝牙关闭时的界面.在蓝牙打开和关闭的情况下,就分别显示和隐藏不同的组件。
  3. R.id.lis_container中的蓝牙打开时的具体的细节,则在PreferenceFragment中的onCreate中调用onCreatePreferences,将相应的PreferenceScreen加入进去。
  4. 而蓝牙界面的整体SwitchBar相关的UI,来自于SettingsActivity中的渲染。
  5. ActionBar的UI,则来自于SettingsDrawerActivity的设置。

上面只是列出了整个UI界面的创建过程,对于其销毁过程,几乎具有对称作用,因此不再做过多讨论。

这里面的内容,其实应该属于Settings部分,权且当做蓝牙的开篇吧。

下一篇就进入真正的蓝牙的扫描,配对,以及连接的分析。

android 蓝牙分析(一)相关推荐

  1. Cubietruck---25.android蓝牙分析3_search分析 2

    一. start_disconvery的上层一系列的调用 1. 界面上的"search for device" 在./device/softwinner/common/packag ...

  2. Android 蓝牙 -- 还原网络设置 删除蓝牙所有存储配对信息流程分析---全网唯一

    同学,别退出呀,我可是全网最牛逼的 Android 蓝牙分析博主,我写了上百篇蓝牙文章,请点击下面了解本专栏,进入本博主主页看看再走呗,一定不会让你后悔的,记得一定要去看主页置顶文章哦. 一.概述 当 ...

  3. Android 蓝牙 HFP sco 和esco链路的异同分析

    同学,别退出呀,我可是全网最牛逼的 Android 蓝牙分析博主,我写了上百篇蓝牙文章,请点击下面了解本专栏,进入本博主主页看看再走呗,一定不会让你后悔的,记得一定要去看主页置顶文章哦. ​​​​​​ ...

  4. Android 蓝牙 ble 随机地址深层次分析

    同学,别退出呀,我可是全网最牛逼的 Android 蓝牙分析博主,我写了上百篇蓝牙文章,请点击下面了解本专栏,进入本博主主页看看再走呗,一定不会让你后悔的,记得一定要去看主页置顶文章哦. 一.IRK概 ...

  5. Android 蓝牙系统打开蓝牙源码分析(一)--- 全网最详细

    同学,别退出呀,我可是全网最牛逼的 Android 蓝牙分析博主,我写了上百篇蓝牙文章,请点击下面了解本专栏,进入本博主主页看看再走呗,一定不会让你后悔的,记得一定要去看主页置顶文章哦. Androi ...

  6. Android 蓝牙音频audio-a2dp分析

    同学,别退出呀,我可是全网最牛逼的 Android 蓝牙分析博主,我写了上百篇蓝牙文章,请点击下面了解本专栏,进入本博主主页看看再走呗,一定不会让你后悔的,记得一定要去看主页置顶文章哦. Androi ...

  7. Android 蓝牙 hid hogp协议分析大全- 全网最详细

    同学,别退出呀,我可是全网最牛逼的 Android 蓝牙分析博主,我写了上百篇蓝牙文章,请点击下面了解本专栏,进入本博主主页看看再走呗,一定不会让你后悔的,记得一定要去看主页置顶文章哦. 简述 HID ...

  8. Android 蓝牙驱动专题分析(1)--- 蓝牙驱动代码流程、kernel dump、tombstone问题分析

    同学,别退出呀,我可是全网最牛逼的 Android 蓝牙分析博主,我写了上百篇蓝牙文章,请点击下面了解本专栏,进入本博主主页看看再走呗,一定不会让你后悔的,记得一定要去看主页置顶文章哦. 一.概述 不 ...

  9. Android 蓝牙 A2dp 编码SBC、AAC、Aptx、LDAC、LHDC aduio音频概述(2)

    android-蓝牙A2dp-avrcp-hfp-opp-配对流程-ble-rfcomm源码流程-点击下载 Android 蓝牙A2dp-Avrcp初始化-连接-播放源码分析文档大全-点击下载 同学, ...

最新文章

  1. FLEX是什么及与FLASH的关系的介绍
  2. PI=3.1415926....
  3. CF908G New Year and Original Order
  4. 电力系统继电保护第二版张保会_《继电保护》复习笔记
  5. Eclipse开发C/C++之使用技巧小结,写给新手
  6. I/0口输入输出实验 流水灯程序 P0、P1、P2、P3口作为输出口,连接八只发光二极管,编写程序,使发光二极管从左至右循环点亮。
  7. Integer与int的比较与区别
  8. 2018软工实践第五次作业——结对作业2
  9. Fusion360删除圆角,把圆角变回直角/Remove Fillets
  10. 计算机硕士论文质疑数据不够,硕士论文伪造数据 抽检_硕士论文编数据的后果_硕士论文数据造假怎么被发现...
  11. mpaaS的kylin框架-项目结构(脚手架)
  12. 网络安全中接口测试的解决方案
  13. 【长按图片识别】uniapp vue开发时,点击图片识别—实现转发、收藏、识别图片二维码
  14. Oracle--rename
  15. python爬虫基础06-常见加密算法
  16. [Unity官方文档翻译]Downloading and Installing Unity下载和安装unity教程
  17. 新书隆重推介:网络协议本质论(2011年8月面世,沤心沥血之作)
  18. 鹏业软件入选住建部第一批智能建造新技术新产品创新服务典型案例
  19. DirectX图形开发(一)-基本概念
  20. 【pyecharts50例】xy轴翻转(reversal_axis)

热门文章

  1. java毕业设计水果购物网站mybatis+源码+调试部署+系统+数据库+lw
  2. 如何在生产中实现Elasticsearch的零停机升级
  3. Oracle排序函数详解
  4. MapReduce编程框架
  5. 3.0 Jmeter应用进阶三--利用Jmeter进行web性能测试(一)
  6. MAC地址,单播、组播、广播的区别
  7. python引入模块的五种方式与内置模块
  8. 通达信接口官网-TcApi的工作机制
  9. php 读取excel大文件,php 如何读大excel
  10. hbase使用协处理器同步es