我们在前面已经分析了Android启动中涉及蓝牙的各个方面,今天我们着重来看看,在蓝牙打开之前,我们能看到的蓝牙UI有哪些,这些UI又是如何实现的。

1,settings中UI的分析

首先,最常见的也是我们通常情况下最新看到的,它就是Settings中蓝牙的显示代码,具体的图片如下:

 

图1,默认settings中的界面

这个界面的实现是在这个文件中:/packages/apps/Settings/res/xml/settings_headers.xml。它采用的是preference-headers来实现的,这样的实现好处就在于可以匹配不同的屏幕,比如pad和phone。我们来看一下,你就会发现其实还是蛮简单的:

<preference-headers
xmlns:android="http://schemas.android.com/apk/res/android"> <!--这个就是那个“无线和网络”五个字了 -->
<!-- WIRELESS and NETWORKS -->
<header android:title="@string/header_category_wireless_networks" />
<!--这个是wifi --> <!-- Wifi -->
<header
android:id="@+id/wifi_settings"
android:fragment="com.android.settings.wifi.WifiSettings"
android:title="@string/wifi_settings_title"
android:icon="@drawable/ic_settings_wireless" />
<!--这个是bluetooth --> <!-- Bluetooth -->
<header
android:id="@+id/bluetooth_settings"
<!—-这里的fragment是比较重要的-->
android:fragment="com.android.settings.bluetooth.BluetoothSettings"
android:title="@string/bluetooth_settings_title"
android:icon="@drawable/ic_settings_bluetooth2" />
……

要显示这个preference-headers,需要重新实现 onBuildHeaders回调方法,毫无疑问,肯定是实现过了,我们来看一下具体的代码:

 @Override
public void onBuildHeaders(List<Header> headers) {
if(UNIVERSEUI_SUPPORT){
loadHeadersFromResource(R.xml.settings_headers_uui, headers);
}else{
//load的preference-headers xml文件
loadHeadersFromResource(R.xml.settings_headers, headers);
} //这个会根据支持的features来决定是否需要把一些list去除掉
updateHeaderList(headers); mHeaders = headers;
}

这样来看,这个preference-headers的显示还是比较简单的,细心的同学会发现,上面header只有title和icon啊,我们在界面上还有一个开关,这里怎么没有啊?呵呵,好问题,其实上面的代码并不是真正的UI上的显示代码,真正的UI显示代码在哪里呢,我们来慢慢看。

我们知道settings其实最终调用的是setListAdapter,那么这个地方是如何实现的呢?我们来看源码:

  public void setListAdapter(ListAdapter adapter) {
if (mHeaders == null) {
mHeaders = new ArrayList<Header>();
// When the saved state provides the list of headers, onBuildHeaders is not called
// Copy the list of Headers from the adapter, preserving their order
for (int i = 0; i < adapter.getCount(); i++) {
mHeaders.add((Header) adapter.getItem(i));
}
} // Ignore the adapter provided by PreferenceActivity and substitute ours instead
//重点要关注这里,看HeaderAdapter是如何构建的
super.setListAdapter(new HeaderAdapter(this, mHeaders));
}

来看一下HeaderAdapter的构造

 public HeaderAdapter(Context context, List<Header> objects) {
super(context, 0, objects);
mInflater = (LayoutInflater)context.getSystemService(Context.LAYOUT_INFLATER_SERVICE); // Temp Switches provided as placeholder until the adapter replaces these with actual
// Switches inflated from their layouts. Must be done before adapter is set in super
//从注释来看,这里只是占位而已,后面会被layout中的内容真正地覆盖的,我们后面会详细分析
mWifiEnabler = new WifiEnabler(context, new Switch(context));
//这里就是要构造我们的BluetoothEnabler了,这个在1.1中进行分析,这里可以理解为蓝牙那边的一些初始化,那边的分析会陷入进去比较多,若是想从整体上先理解,请跳过1.1,直接看后面1.2的内容
mBluetoothEnabler = new BluetoothEnabler(context, new Switch(context));
}

1.1 BluetoothEnabler的分析

BluetoothEnabler主要是用来管理蓝牙的on off的开关的。

public BluetoothEnabler(Context context, Switch switch_) {
mContext = context;
mSwitch = switch_;
//local bluetooth manager就是在bluetooth api上面提供一个简单的接口
//详见1.1.1分析
LocalBluetoothManager manager = LocalBluetoothManager.getInstance(context);
if (manager == null) {
// Bluetooth is not supported
mLocalAdapter = null;
mSwitch.setEnabled(false);
} else {
mLocalAdapter = manager.getBluetoothAdapter();
}
//加入对ACTION_STATE_CHANGED和ACTION_AIRPLANE_MODE_CHANGED的action的处理
mIntentFilter = new IntentFilter(BluetoothAdapter.ACTION_STATE_CHANGED);
mIntentFilter.addAction(Intent.ACTION_AIRPLANE_MODE_CHANGED);
//btwifi的conexist是否被置位。若是没有,意味着wifi和bt只能有一个,则需要加一些action的处理
if (SystemProperties.get("ro.btwifi.coexist", "true").equals("false")) {
mWifiManager = (WifiManager) mContext.getSystemService(Context.WIFI_SERVICE);
mIntentFilter.addAction(WifiManager.WIFI_STATE_CHANGED_ACTION);
mIntentFilter.addAction(WifiManager.WIFI_AP_STATE_CHANGED_ACTION);
mSupportBtWifiCoexist = false;
}
}

1.1.1 LocalBluetoothManager的分析

local bluetooth manager就是在bluetooth api上面提供一个简单的接口。也就是说他是封装在bluetooth提供的api之上的。

public static synchronized LocalBluetoothManager getInstance(Context context) {
if (sInstance == null) {
//调用LocalBluetoothAdapter,调用api得到对应的bluetooth adapter
LocalBluetoothAdapter adapter = LocalBluetoothAdapter.getInstance();
if (adapter == null) {
return null;
}
// This will be around as long as this process is
//得到整个应该的生命周期,所以运行够长时间
Context appContext = context.getApplicationContext();
//新建LocalBluetoothManager
sInstance = new LocalBluetoothManager(adapter, appContext);
} return sInstance;
} private LocalBluetoothManager(LocalBluetoothAdapter adapter, Context context) {
mContext = context;
mLocalAdapter = adapter;
//新建CachedBluetoothDeviceManager,用来管理远端设备的,就是对端
mCachedDeviceManager = new CachedBluetoothDeviceManager(context);
// BluetoothEventManager用来管理从bluetooth API那边传过来的broadcast和callback,并把他们分配到对应的class中去,详见1.1.2
mEventManager = new BluetoothEventManager(mLocalAdapter,
mCachedDeviceManager, context);
//用来管理对bluetooth profile的访问的,详见1.1.3
mProfileManager = new LocalBluetoothProfileManager(context,
mLocalAdapter, mCachedDeviceManager, mEventManager);
}

1.1.2BluetoothEventManager的分析

上文已经讲过了,bluetoothEventManager是用来管理api那边传过来的broadcast和callback,他会根据各个broadcast进行最终的分配,我们来了解一下它究竟关注了哪些broadcast和callback。

 BluetoothEventManager(LocalBluetoothAdapter adapter,
CachedBluetoothDeviceManager deviceManager, Context context) {
mLocalAdapter = adapter;
mDeviceManager = deviceManager;
mAdapterIntentFilter = new IntentFilter();
mProfileIntentFilter = new IntentFilter();
mHandlerMap = new HashMap<String, Handler>();
mContext = context; // Bluetooth on/off broadcasts
// ACTION_STATE_CHANGED,在蓝牙的on和off的时候会发出
addHandler(BluetoothAdapter.ACTION_STATE_CHANGED, new AdapterStateChangedHandler());
//这两个是扫描的broadcast,分别表示开始扫描和停止扫描
// Discovery broadcasts
addHandler(BluetoothAdapter.ACTION_DISCOVERY_STARTED, new ScanningStateChangedHandler(true));
addHandler(BluetoothAdapter.ACTION_DISCOVERY_FINISHED, new ScanningStateChangedHandler(false));
//这是扫描到设备和设备消失的broadcast
addHandler(BluetoothDevice.ACTION_FOUND, new DeviceFoundHandler());
addHandler(BluetoothDevice.ACTION_DISAPPEARED, new DeviceDisappearedHandler());
//这个是设备名字改变的action
addHandler(BluetoothDevice.ACTION_NAME_CHANGED, new NameChangedHandler()); // Pairing broadcasts
//这个是设备配对状态改变的action,比如正在配对,已经配对之类的
addHandler(BluetoothDevice.ACTION_BOND_STATE_CHANGED, new BondStateChangedHandler());
//取消配对的handler
addHandler(BluetoothDevice.ACTION_PAIRING_CANCEL, new PairingCancelHandler()); // Fine-grained state broadcasts
//CLASS和UUID改变的action
addHandler(BluetoothDevice.ACTION_CLASS_CHANGED, new ClassChangedHandler());
addHandler(BluetoothDevice.ACTION_UUID, new UuidChangedHandler()); // Dock event broadcasts
//dock的event
addHandler(Intent.ACTION_DOCK_EVENT, new DockEventHandler());
//注册对这些action处理的receiver
mContext.registerReceiver(mBroadcastReceiver, mAdapterIntentFilter);
}

1.1.3 LocalBluetoothProfileManager的分析

LocalBluetoothProfileManager是用来访问支持的bluetoothprofile的LocalBluetoothProfile的。具体的代码如下:

 LocalBluetoothProfileManager(Context context,
LocalBluetoothAdapter adapter,
CachedBluetoothDeviceManager deviceManager,
BluetoothEventManager eventManager) {
mContext = context; mLocalAdapter = adapter;
mDeviceManager = deviceManager;
mEventManager = eventManager;
// pass this reference to adapter and event manager (circular dependency)
//和localadapter以及eventmanager关联
mLocalAdapter.setProfileManager(this);
mEventManager.setProfileManager(this); ParcelUuid[] uuids = adapter.getUuids(); // uuids may be null if Bluetooth is turned off
if (uuids != null) {
//根据uuid刷新我们支持的profile,在蓝牙off的状态下(从没有打开过的情况下),他应该是null,这里我就暂时不详细介绍了,会在后面的文章中再详细介绍
updateLocalProfiles(uuids);
} // Always add HID and PAN profiles
//HID和PAN总是会加入的,具体的后面的文章用到再详细介绍
mHidProfile = new HidProfile(context, mLocalAdapter);
addProfile(mHidProfile, HidProfile.NAME,
BluetoothInputDevice.ACTION_CONNECTION_STATE_CHANGED); mPanProfile = new PanProfile(context);
addPanProfile(mPanProfile, PanProfile.NAME,
BluetoothPan.ACTION_CONNECTION_STATE_CHANGED); Log.d(TAG, "LocalBluetoothProfileManager construction complete");
}

这里,我们总结一下,BluetoothEnabler构造所涉及的各类和他们的主要作用:

1)BluetoothEnabler—用于管理蓝牙的on/off开关操作。

2)LocalBluetoothManager—在framework的bluetooth api之上进行了重新封装,向该应用本身提供了一些简单的接口。

3)CachedBluetoothDeviceManager—用于管理远端设备的类,比如耳机,鼠标等

4)BluetoothEventManager—用于管理从framework的bluetooth api那边上来的broadcast和callback,并把这些反馈到对应的class中去。

5)LocalBluetoothProfileManager—管理对各个bluetoothprofile的访问和操作

1.2真正的开关实现

基本到bluetooth中兜了一圈,我们还是没有发现任何和那个开关相关的内容。没有关系,我们继续来分析Settings中的内容,我们突然发现它重写了getView,哈哈,大家都知道PreferenceActivity中每个list都是通过getView来得到对应要显示的内容的,所以我们有必要来看看这个内容。

   @Override
public View getView(int position, View convertView, ViewGroup parent) {
HeaderViewHolder holder;
//根据postition得到对应item
Header header = getItem(position);
//得到type,wifi和蓝牙是有swtich的,这个见1.2.1,很简单的
//就是下面switch来判断用的,蓝牙是HEADER_TYPE_SWITCH,就是有个开关啦
int headerType = getHeaderType(header);
View view = null; if (convertView == null) {
holder = new HeaderViewHolder();
switch (headerType) {
case HEADER_TYPE_CATEGORY:
……
//bluetooth是witch的type哦
case HEADER_TYPE_SWITCH:
//找到preference_header_switch_item这个layout
view = mInflater.inflate(R.layout.preference_header_switch_item, parent,
false);
//细心的你一定发现这里的icon和title神马的好像和我们真正要显示的不太一样啊?别急,继续看下面你就明白了
holder.icon = (ImageView) view.findViewById(R.id.icon);
holder.title = (TextView)
view.findViewById(com.android.internal.R.id.title);
holder.summary = (TextView)
view.findViewById(com.android.internal.R.id.summary);
//这里就是开关了
holder.switch_ = (Switch) view.findViewById(R.id.switchWidget);
break; case HEADER_TYPE_NORMAL:
……
break;
}
//这里把这个holder加入到view,需要注意的这个holder还是会变的哦
view.setTag(holder);
} else {
view = convertView;
holder = (HeaderViewHolder) view.getTag();
} // All view fields must be updated every time, because the view may be recycled
switch (headerType) {
case HEADER_TYPE_CATEGORY:
holder.title.setText(header.getTitle(getContext().getResources()));
break; case HEADER_TYPE_SWITCH:
// Would need a different treatment if the main menu had more switches
if (header.id == R.id.wifi_settings) {
mWifiEnabler.setSwitch(holder.switch_);
} else {
//这里会把这个开关和bluetoothEnabler中的开关相关联,具体见1.2.2,这样对这个开关的操作才能真正有所反应,所以这个很关键哦
mBluetoothEnabler.setSwitch(holder.switch_);
}
// No break, fall through on purpose to update common fields
//同样注意的是这里没有break
//$FALL-THROUGH$
case HEADER_TYPE_NORMAL:
//这里就是把我们每个header对应的icon,title重新设置一下哦。
//这样每个header都可以使用自己独有的资源了,了解了吧,呵呵
holder.icon.setImageResource(header.iconRes);
holder.title.setText(header.getTitle(getContext().getResources()));
CharSequence summary = header.getSummary(getContext().getResources());
if (!TextUtils.isEmpty(summary)) {
holder.summary.setVisibility(View.VISIBLE);
holder.summary.setText(summary);
} else {
holder.summary.setVisibility(View.GONE);
}
break;
}
//把这个view返回就可以显示了
return view;
}

1.2.1 getHeaderType

这个函数用于得到不同header的类型,我们关注的蓝牙是有一个开关的。这个其实从上面图1也是可以看出来的,只有wifi和蓝牙后面有一个开关的按钮,我们来看具体的代码:

static int getHeaderType(Header header) {
if (header.fragment == null && header.intent == null) {
return HEADER_TYPE_CATEGORY;
} else if (header.id == R.id.wifi_settings || header.id == R.id.bluetooth_settings) {
//wifi和蓝牙就是switch的类型
return HEADER_TYPE_SWITCH;
} else {
return HEADER_TYPE_NORMAL;
}
}

1.2.2 bluetoothEnabler的setSwitch分析

这个函数的大概作用就是为了把我们ui上的switch和bluetoothEnabler相关联,这样我们在ui上点击这个开关的时候才能真正地去打开/关闭蓝牙。具体代码如下:

public void setSwitch(Switch switch_) {
//已经关联过了,就不需要再次关联了
if (mSwitch == switch_) return;
//把原来开关的监听先清除掉
mSwitch.setOnCheckedChangeListener(null);
mSwitch = switch_;
//这里把开关的操作和自身关联起来,这样你的点击才会真正地起作用
mSwitch.setOnCheckedChangeListener(this);
//得到当前蓝牙的状态
//整个这个地方的state是在开机后所做的操作来实现的,我们在之前的文章中有详细介绍过
int bluetoothState = BluetoothAdapter.STATE_OFF;
if (mLocalAdapter != null) bluetoothState = mLocalAdapter.getBluetoothState();
boolean isOn = bluetoothState == BluetoothAdapter.STATE_ON;
boolean isOff = bluetoothState == BluetoothAdapter.STATE_OFF;
//若是当前蓝牙是打开的,这里就会把开关移到打开的那个位置了,所以,我们可以看到,若是蓝牙默认是打开的,ui上开关就是打开的,它的实现就是在这里喽
mSwitch.setChecked(isOn);
if (WirelessSettings.isRadioAllowed(mContext, Settings.System.RADIO_BLUETOOTH)) {
//允许蓝牙,开关肯定是可见的
mSwitch.setEnabled(isOn || isOff);
} else {
//若是不运行蓝牙,这个开关就不可见了
mSwitch.setEnabled(false);
} if (mSupportBtWifiCoexist == false && isWifiAndWifiApStateDisabled() == false) {
//wifi打开了,这里就不能用蓝牙了,当然这个是在wifi和蓝牙不能共存的设置中。。悲催
mSwitch.setChecked(false);
mSwitch.setEnabled(false);
}
}

至此,在打开Settings的时候,我们看到的ui上蓝牙相关的内容已经全部讲解完毕了。回顾一下,总得来说,就是首先有一个header的列表,然后在onBuildHeaders中会把这个列表加载进来,然后根据每个header不同的类型决定是否加入一些别的元素,比如按钮之类的。然后具体关联到bluetooth中去,根据bluetooth当时处于的状态显示对应的按钮状况,如实是否处于打开之类的。大概的流程就是这样了。

若您觉得该文章对您有帮助,请在下面用鼠标轻轻按一下“顶”,哈哈~~·

眼花缭乱的UI,蓝牙位于何方的更多相关文章

  1. ASP.NET Zero--基于令牌的认证&SWAGGER UI

    基于令牌的认证 任何应用程序都可以将应用程序中的任何功能认证和使用为API.例如,您可以创建一个移动应用程序消耗相同的API.在本节中,我们将演示来自Postman的API (Google Chrom ...

  2. ASP.NET MVC掉过的坑_MVC初识及MVC应用程序结构

    APS.Net MVC 浅谈[转] 来自MSDN 点击访问 MVC 理论结构 模型-视图-控制器 (MVC) 体系结构模式将应用程序分成三个主要组件:模型.视图和控制器. ASP.NET MVC 框架 ...

  3. Android 显示原理简介

    作者:yearzhu,2011年进入腾讯公司,从事过Web端及移动端的测试工作,喜爱新鲜事物及新技术,目前在SNG开放平台测试组负责的移动互联SDK的测试工作. 现在越来越多的应用开始重视流畅度方面的 ...

  4. Unity3D之UGUI学习笔记(二):Rect Transform与Anchor

    Rect Transform 我们都知道,Unity3D中所有的GameObject都必须要携带一个Transform组件,且该组件无法移除,那么作为UI显示的GameObject则不是携带Trans ...

  5. Unity3D之UGUI学习笔记(一):UGUI介绍以及Canvas

    UGUI是Unity3D4.6官方提供的UI系统,支持2D和3D UI的开发. Unity3D UI史 OnGUI 在Unity4.6之前,官方提供的是OnGUI函数来开发UI界面,当然问题也比较多, ...

  6. Activity的测量(Measure)、布局(Layout)和绘制(Draw)过程分析

    一个Android应用程序窗口里面包含了很多UI元素,这些UI元素是以树形结构来组织的,即它们存在着父子关系,其中,子UI元素位于父UI元素里面,因此,在绘制一个Android应用程序窗口的UI之前, ...

  7. Android应用程序窗口(Activity)的测量(Measure)、布局(Layout)和绘制(Draw)过程分析

    文章转载至CSDN社区罗升阳的安卓之旅,原文地址:http://blog.csdn.net/luoshengyang/article/details/8372924 在前面一篇文章中,我们分析了And ...

  8. Unity3D专访——真正的面试

    本来想写一系列的,一半的攻击,现在面试的水.人之奸,用大哥的话说,要走新手是做螺丝钉和抹布用的.还有一半是对出出学校的或者是自废武功转3d的朋友们提供一个比較有价值的參考. 只是我时间实在仓促.没有保 ...

  9. Android View绘制和显示原理简介

    现在越来越多的应用开始重视流畅度方面的测试,了解Android应用程序是如何在屏幕上显示的则是基础中的基础,就让我们一起看看小小屏幕中大大的学问.这也是我下篇文章--<Android应用流畅度测 ...

随机推荐

  1. 服务端API的OAuth认证实现

    http://stackoverflow.com/questions/12499602/body-joints-angle-using-kinect?rq=1 新浪微博跟update相关的api已经挂 ...

  2. where can I find source of com.android.internal.R.styleable.AlertDialog_multiChoiceItemLayout?

    I want to modify Alert dialog multi select layout. For my program I want two line multi-select item. ...

  3. bootstrap注意事项(七)图片

    在本章中,我们将学习 Bootstrap 对图片的支持.Bootstrap 提供了三个可对图片应用简单样式的 class: .img-rounded:添加 border-radius:6px 来获得图 ...

  4. C#中的USB库 WinUSB

    NET C#中的USB库WinUSB,的libusb - Win32和的libusb - 1.0.使用公共设备类,应用程序与所有未经修改的操作系统和驱动程序.大量的示例代码. http://sourc ...

  5. SqlServer2008 数据库可疑

    今天遇到数据库可疑,以前都是直接删了还原,这次没有最新的备份文件,一起看看脚本怎么解决 --最好一句句执行,方便看到错误 USE MASTER GO --开启数据库选项"允许更新" ...

  6. TCP/IP协议族

    1.TCP(传输控制协议)/IP(网际协议)协议族是一个网络通讯模型,以及一整个网络传输协议家族,为互联网的基础通讯架构. TCP/IP四层协议的表示方法: 2.TCP/IP参考模型映射到OSI模型: ...

  7. char str[] 与 char *str的区别详细解析

    char* get_str(void) { char str[] = {"abcd"}; return str; } char str[] = {"abcd"} ...

  8. Ubuntu下使用Vi时方向键变乱码 退格键不能使用的解决方法

    要在Ubuntu下编辑一些文件,这就涉及到了vi这个编辑器了.在Ubuntu下,初始使用vi的时候有点问题,就是在编辑模式下使用方向键的时候,并不会使光标移动,而是在命令行中出现[A [B [C [D ...

  9. Android listview 禁止滑动

    listview.setOnTouchListener(new OnTouchListener() { @Override public boolean onTouch(View v, MotionE ...

  10. 读jQuery源码 jQuery.data

    var rbrace = /(?:\{[\s\S]*\}|\[[\s\S]*\])$/, rmultiDash = /([A-Z])/g; function internalData( elem, n ...