前言
Android系统的默认 Wifi 图标,着实有点不好看,效果如下。
我本着学习的心态,于是我自己改了一波,效果还不错。
信号图标的控制与显示
由 SystemUI之状态图标控制 这篇文章可知,信号图标都是在 StatusBarSignalPolicy 中控制显示的。对于 Wifi 图标,是由 NetworkController 这个接口控制的。
那么现在首先需要分析 NetworkController 对象的创建,然后再来分析 Wifi 图标的显示。
创建 NetworkController
NetworkController 是一个接口,它的实现类为 NetworkControllerImpl,看下它的构造函数
public NetworkControllerImpl(Context context, @Named(BG_LOOPER_NAME) Looper bgLooper,
DeviceProvisionedController deviceProvisionedController) {
// 关于Wifi,主要是创建WifiSignalController对象
this(context, (ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE),
(TelephonyManager) context.getSystemService(Context.TELEPHONY_SERVICE),
(WifiManager) context.getSystemService(Context.WIFI_SERVICE),
SubscriptionManager.from(context), Config.readConfig(context), bgLooper,
new CallbackHandler(),
new AccessPointControllerImpl(context),
new DataUsageController(context),
new SubscriptionDefaults(),
deviceProvisionedController);
// 关于Wifi,主要是监听各种广播
mReceiverHandler.post(mRegisterListeners);
}
首先调用另外一个构造函数,在另外一个构造函数中,与 Wifi 相关的主要操作是创建 WifiSignalController 对象。WifiSignalController 会在 Wifi 状态改变时,通过回调,给监听者提供 wifi 相关信息以及图标。
调用完另外一个构造函数之后,执行了一个注册监听的动作,与 Wifi 相关的监听,主要是监听各种广播。
接下来,就分析这两步,但是由于代码比较繁琐,我只抽出与 wifi 相关的代码来分析。
创建WifiSignalController
先来看下另外一个构造函数中创建 WifiSignalController 的代码
NetworkControllerImpl(Context context, ConnectivityManager connectivityManager,
TelephonyManager telephonyManager, WifiManager wifiManager,
SubscriptionManager subManager, Config config, Looper bgLooper,
CallbackHandler callbackHandler,
AccessPointControllerImpl accessPointController,
DataUsageController dataUsageController,
SubscriptionDefaults defaultsHandler,
DeviceProvisionedController deviceProvisionedController) {
// ...
// 这是一个主线程的Handler
mCallbackHandler = callbackHandler;
// 表示硬件是否支持移动数据连接
mHasMobileDataFeature
=mConnectivityManager.isNetworkSupported(ConnectivityManager.TYPE_MOBILE);
// Wifi信号控制器
// 当wifi状态发生改变时,会通过第三个参数mCallbackHandler进行回调
mWifiSignalController = new WifiSignalController(mContext,mHasMobileDataFeature,
mCallbackHandler, this, mWifiManager);
// ...
}
WifiSignalController 是一个关于 Wifi 的信号控制器,当 Wifi 状态改变时,会通过构造函数传入的第三个参数 mCallbackHandler 进行回调,这一点要记清楚,否则后面分析代码就容易混乱。
那么现在来看下 WifiSignalController 的构造函数做了什么
public WifiSignalController(Context context, boolean hasMobileData,
CallbackHandler callbackHandler, NetworkControllerImpl networkController,
WifiManager wifiManager) {
// 父类构造函数就是保存参数,并且会创建 WifiState mCurrentState,它用来保存wifi状态信息
super("WifiSignalController", context, NetworkCapabilities.TRANSPORT_WIFI,
callbackHandler, networkController);
NetworkScoreManager networkScoreManager =
context.getSystemService(NetworkScoreManager.class);
ConnectivityManager connectivityManager =
context.getSystemService(ConnectivityManager.class);
// 1. 监听网络状态描述的改变
// 网络状态描述: 网络受限, 网络无连接, 网络已连接
mWifiTracker = new WifiStatusTracker(mContext, wifiManager, networkScoreManager,
connectivityManager, this::handleStatusUpdated);
mWifiTracker.setListening(true);
// 是否支持数据连接
mHasMobileData = hasMobileData;
if (wifiManager != null) {
// 2. 监听wifi数据流向的改变
// WifiTrafficStateCallback会更新mCurrentState.activityIn和mCurrentState.activityOut
wifiManager.registerTrafficStateCallback(new WifiTrafficStateCallback(), null);
}
// 3. 初始化图标集
// wifi 技术现在已经更新到第6代,对于不同的wifi技术,使用不同的wifi图标集
initWifiIconGroup();
}
第一步,首先创建 WifiStatusTracker 对象,并对其进行了监听。WifiStatusTracker 是 SettingsLib 库中的一个类,从名字看,它是跟踪 Wifi 状态的,但是通过 setListening()
方法监,最终只更新了 Wifi 的状态的描述,例如网络受限, 网络无连接, 网络已连接。最终,通过 mCurrentState.statusLabel
保存 Wifi 状态的描述。
但是 WifiStatusTracker 功能不止如此,它还会解析与wifi、网络连接有关的广播,并保存状态,这将在后面看到。
SystemUI 为了处理 Wifi 的事件,使用了 SettingsLib 中的 WifiTracker,虽然目的达到了,但是让代码看起来有点僵硬,如果我来优化代码,我会自己创建一个 SystemUI 专用的类来处理 Wifi 的事情。
第二步,向 WifiManager 注册回调,监听 Wifi 数据流向的改变,数据是流入还是输出,或者都有。最终会通过mCurrentState.activityIn
和mCurrentState.activityOut
保存流向的值。
第三步,初始化Wifi图标集。Wifi 技术现在已经更新到第6代,对于不同的 Wifi 技术,Android 系统使用不同的 Wifi 图标集。代码如下
private void initWifiIconGroup() {
mDefaultWifiIconGroup = new IconGroup(
"Wi-Fi Icons", // 图标集的名字
WifiIcons.WIFI_SIGNAL_STRENGTH, // 状态栏所使用的wifi图标
WifiIcons.QS_WIFI_SIGNAL_STRENGTH, // Quick Settings所使用的wifi图标
AccessibilityContentDescriptions.WIFI_CONNECTION_STRENGTH, // 关于已连接wifi图标的描述
WifiIcons.WIFI_NO_NETWORK, // 未知状态时,状态栏上的wifi图标
WifiIcons.QS_WIFI_NO_NETWORK, // 未知状态时,Quick Settings上的wifi图标
WifiIcons.WIFI_NO_NETWORK, // 状态栏上,wifi断开状态图标
WifiIcons.QS_WIFI_NO_NETWORK, // Quick Settings上,wifi断开状态图标
AccessibilityContentDescriptions.WIFI_NO_CONNECTION // 关于断开wifi的描述
);
mWifi4IconGroup = new IconGroup(
"Wi-Fi 4 Icons",
WifiIcons.WIFI_4_SIGNAL_STRENGTH,
WifiIcons.QS_WIFI_4_SIGNAL_STRENGTH,
AccessibilityContentDescriptions.WIFI_CONNECTION_STRENGTH,
WifiIcons.WIFI_NO_NETWORK,
WifiIcons.QS_WIFI_NO_NETWORK,
WifiIcons.WIFI_NO_NETWORK,
WifiIcons.QS_WIFI_NO_NETWORK,
AccessibilityContentDescriptions.WIFI_NO_CONNECTION
);
mWifi5IconGroup = new IconGroup(
"Wi-Fi 5 Icons",
WifiIcons.WIFI_5_SIGNAL_STRENGTH,
WifiIcons.QS_WIFI_5_SIGNAL_STRENGTH,
AccessibilityContentDescriptions.WIFI_CONNECTION_STRENGTH,
WifiIcons.WIFI_NO_NETWORK,
WifiIcons.QS_WIFI_NO_NETWORK,
WifiIcons.WIFI_NO_NETWORK,
WifiIcons.QS_WIFI_NO_NETWORK,
AccessibilityContentDescriptions.WIFI_NO_CONNECTION
);
mWifi6IconGroup = new IconGroup(
"Wi-Fi 6 Icons",
WifiIcons.WIFI_6_SIGNAL_STRENGTH,
WifiIcons.QS_WIFI_6_SIGNAL_STRENGTH,
AccessibilityContentDescriptions.WIFI_CONNECTION_STRENGTH,
WifiIcons.WIFI_NO_NETWORK,
WifiIcons.QS_WIFI_NO_NETWORK,
WifiIcons.WIFI_NO_NETWORK,
WifiIcons.QS_WIFI_NO_NETWORK,
AccessibilityContentDescriptions.WIFI_NO_CONNECTION
);
mCurrentState.iconGroup = mLastState.iconGroup = mDefaultWifiIconGroup;
}
公司的 Wifi 使用的是第4代 Wifi 技术,因此 Wifi 图标会在右下角显示数字4,这就是文章开头看到的效果图。但是对于大部分用户来说,谁会关心他们使用的 Wifi 到底使用哪一代技术呢,他们只关心 Wifi 的信号强度。我看到的市面上的手机,好像都没有显示这个数字。
客户要求修改 Wifi 图标,但是公司只修改了 4 代 Wifi 图标,如果客户使用了 6 代 Wifi,那岂不是打脸了。所以,如果要修改 Wifi 图标,这里全都使用同一种 Wifi 图标。
注册wifi相关的监听
现在来分析 NetworkControllerImpl 构造函数中,注册监听器的代码。
private void registerListeners() {
// ...
IntentFilter filter = new IntentFilter();
// 以下三个为wifi的广播
// 表明wifi信号强度改变
filter.addAction(WifiManager.RSSI_CHANGED_ACTION);
// 表明wifi的状态改变
// 通过EXTRA_WIFI_STATE获取当前状态
// 通过EXTRA_PREVIOUS_WIFI_STATE获取之前的状态
// 状态的值有WIFI_STATE_DISABLED, WIFI_STATE_DISABLING, WIFI_STATE_ENABLED, WIFI_STATE_ENABLING, WIFI_STATE_UNKNOWN(开启或关闭wifi发生错误)
filter.addAction(WifiManager.WIFI_STATE_CHANGED_ACTION);
// 表明Wifi连接状态改变,通过EXTRA_NETWORK_INFO获取NetworkInfo,从而获取Wifi最新状态
filter.addAction(WifiManager.NETWORK_STATE_CHANGED_ACTION);
// ...
// 以下二个表示网络相关的广播
// 表明网络连接改变
filter.addAction(ConnectivityManager.CONNECTIVITY_ACTION);
// 表明网络连接质量改变
// 通过EXTRA_INET_CONDITION获取网络质量
// 通过EXTRA_NETWORK_INFO获取网络状态
filter.addAction(ConnectivityManager.INET_CONDITION_ACTION);
mContext.registerReceiver(this, filter, null, mReceiverHandler);
// ...
}
这里其实有好多的广播,说实话,看得我头都大了。我这里对 Wifi 相关的广播做了分类。前三个表示 Wifi 相关,后二个表示网络连接相关。具体这些广播是什么意思,大家可以看我注释,也可以自己查询API。
由于现在只是在分析 NetworkControllerImpl 的创建,因此这时并不分析如何处理这些广播,留到后面分析。
向 NetworkController 注册监听
现在已经知道 NetworkControllerImpl 创建是做了什么,基本上都是注册与 Wifi 和网络相关监听或者回调。那么现在回到 StatusBarSignalPolicy 类,看看向 NetworkController 注册回调的代码
public StatusBarSignalPolicy(Context context, StatusBarIconController iconController) {
// ...
mNetworkController = Dependency.get(NetworkController.class);
mNetworkController.addCallback(this);
// ...
}
这个实现是在 NewworkControllerImpl 中
public void addCallback(SignalCallback cb) {
// ...
// 1. 获取wifi状态,回调显示Wifi图标
mWifiSignalController.notifyListeners(cb);
// ...
// 2. mCallbackHandler会用mSignalCallbacks保存回调cb
mCallbackHandler.setListening(cb, true);
}
现在只是为了分析注册回调的过程,为了防止不必要的分析,我们假设现在没有连接到 Wifi,那么第一步就不会显示 Wifi 图标,这一步就不分析。
而第二步,其实就是用一个 ArrayList 保存了这个回调。
// frameworks/base/packages/SystemUI/src/com/android/systemui/statusbar/policy/CallbackHandler.java
public void handleMessage(Message msg) {
switch (msg.what) {
// ...
case MSG_ADD_REMOVE_SIGNAL:
// arg1表示是否在监听
if (msg.arg1 != 0) {
mSignalCallbacks.add((SignalCallback) msg.obj);
} else {
mSignalCallbacks.remove((SignalCallback) msg.obj);
}
break;
}
}
就是用 ArrayList mSignalCallbacks
保存了回调。
处理Wifi状态改变
现在一切就绪,假如现在已经连接到一个 Wifi 上,那么就会触发一系列的广播。还记得在前面分析 NetowrkControllerImpl 的创建时,并没有分析广播的处理代码,那么此时就是分析的时机了。
public void onReceive(Context context, Intent intent) {
final String action = intent.getAction();
switch (action) {
// 这里是关于网络连接广播的处理
case ConnectivityManager.CONNECTIVITY_ACTION:
case ConnectivityManager.INET_CONDITION_ACTION:
updateConnectivity();
break;
// ...
// 这里处理wifi相关的广播
default:
int subId = intent.getIntExtra(PhoneConstants.SUBSCRIPTION_KEY,
SubscriptionManager.INVALID_SUBSCRIPTION_ID);
if (SubscriptionManager.isValidSubscriptionId(subId)) {
} else {
// No sub id, must be for the wifi.
mWifiSignalController.handleBroadcast(intent);
}
break;
}
}
这里对于 Wifi 相关的广播的处理,也是分类的,与我刚才的分类一样。处理方式有两种
- 网络连接相关广播,用
updateConnectivity()
处理。 - Wifi 相关广播,交给
WifiSignalController#handleBroadcast()
处理。
处理网络连接广播
先来分析网络连接相关的广播
private void updateConnectivity() {
mConnectedTransports.clear();
mValidatedTransports.clear();
for (NetworkCapabilities nc :
mConnectivityManager.getDefaultNetworkCapabilitiesForUser(mCurrentUserId)) {
for (int transportType : nc.getTransportTypes()) {
// 保存网络类型
mConnectedTransports.set(transportType);
if (nc.hasCapability(NET_CAPABILITY_VALIDATED)) {
// 网络是否验证过
mValidatedTransports.set(transportType);
}
}
}
// 表示网络是否验证过,从后面的代码分析来看,这个变量其实表示 Wifi 是否有网络
mInetCondition = !mValidatedTransports.isEmpty();
// 更新连接状态
pushConnectivityToSignals();
}
private void pushConnectivityToSignals() {
// ...
mWifiSignalController.updateConnectivity(mConnectedTransports, mValidatedTransports);
// ...
}
通过 mConnectedTransports
保存网络类型,通过 mValidatedTransports
保存的值代表网络是否验证过。
最后通过 WifiSignalController#updateConnectivity()
更新网络连接信息,但是这个方法是在基数 SignalController 中实现的。
// frameworks/base/packages/SystemUI/src/com/android/systemui/statusbar/policy/SignalController.java
public void updateConnectivity(BitSet connectedTransports, BitSet validatedTransports) {
// mTransportType表示信号的类型为 Wifi,这里其实表示 Wifi 是否有网络
mCurrentState.inetCondition = validatedTransports.get(mTransportType) ? 1 : 0;
notifyListenersIfNecessary();
}
public void notifyListenersIfNecessary() {
if (isDirty()) {
// 用mLastState保存mCurrentState数据
saveLastState();
notifyListeners();
}
}
public final void notifyListeners() {
// mCallbackHandler是构造函数中传入的
// 这是一个抽象方法,由子类实现
notifyListeners(mCallbackHandler);
}
WifiSignalController 更新了 mCurrentState.inetCondition
的值,表示网络 Wifi 是否有网络。然后调用自己的notifyListeners()
方法,注意这个方法中的参数 mCallbackHandler 是在 NetworkControlerImpl 中创建的 CallbackHanlder对象。
由于基类的方法 notifyListeners()
是一个抽象方法,因此实现是在 WifiSignalController 中,不过这里暂时先打住,因为后面会分析到这段代码。目前你只需要知道,通过这个方法,可以实现 Wifi 图标的显示。
处理 Wifi 状态的广播
关于网络连接的广播,大致已经分析清楚,现在只剩下最后一步了,分析 Wifi 状态广播的处理,这是由 WifiSignalController#handleBroadcast()
处理的
public void handleBroadcast(Intent intent) {
// 利用 WifiTracker 处理广播,并得到 Wifi 状态的各种信息
mWifiTracker.handleBroadcast(intent);
// Wifi 是否开启
mCurrentState.enabled = mWifiTracker.enabled;
// Wifi是否连接
mCurrentState.connected = mWifiTracker.connected;
// Wifi名字
mCurrentState.ssid = mWifiTracker.ssid;
// Wifi 信息强度,单位为dBm
mCurrentState.rssi = mWifiTracker.rssi;
// 实际显示的Wifi信号格数
mCurrentState.level = mWifiTracker.level;
// Wifi 网络状态描述
mCurrentState.statusLabel = mWifiTracker.statusLabel;
// 第几代Wifi技术
mCurrentState.wifiGenerationVersion = mWifiTracker.wifiGeneration;
mCurrentState.isReady = (mWifiTracker.vhtMax8SpatialStreamsSupport
&& mWifiTracker.he8ssCapableAp);
// 根据使用的第几代 Wifi 技术,更新 Wifi 图标集
updateIconGroup();
// 回调通知监听者来显示Wifi图标
notifyListenersIfNecessary();
}
这里通过 WifiTracker 处理了 Wifi 广播,并从 WifiTracker 获取了各种 Wifi 信息。然后根据使用第几代 Wifi 技术,更新了图标集,不同图标集的创建过程,刚刚在前面说过。最后通过 notifyListenersIfNecessary()
回调通知监听者来显示 Wifi 图标。
// frameworks/base/packages/SystemUI/src/com/android/systemui/statusbar/policy/WifiSignalController.java
public void notifyListeners(SignalCallback callback) {
// 显示Wifi图标的情况有两种
// 1. Wifi已经连接
// 2. Wifi-only 设备
boolean wifiVisible = mCurrentState.enabled
&& (mCurrentState.connected || !mHasMobileData);
// Wifi 名字
String wifiDesc = wifiVisible ? mCurrentState.ssid : null;
// 是否显示Wifi名字
boolean ssidPresent = wifiVisible && mCurrentState.ssid != null;
// Accessibility相关
String contentDescription = getStringIfExists(getContentDescription());
// 看来mCurrentState.inetCondition表示是否有网络
if (mCurrentState.inetCondition == 0) {
contentDescription += ("," + mContext.getString(R.string.data_connection_no_internet));
}
// 创建属于status bar的IconState,第二个参数表示 Wifi 图标
IconState statusIcon = new IconState(wifiVisible, getCurrentIconId(), contentDescription);
// 创建属于Quick Settings的IconState
IconState qsIcon = new IconState(mCurrentState.connected, getQsCurrentIconId(),
contentDescription);
// 回调wifi信息,callback是CallbackHandler类型
callback.setWifiIndicators(mCurrentState.enabled, statusIcon, qsIcon,
ssidPresent && mCurrentState.activityIn, ssidPresent && mCurrentState.activityOut,
wifiDesc, mCurrentState.isTransient, mCurrentState.statusLabel);
}
这里解析了Wifi的各种信息,其中 IconState的第二个参数表示 Wifi 图标,这里来看看状态栏上的 Wifi 图标是如何获取的,也就是看看 getCurrentIconId()
方法
public int getCurrentIconId() {
if (mCurrentState.connected) {
return getIcons().mSbIcons[mCurrentState.inetCondition][mCurrentState.level];
} else if (mCurrentState.enabled) {
return getIcons().mSbDiscState;
} else {
return getIcons().mSbNullState;
}
}
getIcons()
获取的是 Wifi 图标集,变量mSbIcons
表示状态栏上 Wifi 图标的二维数组。因此这里就很明白了,根据 Wifi 的各种状态,选取对应的图标。
获取了 Wifi 各种信息后,就把这些信息回调了。注意,这个callback
变量的真实类型了 CallbackHandler,在是 NetworkControllerImpl 中创建的。
// frameworks/base/packages/SystemUI/src/com/android/systemui/statusbar/policy/CallbackHandler.java
public void setWifiIndicators(final boolean enabled, final IconState statusIcon,
final IconState qsIcon, final boolean activityIn, final boolean activityOut,
final String description, boolean isTransient, String secondaryLabel) {
post(new Runnable() {
@Override
public void run() {
// mSignalCallbacks包含了所有向NetworkControllerImpl注册的回调,其中包括StatusBarSignalPolicy注册的回调
for (SignalCallback callback : mSignalCallbacks) {
callback.setWifiIndicators(enabled, statusIcon, qsIcon, activityIn, activityOut,
description, isTransient, secondaryLabel);
}
}
});
}
还记得 mSignalCallbacks 吗,刚才我们在分析 StatusBarSignalPolicy 向 NetworkController 注册回调时,mSignalCallbacks 就是用来保存回调的。现在遍历它,还执行各种回调。那么现在把目光放回到 StatusBarSignalPolicy 中
// frameworks/base/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarSignalPolicy.java
public void setWifiIndicators(boolean enabled, IconState statusIcon, IconState qsIcon,
boolean activityIn, boolean activityOut, String description, boolean isTransient,
String statusLabel) {
boolean visible = statusIcon.visible && !mBlockWifi;
boolean in = activityIn && mActivityEnabled && visible;
boolean out = activityOut && mActivityEnabled && visible;
WifiIconState newState = mWifiIconState.copy();
newState.visible = visible;
newState.resId = statusIcon.icon;
newState.activityIn = in;
newState.activityOut = out;
newState.slot = mSlotWifi;
newState.airplaneSpacerVisible = mIsAirplaneMode;
newState.contentDescription = statusIcon.contentDescription;
MobileIconState first = getFirstMobileState();
newState.signalSpacerVisible = first != null && first.typeId != 0;
// 通过StatusBarIconController在状态栏上显示Wifi图标
updateWifiIconWithState(newState);
mWifiIconState = newState;
}
private void updateWifiIconWithState(WifiIconState state) {
if (state.visible && state.resId > 0) {
mIconController.setSignalIcon(mSlotWifi, state);
mIconController.setIconVisibility(mSlotWifi, true);
} else {
mIconController.setIconVisibility(mSlotWifi, false);
}
}
如果你看过 SystemUI之状态图标控制 这篇文章,那么这里是再简单不过。mIconController 是 StatusBarIconController 类型,它就是控制图标在状态栏上显示的接口。这里就不再深入分析了。
结束
写这样细节分析的文章,确实不好写,因为要讲清楚每个细节。但是为何写这篇文章,一是为了学习有关 Wifi 的一些知识 ,二是我觉得公司对信号图标的修改太挫了,想自己实现更好的。总之,学习到了就是赚到的。