先来一张状态栏的分区图。今天要分析的是信号显示这一小块,就是图中的signal_cluster,对应源码中的View就是SignalClusterView。

这是一个自定义View,我们看一下他的定义:

public class SignalClusterViewextends LinearLayoutimplements NetworkControllerImpl.SignalCallback,SecurityController.SecurityControllerCallback, Tunable {}

继承了线性布局,实现了三个接口。从接口的名称就知道我们关心的东东肯定在NetworkControllerImpl.SignalCallback里面,(由此可见易懂的名称的重要性!)看看它里面有哪些内容:

public interface SignalCallback {void setWifiIndicators(boolean enabled, IconState statusIcon, IconState qsIcon,boolean activityIn, boolean activityOut, String description);void setMobileDataIndicators(IconState statusIcon, IconState qsIcon, int statusType,int qsType, boolean activityIn, boolean activityOut, int dataActivityId,int mobileActivityId, int stackedDataIcon, int stackedVoiceIcon,String typeContentDescription, String description,boolean isWide, int subId);void setSubs(List<SubscriptionInfo> subs);void setNoSims(boolean show);void setEthernetIndicators(IconState icon);void setIsAirplaneMode(IconState icon);void setMobileDataEnabled(boolean enabled);
}

函数名都很直观,就不翻译了,具体的实现后续用到的时候再来分析吧。

SignalClusterView的关于网络信号相关的更新肯定就是依赖于上面列举的几个接口的回调了。

那又是谁在什么情况下会调用这些接口中的回调函数呢?从接口名NetworkControllerImpl.SignalCallback知道应该是NetworkControllerImpl这个类(如果不是这种 类。接口 的形式,就直接全局搜函数名吧)。继续来看这个类的定义:

public class NetworkControllerImpl extends BroadcastReceiverimplements NetworkController, DemoMode {}

看名称NetworkControllerImpl就纯粹是NetworkController的实现类,但它继承了广播接收器,那我们就要看看它到底处理哪些广播:

IntentFilter filter = new IntentFilter();filter.addAction(WifiManager.RSSI_CHANGED_ACTION);filter.addAction(WifiManager.WIFI_STATE_CHANGED_ACTION);filter.addAction(WifiManager.NETWORK_STATE_CHANGED_ACTION);filter.addAction(TelephonyIntents.ACTION_SIM_STATE_CHANGED);filter.addAction(TelephonyIntents.ACTION_DEFAULT_DATA_SUBSCRIPTION_CHANGED);filter.addAction(TelephonyIntents.ACTION_DEFAULT_VOICE_SUBSCRIPTION_CHANGED);filter.addAction(TelephonyIntents.ACTION_SERVICE_STATE_CHANGED);filter.addAction(TelephonyIntents.SPN_STRINGS_UPDATED_ACTION);filter.addAction(ConnectivityManager.CONNECTIVITY_ACTION);filter.addAction(ConnectivityManager.INET_CONDITION_ACTION);filter.addAction(Intent.ACTION_LOCALE_CHANGED);filter.addAction(Intent.ACTION_AIRPLANE_MODE_CHANGED);

看看,都是些网络状态相关的广播事件。

看到这里,大概就知道状态栏信号图标的刷新原理了:

NetworkControllerImpl接收到网络状态广播,通过SignalCallback接口来刷新SignalClusterView。

当然这只是最大概的总结,中间肯定很多的弯弯绕绕,下面我们就顺着流程来详细走一遍吧。

因为NetworkControllerImpl中处理的广播事件比较多,我们取一个作为例子,就选
TelephonyIntents.ACTION_SIM_STATE_CHANGED这个Action吧,既然是读源码,那就看看源码对这个Action的解释吧:

/*** Broadcast Action: The sim card state has changed.* The intent will have the following extra values:</p>* <dl>*   <dt>phoneName</dt><dd>A string version of the phone name.</dd>*   <dt>ss</dt><dd>The sim state. One of:*     <dl>*       <dt>{@code ABSENT}</dt><dd>SIM card not found</dd>*       <dt>{@code LOCKED}</dt><dd>SIM card locked (see {@code reason})</dd>*       <dt>{@code READY}</dt><dd>SIM card ready</dd>*       <dt>{@code IMSI}</dt><dd>FIXME: what is this state?</dd>*       <dt>{@code LOADED}</dt><dd>SIM card data loaded</dd>*     </dl></dd>*   <dt>reason</dt><dd>The reason why ss is {@code LOCKED}; null otherwise.</dd>*   <dl>*       <dt>{@code PIN}</dt><dd>locked on PIN1</dd>*       <dt>{@code PUK}</dt><dd>locked on PUK1</dd>*       <dt>{@code NETWORK}</dt><dd>locked on network personalization</dd>*   </dl>* </dl>

我们注意到IMS这个,注释竟然是what is this state?哈哈,Google也会这么注释。

源码中接收到这个广播,直接调用了updateMobileController()来处理,简单判断是否有listener监听后,继续转给doUpdateMobileControllers()来处理:

@VisibleForTestingvoid doUpdateMobileControllers() {List<SubscriptionInfo> subscriptions = mSubscriptionManager.getActiveSubscriptionInfoList();if (subscriptions == null) {subscriptions = Collections.emptyList();}// If there have been no relevant changes to any of the subscriptions, we can leave as is.if (hasCorrectMobileControllers(subscriptions)) {// Even if the controllers are correct, make sure we have the right no sims state.// Such as on boot, do not need any controllers, because there are no sims,// but we still need to update the no sim state.updateNoSims();return;}setCurrentSubscriptions(subscriptions);updateNoSims();recalculateEmergency();}

因为自Android 5.0后就支持双卡了。这里先通过SubscriptionManager获取当前活动的SIM卡信息。最下的三个方法分别更新对应卡的状态、无卡状态、紧急呼叫状态。紧急呼叫在状态栏图标上没什么体现,我们这里就不看了。先看看较简单的updateNoSims:

@VisibleForTestingprotected void updateNoSims() {boolean hasNoSims = mHasMobileDataFeature && mMobileSignalControllers.size() == 0;if (hasNoSims != mHasNoSims) {mHasNoSims = hasNoSims;mCallbackHandler.setNoSims(mHasNoSims);}}

就是更新一下mHasNoSims变量,然后通过handler来更新状态,如下:

case MSG_NO_SIM_VISIBLE_CHANGED:for (SignalCallback signalCluster : mSignalCallbacks) {signalCluster.setNoSims(msg.arg1 != 0);}break;

这里就看得到使用到了SignalCallback接口,上文中我们介绍过,是SignalClusterView实现了这个接口:

@Overridepublic void setNoSims(boolean show) {mNoSimsVisible = show && !mBlockMobile;apply();}

很简单的,更新变量,刷新。apply方法没什么好分析的,就是把SignalClusterView中包含的View全部依据最新状态更新一遍。
这就是updateNoSims()的流程了。我们再看看重头setCurrentSubscriptions方法:

@VisibleForTestingvoid setCurrentSubscriptions(List<SubscriptionInfo> subscriptions) {Collections.sort(subscriptions, new Comparator<SubscriptionInfo>() {@Overridepublic int compare(SubscriptionInfo lhs, SubscriptionInfo rhs) {return lhs.getSimSlotIndex() == rhs.getSimSlotIndex()? lhs.getSubscriptionId() - rhs.getSubscriptionId(): lhs.getSimSlotIndex() - rhs.getSimSlotIndex();}});mCurrentSubscriptions = subscriptions;HashMap<Integer, MobileSignalController> cachedControllers =new HashMap<Integer, MobileSignalController>(mMobileSignalControllers);mMobileSignalControllers.clear();final int num = subscriptions.size();for (int i = 0; i < num; i++) {int subId = subscriptions.get(i).getSubscriptionId();// If we have a copy of this controller already reuse it, otherwise make a new one.if (cachedControllers.containsKey(subId)) {mMobileSignalControllers.put(subId, cachedControllers.remove(subId));} else {MobileSignalController controller = new MobileSignalController(mContext, mConfig,mHasMobileDataFeature, mPhone, mCallbackHandler,this, subscriptions.get(i), mSubDefaults, mReceiverHandler.getLooper());mMobileSignalControllers.put(subId, controller);if (subscriptions.get(i).getSimSlotIndex() == 0) {mDefaultSignalController = controller;}if (mListening) {controller.registerListener();}}}if (mListening) {for (Integer key : cachedControllers.keySet()) {if (cachedControllers.get(key) == mDefaultSignalController) {mDefaultSignalController = null;}cachedControllers.get(key).unregisterListener();}}mCallbackHandler.setSubs(subscriptions);notifyAllListeners();// There may be new MobileSignalControllers around, make sure they get the current// inet condition and airplane mode.pushConnectivityToSignals();updateAirplaneMode(true /* force */);}

这个函数前面大段都是依据传入的SubscriptionInfo来更新MobileSignalController。在末尾处连续4句:setSubs的处理方式和setNoSims如出一辙,最终是回调到SignalClusterView,主要是做了清理旧数据的工作;然后notifyAllListener

/*** Forces update of all callbacks on both SignalClusters and* NetworkSignalChangedCallbacks.*/private void notifyAllListeners() {notifyListeners();for (MobileSignalController mobileSignalController : mMobileSignalControllers.values()) {mobileSignalController.notifyListeners();}mWifiSignalController.notifyListeners();mEthernetSignalController.notifyListeners();
}

这列各种notify,最终都是会回调到SignalClusterView的,可见一个SignalClusterView同时被多少个controller管理。这里按照功能划分多个controller,很清晰易读。

我们关注的是信号图标的更新,继续查看mobileSignalController的notify:

@Overridepublic void notifyListeners() {if (mConfig.readIconsFromXml) {generateIconGroup();}MobileIconGroup icons = getIcons();String contentDescription = getStringIfExists(getContentDescription());String dataContentDescription = getStringIfExists(icons.mDataContentDescription);// Show icon in QS when we are connected or need to show roaming.boolean showDataIcon = mCurrentState.dataConnected|| mCurrentState.iconGroup == TelephonyIcons.ROAMING;//这里做过定制,大家关心细节可以自行查看原始代码int SIMEnabled = 1;if(getSimSlotIndex() == 0)SIMEnabled = Settings.System.getInt(mContext.getContentResolver(), Settings.System.SIM1_ENABLE, 1);else if(getSimSlotIndex() == 1)SIMEnabled = Settings.System.getInt(mContext.getContentResolver(), Settings.System.SIM2_ENABLE, 1);IconState statusIcon;//IconState构造函数第一个参数决定了信号图标是否显示。if(SIMEnabled == 0 && !mCurrentState.airplaneMode) {statusIcon = new IconState(true, R.drawable.stat_sys_signal_null_1, contentDescription);} else {statusIcon = new IconState(mCurrentState.enabled && !mCurrentState.airplaneMode,getCurrentIconId(), 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|| mCurrentState.iconGroup == TelephonyIcons.ROAMING;showDataIcon &= mStyle == STATUS_BAR_STYLE_ANDROID_DEFAULT;int typeIcon = showDataIcon ? icons.mDataType : 0;int dataActivityId = showMobileActivity() ? 0 : icons.mActivityId;int mobileActivityId = showMobileActivity() ? icons.mActivityId : 0;mCallbackHandler.setMobileDataIndicators(statusIcon, qsIcon, typeIcon, qsTypeIcon,activityIn, activityOut, dataActivityId, mobileActivityId,icons.mStackedDataIcon, icons.mStackedVoiceIcon,dataContentDescription, description, icons.mIsWide,mSubscriptionInfo.getSubscriptionId());}

嗯,看着一大坨。还是依据变量名我们很快就能猜个大概了:最终的mCallbackHandler.setMobileDataIndicators的后续逻辑也是和setNoSims一样,这里带了炒鸡多的参数,都是和状态栏信号图标的显示效果有关的,上面一大坨就是在获取状态。从这里出去很快就到SignalClusterView了。在那边就依据这些状态刷新就好了:

@Overridepublic void setMobileDataIndicators(IconState statusIcon, IconState qsIcon, int statusType,int qsType, boolean activityIn, boolean activityOut, int dataActivityId,int mobileActivityId, int stackedDataId, int stackedVoiceId,String typeContentDescription, String description, boolean isWide, int subId) {PhoneState state = getState(subId);if (state == null) {return;}state.mMobileVisible = statusIcon.visible && !mBlockMobile;state.mMobileStrengthId = statusIcon.icon;state.mMobileTypeId = statusType;state.mMobileDescription = statusIcon.contentDescription;state.mMobileTypeDescription = typeContentDescription;state.mIsMobileTypeIconWide = statusType != 0 && isWide;state.mDataActivityId = dataActivityId;state.mMobileActivityId = mobileActivityId;state.mStackedDataId = stackedDataId;state.mStackedVoiceId = stackedVoiceId;apply();}

和setNoSims逻辑也是一样的。

Android 6.0 状态栏信号图标分析相关推荐

  1. Android 5.0状态栏通知图标的实现

    Android 5.0状态栏通知图标的实现 我之前的博客文章中有一片是介绍了关于Android5.0 下拉通知栏快捷开关的添加,文章牵扯到一个知识点就是Android 5.0状态栏通知图标的实现.那么 ...

  2. Android 8.0 状态栏信号显示、信号定制

    之前发了下拉通知栏开关修改的一篇文章. 这篇文章呢,主要介绍一下Android状态栏信号图标显示的流程. 便于在Android源生上开发的碰到问题的朋友,希望能对读者有所帮助.内容可能比较长 首先还是 ...

  3. Android 11.0 Settings源码分析 - 主界面加载

    Android 11.0 Settings源码分析 - 主界面加载 本篇主要记录AndroidR Settings源码主界面加载流程,方便后续工作调试其流程. Settings代码路径: packag ...

  4. android 绘制5格电量,Android 怎么把状态栏信号格改为5格

    Android 如何把状态栏信号格改为5格 前言         欢迎大家我分享和推荐好用的代码段~~声明         欢迎转载,但请保留文章原始出处: CSDN:http://www.csdn. ...

  5. Android 8.0系统源码分析--Camera processCaptureResult结果回传源码分析

    相机,从上到下概览一下,真是太大了,上面的APP->Framework->CameraServer->CameraHAL,HAL进程中Pipeline.接各种算法的Node.再往下的 ...

  6. Android8.0 SystemUI 状态栏信号图标

    Android 状态栏信号更新架构 状态栏上信号区域在电池图标的左侧包括 vpn,ethernet,wifi,sim,airplane等,该区域对应的View为SignalClusterView,其负 ...

  7. android灰字体什么意思,Android 6.0状态栏使用灰色文字和图标

    Android StatusBar中的字体和图标默认都是白色的,但是Android在6.0之前是没有办法更改这个颜色, 在Android 6.0中提供了一个SYSTEM_UI_FLAG_LIGHT_S ...

  8. Android 7.0 分屏原理分析

    在以往的Android系统上,所有Activity都是全屏的,如果不设置透明效果,一次只能看到一个Activity界面. 但是从Android N(7.0)版本开始,系统支持了多窗口功能.在有了多窗口 ...

  9. Android 5.0 Usb调试拦截分析及修改

    当我们调试安卓机器时,第一次插上usb线,会弹出一个授权的对话框,(前提是打开了usb调试功能)点击确认,才会允许调试. 如果我们想机器默认就可以调试该怎么做呢? 如果我们想动态拦截,需要用户输入帐号 ...

最新文章

  1. JavaScript的语言标准
  2. session存储在redis/memcache/mysql
  3. Python列表和元祖
  4. 每次digital painting 之后,都可以把作品放到这里,比较好看,也和nft相关度比较大
  5. IIS+php无法上传图片(转载自http://hi.baidu.com/0wem/blog/item/d222db163c3c831e972b4306.html)...
  6. 你们的蛙儿子成马云儿子了 阿里巴巴获得《旅行青蛙》独家代理权
  7. C4D模型库!你想要的模型这里都有
  8. 干货!一文讲清楚电商商品生产和库存的数据分析
  9. Nginx服务器的压缩功能和缓存功能
  10. ensp ftp服务器配置文件,ensp配置ftp服务器,显示连接失败。
  11. C# LINQ查询方法及Select()查询方法应用
  12. Java版扫雷小游戏
  13. 十六进制编辑器--ImHex
  14. 微信小程序连续签到获得积分
  15. 动态域名解析服务(花生壳)
  16. 游戏程序员的学习之路
  17. 结合MACD看现货白银价格走势图
  18. dlib php,图片人脸检测——Dlib版(四)
  19. vivo Xplay的usb调试模式在哪里,打开vivo Xplayusb调试模式的教程
  20. 如何做好提升领导力培训PPT课件?

热门文章

  1. AutoGPT保姆级安装使用教程
  2. C++之回炉再造笔记--问题记录1
  3. 华大HC32L136--低功耗ADC功耗过高问题
  4. 三维动画项目实训① ------(3.17-3.24)
  5. 又一个阿里离职的 P10 大佬
  6. excel 画散点图 怎么设置图片的分辨率_Slynyrd像素画教程:像素画基础教程
  7. Error:UserServiceImpl不是抽象的, 并且未覆盖UserService中的抽象方法
  8. 22下半年:来长沙建第二支团队与所读的30本书(含哲学文学历史书单/笔记)
  9. Java多线程不会的看这里,阿里第三版核心技术手册PDF全彩版
  10. (CVPR 2020)3DSSD: Point-based 3D Single Stage Object Detector