引言
今天我们主要讲的是SystemUI状态栏里面另一个常见的icons——signal icons,该icons主要用于显示Wifi、Sim卡、飞行模式等等状态,表示当前手机连接的信号类型,以达到提示用户的目的。
正文
本文主要从两个方面讲述下signal icon功能,主要分为初始化流程和状态显示流程,
signal icon内容也比较多,本文针对wifi和sim icon讲下,其他icon原理都是差不多的。
话不多说,我们开始吧。
初始化流程
同样的先找到它的布局文件,首先我们看下状态栏的布局文件 status_bar.xml
<com.android.keyguard.AlphaOptimizedLinearLayout android:id="@+id/system_icon_area"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:orientation="horizontal"
>
<include layout="@layout/system_icons" />
<com.android.systemui.statusbar.policy.Clock
android:id="@+id/clock"
android:textAppearance="@style/TextAppearance.StatusBar.Clock"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:singleLine="true"
android:paddingStart="@dimen/status_bar_clock_starting_padding"
android:paddingEnd="@dimen/status_bar_clock_end_padding"
android:gravity="center_vertical|start"
/>
</com.android.keyguard.AlphaOptimizedLinearLayout>
其中我们今天讲的signal icons就藏身于 system_icons.xml 之中
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/system_icons"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:gravity="center_vertical">
<com.android.keyguard.AlphaOptimizedLinearLayout android:id="@+id/statusIcons"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:gravity="center_vertical"
android:orientation="horizontal"/>
<include layout="@layout/signal_cluster_view"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="@dimen/signal_cluster_margin_start"/>
<com.android.systemui.BatteryMeterView android:id="@+id/battery"
android:layout_height="match_parent"
android:layout_width="wrap_content"
/>
</LinearLayout>
layout="@layout/signal_cluster_view"出现了,人如其名,我们简单看下这个layout文
<!-- wifi icon -->
<FrameLayout
android:id="@+id/wifi_combo"
android:layout_height="wrap_content"
android:layout_width="wrap_content"
>
<com.android.systemui.statusbar.AlphaOptimizedImageView
android:theme="?attr/lightIconTheme"
android:id="@+id/wifi_signal"
android:layout_height="wrap_content"
android:layout_width="wrap_content"
/>
<com.android.systemui.statusbar.AlphaOptimizedImageView
android:theme="?attr/darkIconTheme"
android:id="@+id/wifi_signal_dark"
android:layout_height="wrap_content"
android:layout_width="wrap_content"
android:alpha="0.0"
/>
<ImageView
android:id="@+id/wifi_inout"
android:layout_height="wrap_content"
android:layout_width="wrap_content"
/>
</FrameLayout>
这里我们就看到了wifi icon,那么接下来我们再看下sim icon是在哪里
SignalClusterView.java
public PhoneState(int subId, Context context) {
ViewGroup root = (ViewGroup) LayoutInflater.from(context)
.inflate(R.layout.mobile_signal_group, null);
setViews(root);
mSubId = subId;
}
能够看到mobile_signal_group.xml中定义了sim卡信号,我们简单看下layout文件
<!-- sim icon -->
<FrameLayout
android:id="@+id/mobile_signal_single"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
>
<com.android.systemui.statusbar.AnimatedImageView
android:theme="@style/DualToneLightTheme"
android:id="@+id/mobile_signal"
android:layout_height="wrap_content"
android:layout_width="wrap_content"
systemui:hasOverlappingRendering="false"
/>
<com.android.systemui.statusbar.AnimatedImageView
android:theme="@style/DualToneDarkTheme"
android:id="@+id/mobile_signal_dark"
android:layout_height="wrap_content"
android:layout_width="wrap_content"
android:alpha="0.0"
systemui:hasOverlappingRendering="false"
/>
<ImageView
android:id="@+id/mobile_type"
android:layout_height="wrap_content"
android:layout_width="wrap_content"
/>
</FrameLayout>
到这里基本就找到了wifi和sim的初始化了,接下来我们就要看下icon状态显示流程了。
状态显示流程
前面我们讲了初始化,下面我们接着讲显示流程,看下具体的icon是怎么一步步设置到layout中的。
-
wifi显示
首先我们看下wifi icon在SignalClusterView.java中的定义:
ViewGroup mWifiGroup;
ImageView mWifi, mWifiDark;
@Override
protected void onFinishInflate() {// wifi imageview初始化
super.onFinishInflate();
mWifiGroup = findViewById(R.id.wifi_combo);
mWifi = findViewById(R.id.wifi_signal);
mWifiDark = findViewById(R.id.wifi_signal_dark);
....................................................
....................................................
}
private void apply() {
if (mWifiGroup == null) return;
if (mWifiVisible) {
if (mWifiStrengthId != mLastWifiStrengthId) {// 设置wifi icon的地方
setIconForView(mWifi, mWifiStrengthId);
setIconForView(mWifiDark, mWifiStrengthId);
mLastWifiStrengthId = mWifiStrengthId;
}
mWifiGroup.setContentDescription(mWifiDescription);
mWifiGroup.setVisibility(View.VISIBLE);
} else {
mIconLogger.onIconHidden(SLOT_WIFI);
mWifiGroup.setVisibility(View.GONE);
}
....................................................
@Override
public void setWifiIndicators(boolean enabled, IconState statusIcon, IconState qsIcon,
boolean activityIn, boolean activityOut, String description, boolean isTransient) {
mWifiVisible = statusIcon.visible && !mBlockWifi;
mWifiStrengthId = statusIcon.icon;// 这就是wifi icon的resId
mWifiDescription = statusIcon.contentDescription;
mWifiIn = activityIn && mWifiActivityEnabled && mWifiVisible;
mWifiOut = activityOut && mWifiActivityEnabled && mWifiVisible;
mWifiActivityId = getWifiActivityId(mWifiIn, mWifiOut);
apply();
}
能够看出来,mWifiStrengthId就是被放在了statusIcon当中,然后最终设置wifi icon的resId,那么我们继续看下setWifiIndicators调用的地方
@Override
public void notifyListeners(SignalCallback callback) {
// only show wifi in the cluster if connected or if wifi-only
boolean wifiVisible = mCurrentState.enabled
&& (mCurrentState.connected || !mHasMobileData);
String wifiDesc = wifiVisible ? mCurrentState.ssid : null;
boolean ssidPresent = wifiVisible && mCurrentState.ssid != null;
String contentDescription = getStringIfExists(getContentDescription());
if (mCurrentState.inetCondition == 0) {
contentDescription +=
("," + mContext.getString(R.string.accessibility_quick_settings_no_internet));
}
IconState statusIcon = new IconState(wifiVisible, getCurrentIconId(), contentDescription);
IconState qsIcon = new IconState(mCurrentState.connected, getQsCurrentIconId(),
contentDescription);
callback.setWifiIndicators(mCurrentState.enabled, statusIcon, qsIcon,
ssidPresent && mCurrentState.activityIn, ssidPresent && mCurrentState.activityOut,
wifiDesc, mCurrentState.isTransient);
}
public static class IconState {
public final boolean visible;
public final int icon;
public final String contentDescription;
public IconState(boolean visible, int icon, String contentDescription) {
this.visible = visible;
this.icon = icon;
this.contentDescription = contentDescription;
}
public IconState(boolean visible, int icon, int contentDescription,
Context context) {
this(visible, icon, context.getString(contentDescription));
}
}
在WifiSignalController.java中,我们能够看到,new IconState(wifiVisible, getCurrentIconId(), contentDescription)里面的getCurrentIconId决定了wifi icon的resId,接着往下看
SignalController.java
/**
* Gets the signal icon for SB based on current state of connected, enabled, and level.
*/
public int getCurrentIconId() {
if (mCurrentState.connected) {
return getIcons().mSbIcons[mCurrentState.inetCondition][mCurrentState.level];
} else if (mCurrentState.enabled) {
return getIcons().mSbDiscState;
} else {
return getIcons().mSbNullState;
}
}
能够看到,mSbIcons二维数组决定了IconState,而定义它是在WifiSignalController.java中
mCurrentState.iconGroup = mLastState.iconGroup = new IconGroup(
"Wi-Fi Icons",
WifiIcons.WIFI_SIGNAL_STRENGTH,
WifiIcons.QS_WIFI_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
);
static final int[][] WIFI_SIGNAL_STRENGTH = {
{ R.drawable.stat_sys_wifi_signal_0,
R.drawable.stat_sys_wifi_signal_1,
R.drawable.stat_sys_wifi_signal_2,
R.drawable.stat_sys_wifi_signal_3,
R.drawable.stat_sys_wifi_signal_4 },
{ R.drawable.stat_sys_wifi_signal_0_fully,
R.drawable.stat_sys_wifi_signal_1_fully,
R.drawable.stat_sys_wifi_signal_2_fully,
R.drawable.stat_sys_wifi_signal_3_fully,
R.drawable.stat_sys_wifi_signal_4_fully }
};
getCurrentIconId主要是根据
(1)mCurrentState.inetCondition代表wifi数据信号驻网类型,0代表连不上google服务器,1代表能连上
有兴趣的朋友,可以去看下我的这篇文章
SystemUI状态栏wifi和sim icon显示"x"号或者"!"号现象分析
(2)mCurrentState.level代表当前wifi 数据信号的格数
从这个二维数组中取到对应的icon,然后完成设置。
-
sim显示
首先我们也看下sim icon在SignalClusterView.java中的定义:
private ViewGroup mMobileGroup;
private ImageView mMobile, mMobileType;
public void setViews(ViewGroup root) {
mMobileGroup = root;
mMobile = root.findViewById(R.id.mobile_signal);
mMobileType = root.findViewById(R.id.mobile_type);
}
public boolean apply(boolean isSecondaryIcon) {
if (mMobileVisible && !mIsAirplaneMode) {
if (mLastMobileStrengthId != mMobileStrengthId) {// 设置sim信号塔
setIconForView(mMobile, mMobileStrengthId);
mLastMobileStrengthId = mMobileStrengthId;
}
if (mLastMobileTypeId != mMobileTypeId) {// 设置sim信号类型
mMobileType.setImageResource(mMobileTypeId);
mLastMobileTypeId = mMobileTypeId;
}
}
}
@Override
public void setMobileDataIndicators(IconState statusIcon, IconState qsIcon, int statusType,
int qsType, boolean activityIn, boolean activityOut, int dataActivityId,
int stackedDataId, int stackedVoiceId,String typeContentDescription,
String description, boolean isWide, int subId, boolean roaming,
int networkIcon, int volteIcon, boolean showDataIcon) {
PhoneState state = getState(subId);
if (state == null) {
return;
}
state.mMobileVisible = statusIcon.visible && !mBlockMobile;
state.mMobileStrengthId = statusIcon.icon;
state.mMobileTypeId = statusType;
能够看出来,mWifiStrengthId和mMobileTypeId就是被放在了setMobileDataIndicators函数当中,然后最终设置的resId,那么我们继续看下setMobileDataIndicators调用的地方
@Override
public void notifyListeners(SignalCallback callback) {
IconState statusIcon = new IconState(mCurrentState.enabled && !mCurrentState.airplaneMode,
getCurrentIconId(), contentDescription);// sim信号塔icon
int typeIcon = (showDataIcon && mStyle == STATUS_BAR_STYLE_ANDROID_DEFAULT) ? icons.mDataType : 0;// sim数据类型icon
callback.setMobileDataIndicators(statusIcon, qsIcon, typeIcon, qsTypeIcon,
activityIn, activityOut, dataActivityId,
icons.mStackedDataIcon, icons.mStackedVoiceIcon,
dataContentDescription, description, icons.mIsWide,
mSubscriptionInfo.getSubscriptionId(), mCurrentState.roaming,
networkIcon, volteIcon, showDataIcon);
@Override
public int getCurrentIconId() {
if (mCurrentState.iconGroup == TelephonyIcons.CARRIER_NETWORK_CHANGE) {
return SignalDrawable.getCarrierChangeState(getNumLevels());
} else if (mCurrentState.connected) {
int level = mCurrentState.level;
if (mConfig.inflateSignalStrengths) {
level++;
}
if (mConfig.readIconsFromXml) {
return getIcons().mSingleSignalIcon;
} else {
return SignalDrawable.getState(level, getNumLevels(),
mCurrentState.inetCondition == 0);
}
} else if (mCurrentState.enabled) {
if (mConfig.readIconsFromXml) {
return getIcons().mSbDiscState;
} else {
return SignalDrawable.getEmptyState(getNumLevels());
}
} else {
return 0;
}
}
在MobileSignalController.java中,我们能够看到,IconState(mCurrentState.enabled && !mCurrentState.airplaneMode,getCurrentIconId(), contentDescription)里面的getCurrentIconId决定了sim icon,SignalDrawable这个类就是专门负责绘制icon的。
/**
* Produce a mapping of data network types to icon groups for simple and quick use in
* updateTelephony.
*/
private void mapIconSets() {
mNetworkToIconLookup.clear();
mNetworkToIconLookup.put(TelephonyManager.NETWORK_TYPE_EVDO_0, TelephonyIcons.THREE_G);
mNetworkToIconLookup.put(TelephonyManager.NETWORK_TYPE_EVDO_A, TelephonyIcons.THREE_G);
mNetworkToIconLookup.put(TelephonyManager.NETWORK_TYPE_EVDO_B, TelephonyIcons.THREE_G);
mNetworkToIconLookup.put(TelephonyManager.NETWORK_TYPE_EHRPD, TelephonyIcons.THREE_G);
mNetworkToIconLookup.put(TelephonyManager.NETWORK_TYPE_UMTS, TelephonyIcons.THREE_G);
mNetworkToIconLookup.put(TelephonyManager.NETWORK_TYPE_TD_SCDMA, TelephonyIcons.THREE_G);
mNetworkToIconLookup.put(TelephonyManager.NETWORK_TYPE_UNKNOWN,
TelephonyIcons.THREE_G);
mNetworkToIconLookup.put(TelephonyManager.NETWORK_TYPE_EDGE,
TelephonyIcons.THREE_G);
mNetworkToIconLookup.put(TelephonyManager.NETWORK_TYPE_CDMA,
TelephonyIcons.THREE_G);
mNetworkToIconLookup.put(TelephonyManager.NETWORK_TYPE_1xRTT,
TelephonyIcons.THREE_G);
mDefaultIcons = TelephonyIcons.THREE_G;
MobileIconGroup hGroup = TelephonyIcons.THREE_G;
if (mConfig.hspaDataDistinguishable) {
hGroup = TelephonyIcons.H;
}
mNetworkToIconLookup.put(TelephonyManager.NETWORK_TYPE_HSDPA, hGroup);
mNetworkToIconLookup.put(TelephonyManager.NETWORK_TYPE_HSUPA, hGroup);
mNetworkToIconLookup.put(TelephonyManager.NETWORK_TYPE_HSPA, hGroup);
mNetworkToIconLookup.put(TelephonyManager.NETWORK_TYPE_HSPAP, hGroup);
mNetworkToIconLookup.put(TelephonyManager.NETWORK_TYPE_LTE, TelephonyIcons.LTE);
if (mConfig.hideLtePlus) {
mNetworkToIconLookup.put(TelephonyManager.NETWORK_TYPE_LTE_CA,
TelephonyIcons.LTE);
} else {
mNetworkToIconLookup.put(TelephonyManager.NETWORK_TYPE_LTE_CA,
TelephonyIcons.LTE_PLUS);
}
mNetworkToIconLookup.put(TelephonyManager.NETWORK_TYPE_IWLAN, TelephonyIcons.WFC);
}
@Override
public void onDataConnectionStateChanged(int state, int networkType) {
if (DEBUG) {
Log.d(mTag, "onDataConnectionStateChanged: state=" + state
+ " type=" + networkType);
}
mDataState = state;
mDataNetType = networkType;
}
TelephonyIcons.java
static final MobileIconGroup FOUR_G = new MobileIconGroup(
"4G",
null,
null,
AccessibilityContentDescriptions.PHONE_SIGNAL_STRENGTH,
0, 0,
0,
0,
AccessibilityContentDescriptions.PHONE_SIGNAL_STRENGTH[0],
R.string.accessibility_data_connection_4g,
TelephonyIcons.ICON_4G,
true,
TelephonyIcons.QS_DATA_4G
);
而typeIcon = (showDataIcon && mStyle == STATUS_BAR_STYLE_ANDROID_DEFAULT) ? icons.mDataType : 0,则是根据当前onDataConnectionStateChanged回调函数上报的mDataNetType,从以上map中取出对应的值,完成设置的。
到这里,signal icon加载流程已经讲完,如有什么问题欢迎指正。
本文章已经独家授权ApeClub公众号使用。