我们在前面已经分析了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当时处于的状态显示对应的按钮状况,如实是否处于打开之类的。大概的流程就是这样了。
若您觉得该文章对您有帮助,请在下面用鼠标轻轻按一下“顶”,哈哈~~·