SystemUI系列文章
Android8.1 MTK平台 SystemUI源码分析之 Notification流程
Android8.1 MTK平台 SystemUI源码分析之 电池时钟刷新
Android 8.1平台SystemUI 导航栏加载流程解析
一、从布局说起
前面的文章分析过,网络信号栏这块属于 system_icon_area,里面包含蓝牙、wifi、VPN、网卡、SIM卡网络类型、
数据流量符号、SIM卡信号格、电池、时钟。
先来看下 system_icon_area 对应的布局文件 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="0dp" /> </LinearLayout>
看到里面的 signal_cluster_view.xml 正是我们要找的信号栏布局文件,内容有点多,下面只截取我们关心的
vendor\mediatek\proprietary\packages\apps\SystemUI\res\layout\signal_cluster_view.xml
<com.android.systemui.statusbar.SignalClusterView xmlns:android="http://schemas.android.com/apk/res/android" android:id="@+id/signal_cluster" android:layout_height="match_parent" android:layout_width="wrap_content" android:gravity="center_vertical" android:orientation="horizontal" android:paddingEnd="@dimen/signal_cluster_battery_padding" > ... vpn ... 网卡 ... wifi 手机信号栏 <LinearLayout android:id="@+id/mobile_signal_group" android:layout_height="wrap_content" android:layout_width="wrap_content" > </LinearLayout> 未插入SIM卡 <FrameLayout android:id="@+id/no_sims_combo" android:layout_height="wrap_content" android:layout_width="wrap_content" android:contentDescription="@string/accessibility_no_sims"> <com.android.systemui.statusbar.AlphaOptimizedImageView android:theme="?attr/lightIconTheme" android:id="@+id/no_sims" android:layout_height="wrap_content" android:layout_width="wrap_content" android:src="@drawable/stat_sys_no_sims" /> <com.android.systemui.statusbar.AlphaOptimizedImageView android:theme="?attr/darkIconTheme" android:id="@+id/no_sims_dark" android:layout_height="wrap_content" android:layout_width="wrap_content" android:src="@drawable/stat_sys_no_sims" android:alpha="0.0" /> </FrameLayout> <View android:id="@+id/wifi_airplane_spacer" android:layout_width="@dimen/status_bar_airplane_spacer_width" android:layout_height="4dp" android:visibility="gone" /> 飞行模式 <ImageView android:id="@+id/airplane" android:layout_height="wrap_content" android:layout_width="wrap_content" /> </com.android.systemui.statusbar.SignalClusterView>
可以看到最外层是自定义 SignalClusterView,xml里包含了 vpn、网卡、wifi、手机信号栏、未插入SIM卡、飞行模式对应的 view,那么接下来看下 SignalClusterView 代码
vendor\mediatek\proprietary\packages\apps\SystemUI\src\com\android\systemui\statusbar\SignalClusterView.java
public class SignalClusterView extends LinearLayout implements NetworkControllerImpl.SignalCallback, SecurityController.SecurityControllerCallback, Tunable,DarkReceiver public SignalClusterView(Context context, AttributeSet attrs, int defStyle) { super(context, attrs, defStyle); Resources res = getResources(); mMobileSignalGroupEndPadding = res.getDimensionPixelSize(R.dimen.mobile_signal_group_end_padding); mMobileDataIconStartPadding = res.getDimensionPixelSize(R.dimen.mobile_data_icon_start_padding); mWideTypeIconStartPadding = res.getDimensionPixelSize(R.dimen.wide_type_icon_start_padding); mSecondaryTelephonyPadding = res.getDimensionPixelSize(R.dimen.secondary_telephony_padding); mEndPadding = res.getDimensionPixelSize(R.dimen.signal_cluster_battery_padding); mEndPaddingNothingVisible = res.getDimensionPixelSize( R.dimen.no_signal_cluster_battery_padding); TypedValue typedValue = new TypedValue(); res.getValue(R.dimen.status_bar_icon_scale_factor, typedValue, true); mIconScaleFactor = typedValue.getFloat(); //网络相关控制器 mNetworkController = Dependency.get(NetworkController.class); //安全相关控制器 mSecurityController = Dependency.get(SecurityController.class); updateActivityEnabled(); /// M: Add for Plugin feature @ { mStatusBarExt = OpSystemUICustomizationFactoryBase.getOpFactory(context) .makeSystemUIStatusBar(context); /// @ } mIsWfcEnable = SystemProperties.get("persist.mtk_wfc_support").equals("1"); }
看到 SignalClusterView 继承自 LinearLayout,实现了 NetworkController、SecurityController(这俩类控制图标的刷新逻辑)
构造方法中通过 Dependency.get() 实例化 NetworkController、SecurityController这俩核心
类,跟进 Dependent 类中大概看了下 get()方法,其实就是通过单例模式来进行管理,里面维护了一
个数据类型为 ArrayMap 的 DependencyProvider 集合对象,通过 put和 get 来存取。
接着回到 SignalClusterView 中看下控件都是怎么初始化的?
@Override protected void onFinishInflate() { super.onFinishInflate(); mVpn = findViewById(R.id.vpn); mEthernetGroup = findViewById(R.id.ethernet_combo); mEthernet = findViewById(R.id.ethernet); mEthernetDark = findViewById(R.id.ethernet_dark); mWifiGroup = findViewById(R.id.wifi_combo); mWifi = findViewById(R.id.wifi_signal); mWifiDark = findViewById(R.id.wifi_signal_dark); mWifiActivityIn = findViewById(R.id.wifi_in); mWifiActivityOut= findViewById(R.id.wifi_out); mAirplane = findViewById(R.id.airplane); mNoSims = findViewById(R.id.no_sims); mNoSimsDark = findViewById(R.id.no_sims_dark); mNoSimsCombo = findViewById(R.id.no_sims_combo); mWifiAirplaneSpacer = findViewById(R.id.wifi_airplane_spacer); mWifiSignalSpacer = findViewById(R.id.wifi_signal_spacer); mMobileSignalGroup = findViewById(R.id.mobile_signal_group); maybeScaleVpnAndNoSimsIcons(); }
这里初始化了一堆刚刚布局文件里的控件,onFinishInflate() 在 xml 布局文件被加载完成后就会调
用,我们看到布局文件中都没有给控件设置对应的 background icon,而且有的 visibility 为
gone,那么信号栏图标是如何设置对应的icon和显示的呢?
二、SignalCluterView 详解
SignalCluterView 中调用频率很高的方法 apply() 就是幕后黑手,通过该方法控制一系列图标的更
新, 然而 SignalCallback 的如下每个回调最终都调用 apply()
1、setWifiIndicators() wifi开关状态、流量上下行
2、setMobileDataIndicators() 手机网络类型、信号强度、流量上下行、volte图标
3、setSubs() SIM卡识别结束
4、setNoSims() 未插入SIM卡状态
5、setEthernetIndicators() 网卡状态
6、setIsAirplaneMode() 飞行模式是否打开
7、setMobileDataEnabled() SIM卡数据流量是否开启
1、apply()
private void apply() { if (mWifiGroup == null) return; //vpn图标 if (mVpnVisible) { if (mLastVpnIconId != mVpnIconId) { setIconForView(mVpn, mVpnIconId); mLastVpnIconId = mVpnIconId; } mIconLogger.onIconShown(SLOT_VPN); mVpn.setVisibility(View.VISIBLE); } else { mIconLogger.onIconHidden(SLOT_VPN); mVpn.setVisibility(View.GONE); } if (DEBUG) Log.d(TAG, String.format("vpn: %s", mVpnVisible ? "VISIBLE" : "GONE")); //网卡图标 if (mEthernetVisible) { if (mLastEthernetIconId != mEthernetIconId) { setIconForView(mEthernet, mEthernetIconId); setIconForView(mEthernetDark, mEthernetIconId); mLastEthernetIconId = mEthernetIconId; } mEthernetGroup.setContentDescription(mEthernetDescription); mIconLogger.onIconShown(SLOT_ETHERNET); mEthernetGroup.setVisibility(View.VISIBLE); } else { mIconLogger.onIconHidden(SLOT_ETHERNET); mEthernetGroup.setVisibility(View.GONE); } if (DEBUG) Log.d(TAG, String.format("ethernet: %s", (mEthernetVisible ? "VISIBLE" : "GONE"))); //wifi图标 if (mWifiVisible) { if (mWifiStrengthId != mLastWifiStrengthId) { setIconForView(mWifi, mWifiStrengthId); setIconForView(mWifiDark, mWifiStrengthId); mLastWifiStrengthId = mWifiStrengthId; } mIconLogger.onIconShown(SLOT_WIFI); mWifiGroup.setContentDescription(mWifiDescription); mWifiGroup.setVisibility(View.VISIBLE); } else { mIconLogger.onIconHidden(SLOT_WIFI); mWifiGroup.setVisibility(View.GONE); } if (DEBUG) Log.d(TAG, String.format("wifi: %s sig=%d", (mWifiVisible ? "VISIBLE" : "GONE"), mWifiStrengthId)); //wifi数据上下行图标 mWifiActivityIn.setVisibility(mWifiIn ? View.VISIBLE : View.GONE); mWifiActivityOut.setVisibility(mWifiOut ? View.VISIBLE : View.GONE); boolean anyMobileVisible = false; /// M: Support for [Network Type on Statusbar] /// A spacer is set between networktype and WIFI icon @ { if (FeatureOptions.MTK_CTA_SET) { anyMobileVisible = true; } /// @ } //SIM 卡组图标 int firstMobileTypeId = 0; for (PhoneState state : mPhoneStates) { //PhoneState中的另一个apply()方法,对应网络类型、信号格等 if (state.apply(anyMobileVisible)) { if (!anyMobileVisible) { firstMobileTypeId = state.mMobileTypeId; anyMobileVisible = true; } } } if (anyMobileVisible) { mIconLogger.onIconShown(SLOT_MOBILE); } else { mIconLogger.onIconHidden(SLOT_MOBILE); } //飞行模式图标 if (mIsAirplaneMode) { if (mLastAirplaneIconId != mAirplaneIconId) { setIconForView(mAirplane, mAirplaneIconId); mLastAirplaneIconId = mAirplaneIconId; } mAirplane.setContentDescription(mAirplaneContentDescription); mIconLogger.onIconShown(SLOT_AIRPLANE); mAirplane.setVisibility(VISIBLE); } else { mIconLogger.onIconHidden(SLOT_AIRPLANE); mAirplane.setVisibility(View.GONE); } //wifi和飞行模式间隔 if (mIsAirplaneMode && mWifiVisible) { mWifiAirplaneSpacer.setVisibility(View.VISIBLE); } else { mWifiAirplaneSpacer.setVisibility(View.GONE); } if (((anyMobileVisible && firstMobileTypeId != 0) || mNoSimsVisible) && mWifiVisible) { mWifiSignalSpacer.setVisibility(View.VISIBLE); } else { mWifiSignalSpacer.setVisibility(View.GONE); } //未插入SIM卡图标组 if (mNoSimsVisible) { mIconLogger.onIconShown(SLOT_MOBILE); mNoSimsCombo.setVisibility(View.VISIBLE); if (!Objects.equals(mSimDetected, mNoSimsCombo.getTag())) { mNoSimsCombo.setTag(mSimDetected); /// M:alps03596830 Don't show lack of signal when airplane mode is on. if (mSimDetected && !mIsAirplaneMode) { SignalDrawable d = new SignalDrawable(mNoSims.getContext()); d.setDarkIntensity(0); mNoSims.setImageDrawable(d); mNoSims.setImageLevel(SignalDrawable.getEmptyState(4)); SignalDrawable dark = new SignalDrawable(mNoSims.getContext()); dark.setDarkIntensity(1); mNoSimsDark.setImageDrawable(dark); mNoSimsDark.setImageLevel(SignalDrawable.getEmptyState(4)); } else { mNoSims.setImageResource(R.drawable.stat_sys_no_sims); mNoSimsDark.setImageResource(R.drawable.stat_sys_no_sims); } } } else { mIconLogger.onIconHidden(SLOT_MOBILE); mNoSimsCombo.setVisibility(View.GONE); } /// M: Add for Plugin feature @ { mStatusBarExt.setCustomizedNoSimsVisible(mNoSimsVisible); mStatusBarExt.setCustomizedAirplaneView(mNoSimsCombo, mIsAirplaneMode); /// @ } boolean anythingVisible = mNoSimsVisible || mWifiVisible || mIsAirplaneMode || anyMobileVisible || mVpnVisible || mEthernetVisible; setPaddingRelative(0, 0, anythingVisible ? mEndPadding : mEndPaddingNothingVisible, 0); }
通过上面的代码发现 apply 控制了 VPN、网卡、wifi、飞行模式、未插入SIM 这几种图标的显示和隐
藏,我们看到里面有另外一个 state.apply(anyMobileVisible) 用来控制 SIM卡相关的图标,接下
来看下都有哪些图标呢?
2、内部类 PhoneState
PhoneState 是 SignalClusterView 中一个内部类,控制volte、网络类型、数据是否打开、信号格
数、漫游等图标
private class PhoneState { //SIM卡id private final int mSubId; //SIM卡组是否可见 private boolean mMobileVisible = false; //信号格数图标、数据流量是否打开图标(关闭是X,打开是网络类型小图标4G/3G) private int mMobileStrengthId = 0, mMobileTypeId = 0; ///M: Add for [Network Type and volte on Statusbar] //网络类型图标 private int mNetworkIcon = 0; //volte图标 private int mVolteIcon = 0; private int mLastMobileStrengthId = -1; private int mLastMobileTypeId = -1; private boolean mIsMobileTypeIconWide; //网络类型描述,运营商类型,或只能拨打紧急号码 private String mMobileDescription, mMobileTypeDescription; //整个PhoneState根本局,SIM信号栏组 private ViewGroup mMobileGroup; //信号格控件、流量图标控件、是否漫游控件 private ImageView mMobile, mMobileDark, mMobileType, mMobileRoaming; public boolean mRoaming; //手机流量上下行控件 private ImageView mMobileActivityIn; private ImageView mMobileActivityOut; public boolean mActivityIn; public boolean mActivityOut; /// M: Add for new features @ { // Add for [Network Type and volte on Statusbar] //网络类型控件 private ImageView mNetworkType; //volte控件 private ImageView mVolteType; private boolean mIsWfcCase; /// @ } /// M: Add for plugin features. @ { private boolean mDataActivityIn, mDataActivityOut; private ISystemUIStatusBarExt mPhoneStateExt; /// @ } public PhoneState(int subId, Context context) { //加载 mobile_signal_group_ext 布局文件 ViewGroup root = (ViewGroup) LayoutInflater.from(context) .inflate(R.layout.mobile_signal_group_ext, null); /// M: Add data group for plugin feature. @ { mPhoneStateExt = OpSystemUICustomizationFactoryBase.getOpFactory(context) .makeSystemUIStatusBar(context); mPhoneStateExt.addCustomizedView(subId, context, root); /// @ } setViews(root); mSubId = subId; } //控件初始化 public void setViews(ViewGroup root) { mMobileGroup = root; mMobile = root.findViewById(R.id.mobile_signal); mMobileDark = root.findViewById(R.id.mobile_signal_dark); mMobileType = root.findViewById(R.id.mobile_type); ///M: Add for [Network Type and volte on Statusbar] mNetworkType = (ImageView) root.findViewById(R.id.network_type); mVolteType = (ImageView) root.findViewById(R.id.volte_indicator_ext); mMobileRoaming = root.findViewById(R.id.mobile_roaming); mMobileActivityIn = root.findViewById(R.id.mobile_in); mMobileActivityOut = root.findViewById(R.id.mobile_out); // TODO: Remove the 2 instances because now the drawable can handle darkness. mMobile.setImageDrawable(new SignalDrawable(mMobile.getContext())); SignalDrawable drawable = new SignalDrawable(mMobileDark.getContext()); drawable.setDarkIntensity(1); mMobileDark.setImageDrawable(drawable); } public boolean apply(boolean isSecondaryIcon) { Log.e(TAG, "apply() mMobileVisible = " + mMobileVisible + ", mIsAirplaneMode = " + mIsAirplaneMode + ", mIsWfcEnable = " + mIsWfcEnable + ", mIsWfcCase = " + mIsWfcCase + ", mVolteIcon = " + mVolteIcon); if (mMobileVisible && !mIsAirplaneMode) { Log.e(TAG, "apply() into this code 1.. mMobileStrengthId=="+mMobileStrengthId); //设置信号格数 if (mLastMobileStrengthId != mMobileStrengthId) { mMobile.getDrawable().setLevel(mMobileStrengthId); mMobileDark.getDrawable().setLevel(mMobileStrengthId); mLastMobileStrengthId = mMobileStrengthId; } //设置流量是否打开 if (mLastMobileTypeId != mMobileTypeId) { if (!mPhoneStateExt.disableHostFunction()) { mMobileType.setImageResource(mMobileTypeId); } mLastMobileTypeId = mMobileTypeId; } mMobileGroup.setContentDescription(mMobileTypeDescription + " " + mMobileDescription); mMobileGroup.setVisibility(View.VISIBLE); showViewInWfcCase(); } else { if (mIsAirplaneMode && (mIsWfcEnable && mVolteIcon != 0)) { Log.e(TAG, "apply() into this code 2.."); /// M:Bug fix for show vowifi icon in flight mode mMobileGroup.setVisibility(View.VISIBLE); hideViewInWfcCase(); } else { Log.e(TAG, "apply() into this code 3.."); if (DEBUG) { Log.d(TAG, "setVisibility as GONE, this = " + this + ", mMobileVisible = " + mMobileVisible + ", mIsAirplaneMode = " + mIsAirplaneMode + ", mIsWfcEnable = " + mIsWfcEnable + ", mVolteIcon = " + mVolteIcon); } mMobileGroup.setVisibility(View.GONE); } } /// M: Set all added or customised view. @ { //更新网络类型和volte图标 setCustomizeViewProperty(); /// @ } // When this isn't next to wifi, give it some extra padding between the signals. mMobileGroup.setPaddingRelative(isSecondaryIcon ? mSecondaryTelephonyPadding : 0, 0, 0, 0); mMobile.setPaddingRelative( mIsMobileTypeIconWide ? mWideTypeIconStartPadding : mMobileDataIconStartPadding, 0, 0, 0); mMobileDark.setPaddingRelative( mIsMobileTypeIconWide ? mWideTypeIconStartPadding : mMobileDataIconStartPadding, 0, 0, 0); if (true) Log.d(TAG, String.format("mobile: %s sig=%d typ=%d", (mMobileVisible ? "VISIBLE" : "GONE"), mMobileStrengthId, mMobileTypeId)); Log.e(TAG, "mActivityIn="+mActivityIn+" mActivityOut="+mActivityOut); if(!mIsWfcCase) { //更新流量是否打开可见性 mMobileType.setVisibility(mMobileTypeId != 0 ? View.VISIBLE :View.GONE); //漫游图标 mMobileRoaming.setVisibility(mRoaming ? View.VISIBLE : View.GONE); //流量上下行图标 mMobileActivityIn.setVisibility(mActivityIn ? View.VISIBLE : View.GONE); mMobileActivityOut.setVisibility(mActivityOut ? View.VISIBLE : View.GONE); } /// M: Add for support plugin featurs. @ { //可通过op01/2/3等加载SystemUI,从6.0延伸来的 setCustomizedOpViews(); /// @ } return mMobileVisible; } ...... }
PhoneState 中的成员变量较多,我在代码里都已经加了注释了,就不细说了。加载的布局文件为
vendor\mediatek\proprietary\packages\apps\SystemUI\res_ext\layout\mobile_signal_group_ext.xml
<?xml version="1.0" encoding="utf-8"?> <!-- Support [Network Type on Statusbar] The layout to wrap original mobile_signal_group and add image view for show network Type --> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_height="wrap_content" android:layout_width="wrap_content" > <ImageView android:id="@+id/volte_indicator_ext" android:layout_width="wrap_content" android:layout_height="wrap_content" android:visibility="gone" /> <ImageView android:id="@+id/network_type" android:layout_height="wrap_content" android:layout_width="wrap_content" android:visibility="gone" /> <include layout="@layout/mobile_signal_group"/> </LinearLayout>
布局文件中对应了 volte 和当前网络类型大图标, 再包含了 mobile_signal_group,里面就是上面提到的各种控件
vendor\mediatek\proprietary\packages\apps\SystemUI\res\layout\mobile_signal_group.xml
3、state.apply(anyMobileVisible) 调用流程
通过上面的分析可以总结下 信号栏的调用流程
NetworkControllerImpl.java 中注册了SIM卡状态改变广播(ACTION_SIM_STATE_CHANGED),当收
到广播通知后调用到 notifyAllListeners()
通知所有的监听,mobileSignalController、mWifiSignalController、
mEthernetSignalController,分别对应手机信号显示、wifi信号、网卡显示通知
我们看到 mobileSignalController 中的回调 notifyListeners(SignalCallback callback),
在进行一系列的赋值操作后,最终回调到
SignalClusterView 中的 setMobileDataIndicators(),给 PhoneState 的成员变量赋值,
最后通过 apply()进行更新
4、PhoneState创建
通过刚刚对 PhoneState 类的介绍,发现通过构造方法 PhoneState(int subId, Context context) 可获取 PhoneState 对象
搜索当前文件找到在 inflatePhoneState(int subId) 中实例化 PhoneState
@Override public void setSubs(List<SubscriptionInfo> subs) { if (DEBUG) { Log.d(TAG, "setSubs, this = " + this + ", size = " + subs.size() + ", subs = " + subs); } if (hasCorrectSubs(subs)) {//判断SIM卡是否改变 if (DEBUG) { Log.d(TAG, "setSubs, hasCorrectSubs and return"); } return; } mPhoneStates.clear(); if (mMobileSignalGroup != null) { mMobileSignalGroup.removeAllViews();//移除手机信号组中的所有view } final int n = subs.size();//SIM卡数量 Log.d(TAG, "setSubs-clear subsize:" + subs.size() + "mStes" + mPhoneStates + ":" + this); for (int i = 0; i < n; i++) { inflatePhoneState(subs.get(i).getSubscriptionId()); } if (isAttachedToWindow()) { applyIconTint();//图标根据背景色动态变化 } } private PhoneState inflatePhoneState(int subId) { PhoneState state = new PhoneState(subId, mContext); if (mMobileSignalGroup != null) { mMobileSignalGroup.addView(state.mMobileGroup);//添加view到手机信号组中 } Log.d(TAG, "inflatePhoneState add subId:" + subId + ",state" + state + ":" + this); mPhoneStates.add(state);//存储PhoneState 对象,方便快速修改状态 return state; }
当SIM卡插入识别后将回调 setSubs(),先判断SIM卡是否改变,有效则移除 mobileGroup 中已添加
所有view,遍历SIM卡数量,通过 subId 创建 PhoneState,并将对应的view添加到 mobileGroup 中。
然后将 PhoneState对象存储到集合中,方便快速修改状态
这里简单说下 subId subid对应卡,slotid对应卡槽
slotid或者phoneid是指卡槽,双卡机器的卡槽1值为0,卡槽2值为1,依次类推
subid的值从1开始,每插入一个新卡,subId的值就会加1。
插入双卡后数据库中就会有subid值为1和2的两个数据条目,
拔卡插卡交换卡槽后,数据库并不会增加新项,只有插入一张新的sim卡才会增加一条id为3的数据条目
详细的介绍请看这篇 subId、slotId