Android8.1 MTK平台 SystemUI源码分析之 网络信号栏显示刷新

本文主要是介绍Android8.1 MTK平台 SystemUI源码分析之 网络信号栏显示刷新,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

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.SignalClusterViewxmlns: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手机信号栏<LinearLayoutandroid:id="@+id/mobile_signal_group"android:layout_height="wrap_content"android:layout_width="wrap_content"></LinearLayout>未插入SIM卡<FrameLayoutandroid: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.AlphaOptimizedImageViewandroid: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.AlphaOptimizedImageViewandroid: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><Viewandroid:id="@+id/wifi_airplane_spacer"android:layout_width="@dimen/status_bar_airplane_spacer_width"android:layout_height="4dp"android:visibility="gone"/>飞行模式<ImageViewandroid: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卡idprivate 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 modemMobileGroup.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"><ImageViewandroid:id="@+id/volte_indicator_ext"android:layout_width="wrap_content"android:layout_height="wrap_content"android:visibility="gone"/><ImageViewandroid: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

5、SIM卡插入后更新图标流程

PhoneState 创建成功了并存到集合中,当收到 setMobileDataIndicators()回调后

给 PhoneState 成员变量赋值,赋值结束通过apply()更新

还记得上面说过的 apply() 中更新SIM卡图标的逻辑吧,

遍历 mPhoneStates 集合,调用PhoneState的apply()将成员变量值设置给对应的控件

int firstMobileTypeId = 0;for (PhoneState state : mPhoneStates) {if (state.apply(anyMobileVisible)) {if (!anyMobileVisible) {firstMobileTypeId = state.mMobileTypeId;anyMobileVisible = true;}}}

那么 setMobileDataIndicators() 是从哪里回调过来的呢?

分析找到 MobileSignalController 中的 notifyListeners()

vendor\mediatek\proprietary\packages\apps\SystemUI\src\com\android\systemui\statusbar\policy\MobileSignalController.java

@Override
public void notifyListeners(SignalCallback callback) {//获取资源ID组MobileIconGroup icons = getIcons();String contentDescription = getStringIfExists(getContentDescription());String dataContentDescription = getStringIfExists(icons.mDataContentDescription);//移动数据是否开启final boolean dataDisabled = mCurrentState.iconGroup == TelephonyIcons.DATA_DISABLED&& mCurrentState.userSetup;/// M: Customize the signal strength icon id. @ {//当前手机信号格数资源IDint iconId = getCurrentIconId();//用户可自定义的资源ID,该方法将目前的iconId原值赋给了iconID,如需定制可在此修改iconId = mStatusBarExt.getCustomizeSignalStrengthIcon(mSubscriptionInfo.getSubscriptionId(),iconId,mSignalStrength,mDataNetType,mServiceState);/// @ }// Show icon in QS when we are connected or data is disabled.//是否显示移动数据图标boolean showDataIcon = mCurrentState.dataConnected || dataDisabled;//是否显示 mobileGroup、信号格数、SIM卡信息描述IconState statusIcon = new IconState(mCurrentState.enabled && !mCurrentState.airplaneMode,iconId, contentDescription);int qsTypeIcon = 0;IconState qsIcon = null;String description = null;// Only send data sim callbacks to QS.if (mCurrentState.dataSim) {qsTypeIcon = showDataIcon ? icons.mQsDataType : 0;qsIcon = new IconState(mCurrentState.enabled&& !mCurrentState.isEmergency, getQsCurrentIconId(), contentDescription);//状态栏显示只能拨打紧急电话或当前的网络类型description = mCurrentState.isEmergency ? null : mCurrentState.networkName;}//数据下行boolean activityIn = mCurrentState.dataConnected&& !mCurrentState.carrierNetworkChangeMode&& mCurrentState.activityIn;//数据上行boolean activityOut = mCurrentState.dataConnected&& !mCurrentState.carrierNetworkChangeMode&& mCurrentState.activityOut;showDataIcon &= mCurrentState.isDefault || dataDisabled;//移动数据类型资源ID,关闭是X,打开是小的网络类型4G/3G/2Gint typeIcon = showDataIcon ? icons.mDataType : 0;/// M: Add for lwa.typeIcon = mCurrentState.lwaRegState == NetworkTypeUtils.LWA_STATE_CONNCTED&& showDataIcon ? NetworkTypeUtils.LWA_ICON : typeIcon;/** M: Support [Network Type on StatusBar], change the implement methods.* Get the network icon base on service state.* Add one more parameter for network type.* @ { **///当前网络类型资源IDint networkIcon = mCurrentState.networkIcon;/// M: Support volte icon.Bug fix when airplane mode is on go to hide volte icon//VOlTE资源IDint volteIcon = mCurrentState.airplaneMode && !isImsOverWfc()? 0 : mCurrentState.volteIcon;/// M: when data disabled, common show data icon as x, but op do not need show it @ {mStatusBarExt.isDataDisabled(mSubscriptionInfo.getSubscriptionId(), dataDisabled);/// @ }/// M: Customize the data type icon id. @ {//可自定义移动数据类型资源ID(比如常见的上下箭头)typeIcon = mStatusBarExt.getDataTypeIcon(mSubscriptionInfo.getSubscriptionId(),typeIcon,mDataNetType,mCurrentState.dataConnected ? TelephonyManager.DATA_CONNECTED :TelephonyManager.DATA_DISCONNECTED,mServiceState);/// @ }/// M: Customize the network type icon id. @ {//可自定义网络类型资源IDnetworkIcon = mStatusBarExt.getNetworkTypeIcon(mSubscriptionInfo.getSubscriptionId(),networkIcon,mDataNetType,mServiceState);callback.setMobileDataIndicators(statusIcon, qsIcon, typeIcon, networkIcon, volteIcon,qsTypeIcon,activityIn, activityOut, dataContentDescription, description,icons.mIsWide, mSubscriptionInfo.getSubscriptionId(), mCurrentState.roaming);/// M: update plmn label @{mNetworkController.refreshPlmnCarrierLabel();/// @}
}

这个方法比较重要,上面写了简单的注释,接下来我们会详细看下每个资源ID都是如何获取的?

在这之前我们先介绍下几个重要的 Bean 类

vendor\mediatek\proprietary\packages\apps\SystemUI\src\com\android\systemui\statusbar\policy\SignalController.java

State

static class State {boolean connected;boolean enabled;boolean activityIn;boolean activityOut;int level;IconGroup iconGroup;int inetCondition;int rssi;...
}

IconGroup

static class IconGroup {final int[][] mSbIcons;final int[][] mQsIcons;final int[] mContentDesc;final int mSbNullState;final int mQsNullState;final int mSbDiscState;final int mQsDiscState;final int mDiscContentDesc;// For logging.final String mName;....
}

vendor\mediatek\proprietary\packages\apps\SystemUI\src\com\android\systemui\statusbar\policy\MobileSignalController.java

MobileState

static class MobileState extends SignalController.State {String networkName;//当前网络类型String networkNameData;//移动数据网络类型boolean dataSim;boolean dataConnected;//数据是否连接boolean isEmergency;//是否是紧急电话模式boolean airplaneMode;//是否是飞行模式boolean carrierNetworkChangeMode;//SIM卡网络类型是否改变boolean isDefault;boolean userSetup;//是否是用户操作boolean roaming;//是否漫游/// M: Add for 4G+Wint lwaRegState = NetworkTypeUtils.LWA_STATE_UNKNOWN;/// M: For network type big icon.int networkIcon;//网络类型大图标资源ID/// M: Add for data network type.int dataNetType;//移动数据网络类型/// M: Add for op network tower type.int customizedState;//自定义状态/// M: Add for op signal strength tower icon.int customizedSignalStrengthIcon;//自定义信号格资源ID/// M: Add for volte @{int imsRegState = ServiceState.STATE_POWER_OFF;int imsCap;int volteIcon;//volte资源ID......	
}

MobileIconGroup

static class MobileIconGroup extends SignalController.IconGroup {final int mDataContentDescription; // mContentDescriptionDataTypefinal int mDataType;//移动数据网络类型资源IDfinal boolean mIsWide;final int mQsDataType;//下拉快捷访问资源...		
}

好了重要的Bean类介绍完了,接下来又要说一个重要的方法了 updateTelephony()

还是在 MobileSignalController.java 中

private final void updateTelephony() {if (DEBUG && FeatureOptions.LOG_ENABLE) {Log.d(mTag, "updateTelephonySignalStrength: hasService=" + hasService()+ " ss=" + mSignalStrength);}//连接状态,是否在服务中mCurrentState.connected = hasService() && mSignalStrength != null;handleIWLANNetwork();if (mCurrentState.connected) {//SIM 卡信号格数级别 0~4格if (!mSignalStrength.isGsm() && mConfig.alwaysShowCdmaRssi) {mCurrentState.level = mSignalStrength.getCdmaLevel();} else {mCurrentState.level = mSignalStrength.getLevel();}/// M: Customize the signal strength level. @ {//客户可自定义mCurrentState.level = mStatusBarExt.getCustomizeSignalStrengthLevel(mCurrentState.level, mSignalStrength, mServiceState);/// @ }}//当前网络类型获取对应的图标组if (mNetworkToIconLookup.indexOfKey(mDataNetType) >= 0) {mCurrentState.iconGroup = mNetworkToIconLookup.get(mDataNetType);} else {mCurrentState.iconGroup = mDefaultIcons;}/// M: Add for data network type.//数据网络类型mCurrentState.dataNetType = mDataNetType;//数据状态mCurrentState.dataConnected = mCurrentState.connected&& mDataState == TelephonyManager.DATA_CONNECTED;/// M: Add for op network tower type.mCurrentState.customizedState = mStatusBarExt.getCustomizeCsState(mServiceState,mCurrentState.customizedState);/// M: Add for op signal strength tower icon.mCurrentState.customizedSignalStrengthIcon = mStatusBarExt.getCustomizeSignalStrengthIcon(mSubscriptionInfo.getSubscriptionId(),mCurrentState.customizedSignalStrengthIcon,mSignalStrength,mDataNetType,mServiceState);mCurrentState.roaming = isRoaming();if (isCarrierNetworkChangeActive()) {mCurrentState.iconGroup = TelephonyIcons.CARRIER_NETWORK_CHANGE;} else if (isDataDisabled()) {//数据未打开,对应xmCurrentState.iconGroup = TelephonyIcons.DATA_DISABLED;}if (isEmergencyOnly() != mCurrentState.isEmergency) {mCurrentState.isEmergency = isEmergencyOnly();mNetworkController.recalculateEmergency();}// Fill in the network name if we think we have it.//当前网络运营商if (mCurrentState.networkName == mNetworkNameDefault && mServiceState != null&& !TextUtils.isEmpty(mServiceState.getOperatorAlphaShort())) {mCurrentState.networkName = mServiceState.getOperatorAlphaShort();}/// M: For network type big icon. 网络类型大图标mCurrentState.networkIcon =NetworkTypeUtils.getNetworkTypeIcon(mServiceState, mConfig, hasService());/// M: For volte type icon. volte图标mCurrentState.volteIcon = getVolteIcon();//通知更新,最终回调到notifyListeners()中notifyListenersIfNecessary();
}

基本上获取资源ID的方法都在 updateTelephony()中了,那么都在那里调用了 updateTelephony()?

MobileSignalController中构造方法初始化了 MobilePhoneStateListener 分别监听了

mPhone.listen(mPhoneStateListener,
PhoneStateListener.LISTEN_SERVICE_STATE//服务状态改变,可用、不可用| PhoneStateListener.LISTEN_SIGNAL_STRENGTHS//信号强度改变,用于获取dbm、asu| PhoneStateListener.LISTEN_CALL_STATE//电话状态改变,空闲、来电、通话| PhoneStateListener.LISTEN_DATA_CONNECTION_STATE//数据网络连接状态,网络断开、正在连接中、已连接上| PhoneStateListener.LISTEN_DATA_ACTIVITY//数据上下行状态| PhoneStateListener.LISTEN_CARRIER_NETWORK_CHANGE);//网络状态发送改变class MobilePhoneStateListener extends PhoneStateListener {public MobilePhoneStateListener(int subId, Looper looper) {super(subId, looper);}@Overridepublic void onSignalStrengthsChanged(SignalStrength signalStrength) {...updateTelephony();}@Overridepublic void onServiceStateChanged(ServiceState state) {...updateTelephony();}@Overridepublic void onDataConnectionStateChanged(int state, int networkType) {...updateTelephony();}@Overridepublic void onDataActivity(int direction) {...setActivity(direction);}@Overridepublic void onCarrierNetworkChange(boolean active) {...updateTelephony();}/// M: Add for Plugin feature. @{@Overridepublic void onCallStateChanged(int state, String incomingNumber) {...updateTelephony();}/// @}
};

LISTEN_SIGNAL_STRENGTHS、LISTEN_CALL_STATE、LISTEN_CARRIER_NETWORK_CHANGE

这三个监听应该是我们平常用较多的,好了说了这么久,接下来重要看获取资源ID的具体方法了

5.1、Vlote资源ID

mCurrentState.volteIcon = getVolteIcon();private int getVolteIcon() {int icon = 0;if (isImsOverWfc()) {boolean needShowWfcSysIcon = mStatusBarExt.needShowWfcIcon();if (needShowWfcSysIcon) {icon = NetworkTypeUtils.WFC_ICON;}} else if (isImsOverVoice() && isLteNetWork()) {if (mCurrentState.imsRegState == ServiceState.STATE_IN_SERVICE) {//volte可用icon = NetworkTypeUtils.VOLTE_ICON;} else if(FeatureOptions.MTK_CT_MIXED_VOLTE_SUPPORT &&SIMHelper.isSecondaryCSIMForMixedVolte(mSubscriptionInfo.getSubscriptionId()) &&mCurrentState.imsRegState == ServiceState.STATE_OUT_OF_SERVICE) {if (DEBUG) {Log.d(mTag, "set dis volte icon");}//volte不可用icon = NetworkTypeUtils.VOLTE_DIS_ICON;}}/// M: add for disconnected volte feature. @{mStatusBarExt.setImsRegInfo(mSubscriptionInfo.getSubscriptionId(),mCurrentState.imsRegState, isImsOverWfc(), isImsOverVoice());/// @}return icon;
}

vendor\mediatek\proprietary\packages\apps\SystemUI\src\com\mediatek\systemui\statusbar\networktype\NetworkTypeUtils.java

public static final int VOLTE_ICON = R.drawable.stat_sys_volte;

在这里插入图片描述

5.2、网络类型大图标资源ID

vendor\mediatek\proprietary\packages\apps\SystemUI\src\com\mediatek\systemui\statusbar\networktype\NetworkTypeUtils.java

mCurrentState.networkIcon =NetworkTypeUtils.getNetworkTypeIcon(mServiceState, mConfig, hasService());public static int getNetworkTypeIcon(ServiceState serviceState, Config config,boolean hasService) {if (!hasService) {// Not in service, no network type. 未注册成功,比如废卡、停机卡return 0;}//通过 serviceState 获取当前注册的网络类型int tempNetworkType = getNetworkType(serviceState);Integer iconId = sNetworkTypeIcons.get(tempNetworkType);if (iconId == null) {iconId = tempNetworkType == TelephonyManager.NETWORK_TYPE_UNKNOWN ? 0 :config.showAtLeast3G ? R.drawable.stat_sys_network_type_3g :R.drawable.stat_sys_network_type_g;}return iconId.intValue();
}private static int getNetworkType(ServiceState serviceState) {int type = TelephonyManager.NETWORK_TYPE_UNKNOWN;if (serviceState != null) {type = serviceState.getDataNetworkType() != TelephonyManager.NETWORK_TYPE_UNKNOWN ?serviceState.getDataNetworkType() : serviceState.getVoiceNetworkType();}return type;
}//网络类型-资源ID    4g/3g/2g/e/1x
static final Map<Integer, Integer> sNetworkTypeIcons = new HashMap<Integer, Integer>() {{// For CDMA 3Gput(TelephonyManager.NETWORK_TYPE_EVDO_0, R.drawable.stat_sys_network_type_3g);put(TelephonyManager.NETWORK_TYPE_EVDO_A, R.drawable.stat_sys_network_type_3g);put(TelephonyManager.NETWORK_TYPE_EVDO_B, R.drawable.stat_sys_network_type_3g);put(TelephonyManager.NETWORK_TYPE_EHRPD, R.drawable.stat_sys_network_type_3g);// For CDMA 1xput(TelephonyManager.NETWORK_TYPE_CDMA, R.drawable.stat_sys_network_type_1x);put(TelephonyManager.NETWORK_TYPE_1xRTT, R.drawable.stat_sys_network_type_1x);// Edgeput(TelephonyManager.NETWORK_TYPE_EDGE, R.drawable.stat_sys_network_type_e);// 3Gput(TelephonyManager.NETWORK_TYPE_UMTS, R.drawable.stat_sys_network_type_3g);// For 4Gput(TelephonyManager.NETWORK_TYPE_LTE, R.drawable.stat_sys_network_type_4g);// 3Gput(TelephonyManager.NETWORK_TYPE_HSDPA, R.drawable.stat_sys_network_type_3g);put(TelephonyManager.NETWORK_TYPE_HSUPA, R.drawable.stat_sys_network_type_3g);put(TelephonyManager.NETWORK_TYPE_HSPA, R.drawable.stat_sys_network_type_3g);put(TelephonyManager.NETWORK_TYPE_HSPAP, R.drawable.stat_sys_network_type_3g);put(TelephonyManager.NETWORK_TYPE_IWLAN, 0);}
};

在这里插入图片描述

5.3、移动数据类型资源ID

//mNetworkToIconLookup 和上面的网络类型 Map有点类似,键都是网络类型,
//不同的是,这次的key是上面介绍过的 MobileIconGroup
if (mNetworkToIconLookup.indexOfKey(mDataNetType) >= 0) {mCurrentState.iconGroup = mNetworkToIconLookup.get(mDataNetType);
} else {mCurrentState.iconGroup = mDefaultIcons;
}if (isCarrierNetworkChangeActive()) {mCurrentState.iconGroup = TelephonyIcons.CARRIER_NETWORK_CHANGE;
} else if (isDataDisabled()) {mCurrentState.iconGroup = TelephonyIcons.DATA_DISABLED;
}

vendor\mediatek\proprietary\packages\apps\SystemUI\src\com\android\systemui\statusbar\policy\TelephonyIcons.java

class TelephonyIcons {//***** Data connection icons//状态栏快捷访问,其实和下面的差多不static final int QS_DATA_G = R.drawable.ic_qs_signal_g;static final int QS_DATA_3G = R.drawable.ic_qs_signal_3g;static final int QS_DATA_E = R.drawable.ic_qs_signal_e;static final int QS_DATA_H = R.drawable.ic_qs_signal_h;static final int QS_DATA_1X = R.drawable.ic_qs_signal_1x;static final int QS_DATA_4G = R.drawable.ic_qs_signal_4g;static final int QS_DATA_4G_PLUS = R.drawable.ic_qs_signal_4g_plus;static final int QS_DATA_LTE = R.drawable.ic_qs_signal_lte;static final int QS_DATA_LTE_PLUS = R.drawable.ic_qs_signal_lte_plus;static final int FLIGHT_MODE_ICON = R.drawable.stat_sys_airplane_mode;//此处的图标为小图标,网络类型static final int ICON_LTE = R.drawable.stat_sys_data_fully_connected_lte;static final int ICON_LTE_PLUS = R.drawable.stat_sys_data_fully_connected_lte_plus;static final int ICON_G = R.drawable.stat_sys_data_fully_connected_g;static final int ICON_E = R.drawable.stat_sys_data_fully_connected_e;static final int ICON_H = R.drawable.stat_sys_data_fully_connected_h;static final int ICON_3G = R.drawable.stat_sys_data_fully_connected_3g;static final int ICON_4G = R.drawable.stat_sys_data_fully_connected_4g;static final int ICON_4G_PLUS = R.drawable.stat_sys_data_fully_connected_4g_plus;static final int ICON_1X = R.drawable.stat_sys_data_fully_connected_1x;//流量未打开static final int ICON_DATA_DISABLED = R.drawable.stat_sys_data_disabled;static final int QS_ICON_DATA_DISABLED = R.drawable.ic_qs_data_disabled;...static final MobileIconGroup DATA_DISABLED = new MobileIconGroup("DataDisabled",null,null,AccessibilityContentDescriptions.PHONE_SIGNAL_STRENGTH,0, 0,0,0,AccessibilityContentDescriptions.PHONE_SIGNAL_STRENGTH[0],R.string.accessibility_cell_data_off,TelephonyIcons.ICON_DATA_DISABLED,//这个值对应的就是 移动数据类型资源IDfalse,TelephonyIcons.QS_ICON_DATA_DISABLED);}

MobileIconGroup 的倒数第三个参数就是 移动数据类型资源ID

在这里插入图片描述

5.4、信号格数资源ID

if (mCurrentState.connected) {if (!mSignalStrength.isGsm() && mConfig.alwaysShowCdmaRssi) {mCurrentState.level = mSignalStrength.getCdmaLevel();} else {mCurrentState.level = mSignalStrength.getLevel();}/// M: Customize the signal strength level. @ {mCurrentState.level = mStatusBarExt.getCustomizeSignalStrengthLevel(mCurrentState.level, mSignalStrength, mServiceState);/// @ }}

信号格数对应的是 SignalDrawable,通过 setLevel()来控制显示几格,其实以上的大部分资源ID都是

通过 Vector 标签绘制而来的,里面都是一堆 path,开始看可能会觉得很迷糊,可以把xml文件拷贝到

AS中进行预览,再学上一些基础语法就可对简单的图形进行自定义修改。比方说6.0的信号格数是通过

vector 绘制的,格与格之间是有间隔,而8.1是通过 SignalDrawable绘制,是一个填满的三角形

在这里插入图片描述
修改前样式
在这里插入图片描述
修改后样式

来看下 mSignalStrength.getLevel() 方法

frameworks/base/telephony/java/android/telephony/SignalStrength.java

    public int getLevel() {int level = 0;if (isGsm) { //移动或联通卡level = getLteLevel(); //首先获取4G信号格if (level == SIGNAL_STRENGTH_NONE_OR_UNKNOWN) { //未获取到level = getTdScdmaLevel(); //获取移动或联通的3G信号格if (level == SIGNAL_STRENGTH_NONE_OR_UNKNOWN) {//仍然未获取level = getGsmLevel(); //获取移动或联通的2G信号格}}} else {//电信int cdmaLevel = getCdmaLevel(); //获取电信2G信号格int evdoLevel = getEvdoLevel(); //获取电信3G信号格if (evdoLevel == SIGNAL_STRENGTH_NONE_OR_UNKNOWN) {/* We don't know evdo, use cdma */level = cdmaLevel;} else if (cdmaLevel == SIGNAL_STRENGTH_NONE_OR_UNKNOWN) {/* We don't know cdma, use evdo */level = evdoLevel;} else {/* We know both, use the lowest level */level = cdmaLevel < evdoLevel ? cdmaLevel : evdoLevel;}}if (DBG) log("getLevel=" + level);return level;
}

在此介绍下手机是几模的配置:GSM是移动和联通公用的band;LTE从编码方式上分为TDD和FDD,从频段上分有各种不同的band

移动:GSM、TDSCDMA、LTE(TDD)

联通:GSM、WCDMA、LTE(FDD)

电信:CDMA、EVDO、LTE(FDD)

因此如果手机支持GSM、WCDMA、TDSCDMA、TDD-LTE、FDD-LTE 这是五模;加上 CDMA、EVDO 就是七模

5.5、漫游资源ID R.drawable.stat_sys_roaming

大写的R

三、总结

信号栏的定制还是很容易的,只要理清楚了控件和对应的回调逻辑,加上日志打印,就能搞定你想要的效果。

四、相关资源

这里附上我定制使用的drawable文件,CSDN有点坑啊,我想传免费的来着,非要给我设置5分,好吧,只好换平台改链接免费下载

在这里插入图片描述

drawable.zip

这篇关于Android8.1 MTK平台 SystemUI源码分析之 网络信号栏显示刷新的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/1095524

相关文章

流媒体平台/视频监控/安防视频汇聚EasyCVR播放暂停后视频画面黑屏是什么原因?

视频智能分析/视频监控/安防监控综合管理系统EasyCVR视频汇聚融合平台,是TSINGSEE青犀视频垂直深耕音视频流媒体技术、AI智能技术领域的杰出成果。该平台以其强大的视频处理、汇聚与融合能力,在构建全栈视频监控系统中展现出了独特的优势。视频监控管理系统EasyCVR平台内置了强大的视频解码、转码、压缩等技术,能够处理多种视频流格式,并以多种格式(RTMP、RTSP、HTTP-FLV、WebS

第10章 中断和动态时钟显示

第10章 中断和动态时钟显示 从本章开始,按照书籍的划分,第10章开始就进入保护模式(Protected Mode)部分了,感觉从这里开始难度突然就增加了。 书中介绍了为什么有中断(Interrupt)的设计,中断的几种方式:外部硬件中断、内部中断和软中断。通过中断做了一个会走的时钟和屏幕上输入字符的程序。 我自己理解中断的一些作用: 为了更好的利用处理器的性能。协同快速和慢速设备一起工作

性能分析之MySQL索引实战案例

文章目录 一、前言二、准备三、MySQL索引优化四、MySQL 索引知识回顾五、总结 一、前言 在上一讲性能工具之 JProfiler 简单登录案例分析实战中已经发现SQL没有建立索引问题,本文将一起从代码层去分析为什么没有建立索引? 开源ERP项目地址:https://gitee.com/jishenghua/JSH_ERP 二、准备 打开IDEA找到登录请求资源路径位置

综合安防管理平台LntonAIServer视频监控汇聚抖动检测算法优势

LntonAIServer视频质量诊断功能中的抖动检测是一个专门针对视频稳定性进行分析的功能。抖动通常是指视频帧之间的不必要运动,这种运动可能是由于摄像机的移动、传输中的错误或编解码问题导致的。抖动检测对于确保视频内容的平滑性和观看体验至关重要。 优势 1. 提高图像质量 - 清晰度提升:减少抖动,提高图像的清晰度和细节表现力,使得监控画面更加真实可信。 - 细节增强:在低光条件下,抖

JAVA智听未来一站式有声阅读平台听书系统小程序源码

智听未来,一站式有声阅读平台听书系统 🌟&nbsp;开篇:遇见未来,从“智听”开始 在这个快节奏的时代,你是否渴望在忙碌的间隙,找到一片属于自己的宁静角落?是否梦想着能随时随地,沉浸在知识的海洋,或是故事的奇幻世界里?今天,就让我带你一起探索“智听未来”——这一站式有声阅读平台听书系统,它正悄悄改变着我们的阅读方式,让未来触手可及! 📚&nbsp;第一站:海量资源,应有尽有 走进“智听

Linux 网络编程 --- 应用层

一、自定义协议和序列化反序列化 代码: 序列化反序列化实现网络版本计算器 二、HTTP协议 1、谈两个简单的预备知识 https://www.baidu.com/ --- 域名 --- 域名解析 --- IP地址 http的端口号为80端口,https的端口号为443 url为统一资源定位符。CSDNhttps://mp.csdn.net/mp_blog/creation/editor

安卓链接正常显示,ios#符被转义%23导致链接访问404

原因分析: url中含有特殊字符 中文未编码 都有可能导致URL转换失败,所以需要对url编码处理  如下: guard let allowUrl = webUrl.addingPercentEncoding(withAllowedCharacters: .urlQueryAllowed) else {return} 后面发现当url中有#号时,会被误伤转义为%23,导致链接无法访问

如何解决线上平台抽佣高 线下门店客流少的痛点!

目前,许多传统零售店铺正遭遇客源下降的难题。尽管广告推广能带来一定的客流,但其费用昂贵。鉴于此,众多零售商纷纷选择加入像美团、饿了么和抖音这样的大型在线平台,但这些平台的高佣金率导致了利润的大幅缩水。在这样的市场环境下,商家之间的合作网络逐渐成为一种有效的解决方案,通过资源和客户基础的共享,实现共同的利益增长。 以最近在上海兴起的一个跨行业合作平台为例,该平台融合了环保消费积分系统,在短

C#实战|大乐透选号器[6]:实现实时显示已选择的红蓝球数量

哈喽,你好啊,我是雷工。 关于大乐透选号器在前面已经记录了5篇笔记,这是第6篇; 接下来实现实时显示当前选中红球数量,蓝球数量; 以下为练习笔记。 01 效果演示 当选择和取消选择红球或蓝球时,在对应的位置显示实时已选择的红球、蓝球的数量; 02 标签名称 分别设置Label标签名称为:lblRedCount、lblBlueCount

Android平台播放RTSP流的几种方案探究(VLC VS ExoPlayer VS SmartPlayer)

技术背景 好多开发者需要遴选Android平台RTSP直播播放器的时候,不知道如何选的好,本文针对常用的方案,做个大概的说明: 1. 使用VLC for Android VLC Media Player(VLC多媒体播放器),最初命名为VideoLAN客户端,是VideoLAN品牌产品,是VideoLAN计划的多媒体播放器。它支持众多音频与视频解码器及文件格式,并支持DVD影音光盘,VCD影