在WiFiSettings界面连接WiFi的时候会看到WiFi Preference的summary状态会从正在连接切换到获取ip再切换到已连接,那这状态更新流程是怎么实现的呢?
Settings界面更新
/**
* Called to indicate the list of AccessPoints has been updated and
* getAccessPoints should be called to get the latest information.
*/
@Override
public void onAccessPointsChanged() {
Log.d(TAG, "onAccessPointsChanged (WifiTracker) callback initiated");
updateAccessPointsDelayed();
}
/**
* Updates access points from {@link WifiManager#getScanResults()}. Adds a delay to have
* progress bar displayed before starting to modify APs.
*/
private void updateAccessPointsDelayed() {
// Safeguard from some delayed event handling
if (getActivity() != null && !mIsRestricted && mWifiManager.isWifiEnabled()) {
final View view = getView();
final Handler handler = view.getHandler();
if (handler != null && handler.hasCallbacks(mUpdateAccessPointsRunnable)) {
return;
}
setProgressBarVisible(true);
view.postDelayed(mUpdateAccessPointsRunnable, 300 /* delay milliseconds */);
}
}
private final Runnable mUpdateAccessPointsRunnable = () -> {
updateAccessPointPreferences();
};
private void updateAccessPointPreferences() {
// in case state has changed
if (!mWifiManager.isWifiEnabled()) {
return;
}
// AccessPoints are sorted by the WifiTracker
final List accessPoints = mWifiTracker.getAccessPoints();
if (isVerboseLoggingEnabled()) {
Log.i(TAG, "updateAccessPoints called for: " + accessPoints);
}
boolean hasAvailableAccessPoints = false;
mAccessPointsPreferenceCategory.removePreference(mStatusMessagePreference);
cacheRemoveAllPrefs(mAccessPointsPreferenceCategory);
int index =
configureConnectedAccessPointPreferenceCategory(accessPoints) ? 1 : 0;
int numAccessPoints = accessPoints.size();
for (; index < numAccessPoints; index++) {
AccessPoint accessPoint = accessPoints.get(index);
// Ignore access points that are out of range.
if (accessPoint.isReachable()) {
String key = accessPoint.getKey();
hasAvailableAccessPoints = true;
LongPressAccessPointPreference pref =
(LongPressAccessPointPreference) getCachedPreference(key);
if (pref != null) {
pref.setOrder(index);
continue;
}
LongPressAccessPointPreference preference =
createLongPressAccessPointPreference(accessPoint);
preference.setKey(key);
preference.setOrder(index);
if (mOpenSsid != null && mOpenSsid.equals(accessPoint.getSsidStr())
&& accessPoint.getSecurity() != AccessPoint.SECURITY_NONE) {
if (!accessPoint.isSaved() || isDisabledByWrongPassword(accessPoint)) {
onPreferenceTreeClick(preference);
mOpenSsid = null;
}
}
mAccessPointsPreferenceCategory.addPreference(preference);
accessPoint.setListener(WifiSettings.this);
preference.refresh();
}
}
removeCachedPrefs(mAccessPointsPreferenceCategory);
mAddPreference.setOrder(index);
mAccessPointsPreferenceCategory.addPreference(mAddPreference);
setAdditionalSettingsSummaries();
if (!hasAvailableAccessPoints) {
setProgressBarVisible(true);
Preference pref = new Preference(getPrefContext());
pref.setSelectable(false);
pref.setSummary(R.string.wifi_empty_list_wifi_on);
pref.setOrder(index++);
pref.setKey(PREF_KEY_EMPTY_WIFI_LIST);
mAccessPointsPreferenceCategory.addPreference(pref);
} else {
// Continuing showing progress bar for an additional delay to overlap with animation
getView().postDelayed(mHideProgressBarRunnable, 1700 /* delay millis */);
}
}
可以看到Settings的界面更新是由于调用到onAccessPointsChanged触发的,那看下是什么调用到onAccessPointsChanged的。
/**
* Invokes {@link WifiListenerExecutor#onAccessPointsChanged()} iif {@link #mStaleScanResults}
* is false.
*/
private void conditionallyNotifyListeners() {
if (mStaleScanResults) {
return;
}
mListener.onAccessPointsChanged();
}
/**
* Retrieves latest scan results and wifi configs, then calls
* {@link #updateAccessPoints(List, List)}.
*/
private void fetchScansAndConfigsAndUpdateAccessPoints() {
final List newScanResults = mWifiManager.getScanResults();
if (isVerboseLoggingEnabled()) {
Log.i(TAG, "Fetched scan results: " + newScanResults);
}
List configs = mWifiManager.getConfiguredNetworks();
updateAccessPoints(newScanResults, configs);
}
/** Update the internal list of access points. */
private void updateAccessPoints(final List newScanResults,
List configs) {
// Map configs and scan results necessary to make AccessPoints
final Map configsByKey = new ArrayMap(configs.size());
if (configs != null) {
for (WifiConfiguration config : configs) {
configsByKey.put(AccessPoint.getKey(config), config);
}
}
ArrayMap> scanResultsByApKey =
updateScanResultCache(newScanResults);
WifiConfiguration connectionConfig = null;
if (mLastInfo != null) {
connectionConfig = getWifiConfigurationForNetworkId(mLastInfo.getNetworkId(), configs);
}
// Rather than dropping and reacquiring the lock multiple times in this method, we lock
// once for efficiency of lock acquisition time and readability
synchronized (mLock) {
// Swap the current access points into a cached list for maintaining AP listeners
List cachedAccessPoints;
cachedAccessPoints = new ArrayList<>(mInternalAccessPoints);
ArrayList accessPoints = new ArrayList<>();
final List scoresToRequest = new ArrayList<>();
for (Map.Entry> entry : scanResultsByApKey.entrySet()) {
for (ScanResult result : entry.getValue()) {
NetworkKey key = NetworkKey.createFromScanResult(result);
if (key != null && !mRequestedScores.contains(key)) {
scoresToRequest.add(key);
}
}
AccessPoint accessPoint =
getCachedOrCreate(entry.getValue(), cachedAccessPoints);
if (mLastInfo != null && mLastNetworkInfo != null) {
accessPoint.update(connectionConfig, mLastInfo, mLastNetworkInfo);
}
// Update the matching config if there is one, to populate saved network info
accessPoint.update(configsByKey.get(entry.getKey()));
accessPoints.add(accessPoint);
}
// If there were no scan results, create an AP for the currently connected network (if
// it exists).
// TODO(b/b/73076869): Add support for passpoint (ephemeral) networks
if (accessPoints.isEmpty() && connectionConfig != null) {
AccessPoint activeAp = new AccessPoint(mContext, connectionConfig);
activeAp.update(connectionConfig, mLastInfo, mLastNetworkInfo);
accessPoints.add(activeAp);
scoresToRequest.add(NetworkKey.createFromWifiInfo(mLastInfo));
}
requestScoresForNetworkKeys(scoresToRequest);
for (AccessPoint ap : accessPoints) {
ap.update(mScoreCache, mNetworkScoringUiEnabled, mMaxSpeedLabelScoreCacheAge);
}
// Pre-sort accessPoints to speed preference insertion
Collections.sort(accessPoints);
// Log accesspoints that are being removed
if (DBG()) {
Log.d(TAG, "------ Dumping SSIDs that were not seen on this scan ------");
for (AccessPoint prevAccessPoint : mInternalAccessPoints) {
if (prevAccessPoint.getSsid() == null)
continue;
String prevSsid = prevAccessPoint.getSsidStr();
boolean found = false;
for (AccessPoint newAccessPoint : accessPoints) {
if (newAccessPoint.getSsidStr() != null && newAccessPoint.getSsidStr()
.equals(prevSsid)) {
found = true;
break;
}
}
if (!found)
Log.d(TAG, "Did not find " + prevSsid + " in this scan");
}
Log.d(TAG, "---- Done dumping SSIDs that were not seen on this scan ----");
}
mInternalAccessPoints.clear();
mInternalAccessPoints.addAll(accessPoints);
}
conditionallyNotifyListeners();
}
看下哪边会调用
} else if (WifiManager.SCAN_RESULTS_AVAILABLE_ACTION.equals(action)) {
mStaleScanResults = false;
fetchScansAndConfigsAndUpdateAccessPoints();
} else if (WifiManager.CONFIGURED_NETWORKS_CHANGED_ACTION.equals(action)
|| WifiManager.LINK_CONFIGURATION_CHANGED_ACTION.equals(action)) {
fetchScansAndConfigsAndUpdateAccessPoints();
} else if (WifiManager.NETWORK_STATE_CHANGED_ACTION.equals(action)) {
// TODO(sghuman): Refactor these methods so they cannot result in duplicate
// onAccessPointsChanged updates being called from this intent.
NetworkInfo info = intent.getParcelableExtra(WifiManager.EXTRA_NETWORK_INFO);
updateNetworkInfo(info);
fetchScansAndConfigsAndUpdateAccessPoints();
}
/**
* Synchronously update the list of access points with the latest information.
*
* Intended to only be invoked within {@link #onStart()}.
*/
@MainThread
private void forceUpdate() {
mLastInfo = mWifiManager.getConnectionInfo();
mLastNetworkInfo = mConnectivityManager.getNetworkInfo(mWifiManager.getCurrentNetwork());
fetchScansAndConfigsAndUpdateAccessPoints();
}
/**
* Start tracking wifi networks and scores.
*
*
Registers listeners and starts scanning for wifi networks. If this is not called
* then forceUpdate() must be called to populate getAccessPoints().
*/
@Override
@MainThread
public void onStart() {
// fetch current ScanResults instead of waiting for broadcast of fresh results
forceUpdate();
总结一下
private void updateNetworkInfo(NetworkInfo networkInfo) {
/* Sticky broadcasts can call this when wifi is disabled */
if (!mWifiManager.isWifiEnabled()) {
clearAccessPointsAndConditionallyUpdate();
return;
}
if (networkInfo != null) {
mLastNetworkInfo = networkInfo;
if (DBG()) {
Log.d(TAG, "mLastNetworkInfo set: " + mLastNetworkInfo);
}
if(networkInfo.isConnected() != mConnected.getAndSet(networkInfo.isConnected())) {
mListener.onConnectedChanged();
}
}
WifiConfiguration connectionConfig = null;
mLastInfo = mWifiManager.getConnectionInfo();
if (DBG()) {
Log.d(TAG, "mLastInfo set as: " + mLastInfo);
}
if (mLastInfo != null) {
connectionConfig = getWifiConfigurationForNetworkId(mLastInfo.getNetworkId(),
mWifiManager.getConfiguredNetworks());
}
boolean updated = false;
boolean reorder = false; // Only reorder if connected AP was changed
synchronized (mLock) {
for (int i = mInternalAccessPoints.size() - 1; i >= 0; --i) {
AccessPoint ap = mInternalAccessPoints.get(i);
boolean previouslyConnected = ap.isActive();
if (ap.update(connectionConfig, mLastInfo, mLastNetworkInfo)) {
updated = true;
if (previouslyConnected != ap.isActive()) reorder = true;
}
if (ap.update(mScoreCache, mNetworkScoringUiEnabled, mMaxSpeedLabelScoreCacheAge)) {
reorder = true;
updated = true;
}
}
if (reorder) {
Collections.sort(mInternalAccessPoints);
}
if (updated) {
conditionallyNotifyListeners();
}
}
}
else if (WifiManager.NETWORK_STATE_CHANGED_ACTION.equals(action)) {
// TODO(sghuman): Refactor these methods so they cannot result in duplicate
// onAccessPointsChanged updates being called from this intent.
NetworkInfo info = intent.getParcelableExtra(WifiManager.EXTRA_NETWORK_INFO);
updateNetworkInfo(info);
fetchScansAndConfigsAndUpdateAccessPoints();
} else if (WifiManager.RSSI_CHANGED_ACTION.equals(action)) {
NetworkInfo info =
mConnectivityManager.getNetworkInfo(mWifiManager.getCurrentNetwork());
updateNetworkInfo(info);
}
总结一下
/**
* Clears the access point list and conditionally invokes
* {@link WifiListener#onAccessPointsChanged()} if required (i.e. the list was not already
* empty).
*/
private void clearAccessPointsAndConditionallyUpdate() {
synchronized (mLock) {
if (!mInternalAccessPoints.isEmpty()) {
mInternalAccessPoints.clear();
conditionallyNotifyListeners();
}
}
}
看起来是WiFi关闭的时候更新下界面
/**
* Update all the internal access points rankingScores, badge and metering.
*
* Will trigger a resort and notify listeners of changes if applicable.
*
*
Synchronized on {@link #mLock}.
*/
private void updateNetworkScores() {
synchronized (mLock) {
boolean updated = false;
for (int i = 0; i < mInternalAccessPoints.size(); i++) {
if (mInternalAccessPoints.get(i).update(
mScoreCache, mNetworkScoringUiEnabled, mMaxSpeedLabelScoreCacheAge)) {
updated = true;
}
}
if (updated) {
Collections.sort(mInternalAccessPoints);
conditionallyNotifyListeners();
}
}
}
/**
* Sanity warning: this wipes out mScoreCache, so use with extreme caution
* @param workThread substitute Handler thread, for testing purposes only
*/
@VisibleForTesting
// TODO(sghuman): Remove this method, this needs to happen in a factory method and be passed in
// during construction
void setWorkThread(HandlerThread workThread) {
mWorkThread = workThread;
mWorkHandler = new Handler(workThread.getLooper());
mScoreCache = new WifiNetworkScoreCache(mContext, new CacheListener(mWorkHandler) {
@Override
public void networkCacheUpdated(List networks) {
if (!mRegistered) return;
if (Log.isLoggable(TAG, Log.VERBOSE)) {
Log.v(TAG, "Score cache was updated with networks: " + networks);
}
updateNetworkScores();
}
});
}
这scores wifiStateMachine中也有相关代码,后续梳理下。
private void updateAccessPointPreferences() {
// in case state has changed
if (!mWifiManager.isWifiEnabled()) {
return;
}
// AccessPoints are sorted by the WifiTracker
final List accessPoints = mWifiTracker.getAccessPoints();
if (isVerboseLoggingEnabled()) {
Log.i(TAG, "updateAccessPoints called for: " + accessPoints);
}
boolean hasAvailableAccessPoints = false;
mAccessPointsPreferenceCategory.removePreference(mStatusMessagePreference);
cacheRemoveAllPrefs(mAccessPointsPreferenceCategory);
int index =
configureConnectedAccessPointPreferenceCategory(accessPoints) ? 1 : 0;
int numAccessPoints = accessPoints.size();
for (; index < numAccessPoints; index++) {
AccessPoint accessPoint = accessPoints.get(index);
// Ignore access points that are out of range.
if (accessPoint.isReachable()) {
String key = accessPoint.getKey();
hasAvailableAccessPoints = true;
LongPressAccessPointPreference pref =
(LongPressAccessPointPreference) getCachedPreference(key);
if (pref != null) {
pref.setOrder(index);
continue;
}
LongPressAccessPointPreference preference =
createLongPressAccessPointPreference(accessPoint);
preference.setKey(key);
preference.setOrder(index);
if (mOpenSsid != null && mOpenSsid.equals(accessPoint.getSsidStr())
&& accessPoint.getSecurity() != AccessPoint.SECURITY_NONE) {
if (!accessPoint.isSaved() || isDisabledByWrongPassword(accessPoint)) {
onPreferenceTreeClick(preference);
mOpenSsid = null;
}
}
mAccessPointsPreferenceCategory.addPreference(preference);
accessPoint.setListener(WifiSettings.this);
preference.refresh();
...
}
}
...
}
@NonNull
private LongPressAccessPointPreference createLongPressAccessPointPreference(
AccessPoint accessPoint) {
return new LongPressAccessPointPreference(accessPoint, getPrefContext(), mUserBadgeCache,
false /* forSavedNetworks */, R.drawable.ic_wifi_signal_0, this);
}
触发界面更新的时候会调用AccessPointPreference的refresh()更新Preference
AccessPointPreference.java
/**
* Updates the title and summary; may indirectly call notifyChanged().
*/
public void refresh() {
setTitle(this, mAccessPoint, mForSavedNetworks);
final Context context = getContext();
int level = mAccessPoint.getLevel();
int wifiSpeed = mAccessPoint.getSpeed();
if (level != mLevel || wifiSpeed != mWifiSpeed) {
mLevel = level;
mWifiSpeed = wifiSpeed;
updateIcon(mLevel, context);
notifyChanged();
}
updateBadge(context);
setSummary(mForSavedNetworks ? mAccessPoint.getSavedNetworkSummary()
: mAccessPoint.getSettingsSummary());
mContentDescription = buildContentDescription(getContext(), this /* pref */, mAccessPoint);
}
从这边可以看到summary是从accessPoint中获取的。forSavedNetworks是表示是否是已保存网络界面,WifiSettings中不是,所以调用的是AccessPoint的getSettingsSummary
private String getSettingsSummary(WifiConfiguration config) {
// Update to new summary
StringBuilder summary = new StringBuilder();
if (isActive() && config != null && config.isPasspoint()) {
// This is the active connection on passpoint
summary.append(getSummary(mContext, getDetailedState(),
false, config.providerFriendlyName));
} else if (isActive() && config != null && getDetailedState() == DetailedState.CONNECTED
&& mIsCarrierAp) {
summary.append(String.format(mContext.getString(R.string.connected_via_carrier), mCarrierName));
} else if (isActive()) {
// This is the active connection on non-passpoint network
summary.append(getSummary(mContext, getDetailedState(),
mInfo != null && mInfo.isEphemeral()));
} else if (config != null && config.isPasspoint()
&& config.getNetworkSelectionStatus().isNetworkEnabled()) {
String format = mContext.getString(R.string.available_via_passpoint);
summary.append(String.format(format, config.providerFriendlyName));
} else if (config != null && config.hasNoInternetAccess()) {
int messageID = config.getNetworkSelectionStatus().isNetworkPermanentlyDisabled()
? R.string.wifi_no_internet_no_reconnect
: R.string.wifi_no_internet;
summary.append(mContext.getString(messageID));
} else if (config != null && !config.getNetworkSelectionStatus().isNetworkEnabled()) {
WifiConfiguration.NetworkSelectionStatus networkStatus =
config.getNetworkSelectionStatus();
switch (networkStatus.getNetworkSelectionDisableReason()) {
case WifiConfiguration.NetworkSelectionStatus.DISABLED_AUTHENTICATION_FAILURE:
summary.append(mContext.getString(R.string.wifi_disabled_password_failure));
break;
case WifiConfiguration.NetworkSelectionStatus.DISABLED_BY_WRONG_PASSWORD:
summary.append(mContext.getString(R.string.wifi_check_password_try_again));
break;
case WifiConfiguration.NetworkSelectionStatus.DISABLED_DHCP_FAILURE:
case WifiConfiguration.NetworkSelectionStatus.DISABLED_DNS_FAILURE:
summary.append(mContext.getString(R.string.wifi_disabled_network_failure));
break;
case WifiConfiguration.NetworkSelectionStatus.DISABLED_ASSOCIATION_REJECTION:
summary.append(mContext.getString(R.string.wifi_disabled_generic));
break;
}
} else if (config != null && config.getNetworkSelectionStatus().isNotRecommended()) {
summary.append(mContext.getString(R.string.wifi_disabled_by_recommendation_provider));
} else if (mIsCarrierAp) {
summary.append(String.format(mContext.getString(R.string.available_via_carrier), mCarrierName));
} else if (!isReachable()) { // Wifi out of range
summary.append(mContext.getString(R.string.wifi_not_in_range));
} else { // In range, not disabled.
if (config != null) { // Is saved network
// Last attempt to connect to this failed. Show reason why
switch (config.recentFailure.getAssociationStatus()) {
case WifiConfiguration.RecentFailure.STATUS_AP_UNABLE_TO_HANDLE_NEW_STA:
summary.append(mContext.getString(
R.string.wifi_ap_unable_to_handle_new_sta));
break;
default:
// "Saved"
summary.append(mContext.getString(R.string.wifi_remembered));
break;
}
}
}
if (isVerboseLoggingEnabled()) {
summary.append(WifiUtils.buildLoggingSummary(this, config));
}
if (config != null && (WifiUtils.isMeteredOverridden(config) || config.meteredHint)) {
return mContext.getResources().getString(
R.string.preference_summary_default_combination,
WifiUtils.getMeteredLabel(mContext, config),
summary.toString());
}
// If Speed label and summary are both present, use the preference combination to combine
// the two, else return the non-null one.
if (getSpeedLabel() != null && summary.length() != 0) {
return mContext.getResources().getString(
R.string.preference_summary_default_combination,
getSpeedLabel(),
summary.toString());
} else if (getSpeedLabel() != null) {
return getSpeedLabel();
} else {
return summary.toString();
}
}
最常见的wpa/wpa2 psk应该是走到下面的if里面
else if (isActive()) {
// This is the active connection on non-passpoint network
summary.append(getSummary(mContext, getDetailedState(),
mInfo != null && mInfo.isEphemeral()));
/**
* Return whether this is the active connection.
* For ephemeral connections (networkId is invalid), this returns false if the network is
* disconnected.
*/
public boolean isActive() {
return mNetworkInfo != null &&
(networkId != WifiConfiguration.INVALID_NETWORK_ID ||
mNetworkInfo.getState() != State.DISCONNECTED);
}
public DetailedState getDetailedState() {
if (mNetworkInfo != null) {
return mNetworkInfo.getDetailedState();
}
Log.w(TAG, "NetworkInfo is null, cannot return detailed state");
return null;
}
看到这边就知道了NetworkInfo是WifiStateMachine连接过程中状态机变化发出来的广播带来的网络相关信息,之前其实有看到,WifiTracker有监听WifiManager.NETWORK_STATE_CHANGED_ACTION,其中带有了WiFi 连接状态的消息,会更新到NetworkInfo中,所以Settings summary显示的其实是WifiStateMachine发来的状态消息
WifiTracker
else if (WifiManager.NETWORK_STATE_CHANGED_ACTION.equals(action)) {
// TODO(sghuman): Refactor these methods so they cannot result in duplicate
// onAccessPointsChanged updates being called from this intent.
NetworkInfo info = intent.getParcelableExtra(WifiManager.EXTRA_NETWORK_INFO);
updateNetworkInfo(info);
fetchScansAndConfigsAndUpdateAccessPoints();
}
。。。
if (mLastInfo != null && mLastNetworkInfo != null) {
accessPoint.update(connectionConfig, mLastInfo, mLastNetworkInfo);
}
WiFi连接过程中Preference的summary更新本质上就是将WiFi连接过程中的信息从WifiStateMachine通过广播传递到WifiTracker,之后触发界面更新,界面更新会从广播带来的NetworkInfo中取出状态相关信息并显示出来。