您的位置:首页 > 移动开发 > Android开发

Android 6.0 状态栏信号图标分析

2017-08-03 11:32 363 查看
先来一张状态栏的分区图。今天要分析的是信号显示这一小块,就是图中的signal_cluster,对应源码中的View就是SignalClusterView。



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

public class SignalClusterView
extends LinearLayout
implements 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 BroadcastReceiver
implements 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>
*     </
f4da
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()来处理:

@VisibleForTesting
void 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:

@VisibleForTesting
protected 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实现了这个接口:

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


很简单的,更新变量,刷新。apply方法没什么好分析的,就是把SignalClusterView中包含的View全部依据最新状态更新一遍。

这就是updateNoSims()的流程了。我们再看看重头setCurrentSubscriptions方法:

@VisibleForTesting
void setCurrentSubscriptions(List<SubscriptionInfo> subscriptions) {
Collections.sort(subscriptions, new Comparator<SubscriptionInfo>() {
@Override
public 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:

@Override
public 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了。在那边就依据这些状态刷新就好了:

@Override
public 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逻辑也是一样的。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息