文章目录
写在前面
Android R 有四个 WifiCandidates.CandidateScorer,分别是
- CompatibilityScorer
- ScoreCardBasedScorer
- BubbleFunScorer
- ThroughputScorer
它们在 WifiInjector 的构造函数中被初始化,并通过 mWifiNetworkSelector.registerCandidateScorer 方法被添加到 mWifiNetworkSelector 的 mCandidateScorers 变量中
private final Map<String, WifiCandidates.CandidateScorer> mCandidateScorers = new ArrayMap<>();
在 WifiConnectivityManager 的 handleScanResults 方法中, mWifiNetworkSelector 调用 selectNetwork 方法进行网络选择,其中有这四个评分器的评分环节
// Run all the CandidateScorers
for (WifiCandidates.CandidateScorer candidateScorer : mCandidateScorers.values()) {
WifiCandidates.ScoredCandidate choice;
try {
choice = wifiCandidates.choose(candidateScorer);
} catch (RuntimeException e) {
Log.wtf(TAG, "Exception running a CandidateScorer", e);
continue;
}
}
wifiCandidates.choose(candidateScorer) 做的事情就是
ScoredCandidate choice = candidateScorer.scoreCandidates(candidates);
return choice == null ? ScoredCandidate.NONE : choice;
所以我们主要看这个四个评分器对 scoreCandidates 方法的实现
CompatibilityScorer
/**
* A candidate scorer that attempts to match the previous behavior.
*/
final class CompatibilityScorer implements WifiCandidates.CandidateScorer {
//遍历 candidates 进行 scoreCandidate 操作,返回评分最高的一个
@Override
public ScoredCandidate scoreCandidates(@NonNull Collection<Candidate> candidates) {
ScoredCandidate choice = ScoredCandidate.NONE;
for (Candidate candidate : candidates) {
ScoredCandidate scoredCandidate = scoreCandidate(candidate);
if (scoredCandidate.value > choice.value) {
choice = scoredCandidate;
}
}
// Here we just return the highest scored candidate; we could
// compute a new score, if desired.
return choice;
}
/**
* Calculates an individual candidate's score.
*/
private ScoredCandidate scoreCandidate(Candidate candidate) {
int rssiSaturationThreshold = mScoringParams.getGoodRssi(candidate.getFrequency());
int rssi = Math.min(candidate.getScanRssi(), rssiSaturationThreshold);
int score = (rssi + RSSI_SCORE_OFFSET) * RSSI_SCORE_SLOPE_IS_4;
if (ScanResult.is6GHz(candidate.getFrequency())) {
score += BAND_6GHZ_AWARD_IS_40;
} else if (ScanResult.is5GHz(candidate.getFrequency())) {
score += BAND_5GHZ_AWARD_IS_40;
}
score += (int) (candidate.getLastSelectionWeight() * LAST_SELECTION_AWARD_IS_480);
if (candidate.isCurrentNetwork()) {
// Add both traditional awards, as would be be case with firmware roaming
score += CURRENT_NETWORK_BOOST_IS_16 + SAME_BSSID_AWARD_IS_24;
}
if (!candidate.isOpenNetwork()) {
score += SECURITY_AWARD_IS_80;
}
// To simulate the old strict priority rule, subtract a penalty based on
// which nominator added the candidate.
score -= 1000 * candidate.getNominatorId();
// The old method breaks ties on the basis of RSSI, which we can
// emulate easily since our score does not need to be an integer.
double tieBreaker = candidate.getScanRssi() / 1000.0;
return new ScoredCandidate(score + tieBreaker, 10,
USE_USER_CONNECT_CHOICE, candidate);
}
}
由此可见 CompatibilityScorer 的几个加分项
1.rssi值
2.5G或者6G频段的网络
3.是否是上次选择的网络
4.是否是当前网络
5.是否是开放网络
ScoreCardBasedScorer
/**
* A candidate scorer that uses the scorecard to influence the choice.
*/
final class ScoreCardBasedScorer implements WifiCandidates.CandidateScorer
/**
* Calculates an individual candidate's score.
*/
private ScoredCandidate scoreCandidate(Candidate candidate) {
int rssiSaturationThreshold = mScoringParams.getGoodRssi(candidate.getFrequency());
int rssi = Math.min(candidate.getScanRssi(), rssiSaturationThreshold);
int cutoff = estimatedCutoff(candidate);
int score = (rssi - cutoff) * RSSI_SCORE_SLOPE_IS_4;
if (ScanResult.is6GHz(candidate.getFrequency())) {
score += BAND_6GHZ_AWARD_IS_40;
} else if (ScanResult.is5GHz(candidate.getFrequency())) {
score += BAND_5GHZ_AWARD_IS_40;
}
score += (int) (candidate.getLastSelectionWeight() * LAST_SELECTION_AWARD_IS_480);
if (candidate.isCurrentNetwork()) {
score += CURRENT_NETWORK_BOOST_IS_16 + SAME_BSSID_AWARD_IS_24;
}
if (!candidate.isOpenNetwork()) {
score += SECURITY_AWARD_IS_80;
}
// To simulate the old strict priority rule, subtract a penalty based on
// which nominator added the candidate.
score -= 1000 * candidate.getNominatorId();
return new ScoredCandidate(score, 10,
USE_USER_CONNECT_CHOICE, candidate);
}
private int estimatedCutoff(Candidate candidate) {
int cutoff = -RSSI_SCORE_OFFSET;
int lowest = cutoff - RSSI_RAIL;
int highest = cutoff + RSSI_RAIL;
WifiScoreCardProto.Signal signal = candidate.getEventStatistics(Event.SIGNAL_POLL);
if (signal == null) return cutoff;
if (!signal.hasRssi()) return cutoff;
if (signal.getRssi().getCount() > MIN_POLLS_FOR_SIGNIFICANCE) {
double mean = signal.getRssi().getSum() / signal.getRssi().getCount();
double mean_square = signal.getRssi().getSumOfSquares() / signal.getRssi().getCount();
double variance = mean_square - mean * mean;
double sigma = Math.sqrt(variance);
double value = mean - 2.0 * sigma;
cutoff = (int) Math.min(Math.max(value, lowest), highest);
}
return cutoff;
}
ScoreCardBasedScorer 的加分项和上面的评分器一样,但是多了一个 estimatedCutoff 步骤
BubbleFunScorer
/**
* A CandidateScorer that weights the RSSIs for more compactly-shaped
* regions of selection around access points.
*/
final class BubbleFunScorer implements WifiCandidates.CandidateScorer
/**
* Calculates an individual candidate's score.
*
* Ideally, this is a pure function of the candidate, and side-effect free.
*/
private ScoredCandidate scoreCandidate(Candidate candidate) {
final int rssi = candidate.getScanRssi();
final int rssiEntryThreshold = mScoringParams.getEntryRssi(candidate.getFrequency());
double score = shapeFunction(rssi) - shapeFunction(rssiEntryThreshold);
// If we are below the entry threshold, make the score more negative
if (score < 0.0) score *= 2.0;
// The gain is approximately the derivative of shapeFunction at the given rssi
// This is used to estimate the error
double gain = shapeFunction(rssi + 0.5)
- shapeFunction(rssi - 0.5);
// Prefer 5GHz/6GHz when all are strong, but at the fringes, 2.4 might be better
// Typically the entry rssi is lower for the 2.4 band, which provides the fringe boost
if (ScanResult.is24GHz(candidate.getFrequency())) {
score *= LOW_BAND_FACTOR;
gain *= LOW_BAND_FACTOR;
}
// A recently selected network gets a large boost
score += candidate.getLastSelectionWeight() * LAST_SELECTION_BOOST;
// Hysteresis to prefer staying on the current network.
if (candidate.isCurrentNetwork()) {
score += CURRENT_NETWORK_BOOST;
}
if (!candidate.isOpenNetwork()) {
score += SECURITY_AWARD;
}
return new ScoredCandidate(score, TYPICAL_SCAN_RSSI_STD * gain,
USE_USER_CONNECT_CHOICE, candidate);
}
/**
* Reshapes raw RSSI into a value that varies more usefully for scoring purposes.
*
* The most important aspect of this function is that it is monotone (has
* positive slope). The offset and scale are not important, because the
* calculation above uses differences that cancel out the offset, and
* a rescaling here effects all the candidates' scores in the same way.
* However, we choose to scale things for an overall range of about 100 for
* useful values of RSSI.
*/
private static double unscaledShapeFunction(double rssi) {
return -Math.exp(-rssi * BELS_PER_DECIBEL);
}
private static final double BELS_PER_DECIBEL = 0.1;
private static final double RESCALE_FACTOR = 100.0 / (
unscaledShapeFunction(0.0) - unscaledShapeFunction(-85.0));
private static double shapeFunction(double rssi) {
return unscaledShapeFunction(rssi) * RESCALE_FACTOR;
}
ThroughputScorer
/**
* A candidate scorer that combines RSSI base score and network throughput score.
*/
final class ThroughputScorer implements WifiCandidates.CandidateScorer
/**
* Calculates an individual candidate's score.
*/
private ScoredCandidate scoreCandidate(Candidate candidate) {
int rssiSaturationThreshold = mScoringParams.getSufficientRssi(candidate.getFrequency());
int rssi = Math.min(candidate.getScanRssi(), rssiSaturationThreshold);
int rssiBaseScore = (rssi + RSSI_SCORE_OFFSET) * RSSI_SCORE_SLOPE_IS_4;
int throughputBonusScore = calculateThroughputBonusScore(candidate);
int rssiAndThroughputScore = rssiBaseScore + throughputBonusScore;
boolean unExpectedNoInternet = candidate.hasNoInternetAccess()
&& !candidate.isNoInternetAccessExpected();
int currentNetworkBonusMin = mScoringParams.getCurrentNetworkBonusMin();
int currentNetworkBonus = Math.max(currentNetworkBonusMin, rssiAndThroughputScore
* mScoringParams.getCurrentNetworkBonusPercent() / 100);
int currentNetworkBoost = (candidate.isCurrentNetwork() && !unExpectedNoInternet)
? currentNetworkBonus : 0;
int securityAward = candidate.isOpenNetwork()
? 0
: mScoringParams.getSecureNetworkBonus();
int unmeteredAward = candidate.isMetered()
? 0
: mScoringParams.getUnmeteredNetworkBonus();
int savedNetworkAward = candidate.isEphemeral() ? 0 : mScoringParams.getSavedNetworkBonus();
int trustedAward = TRUSTED_AWARD;
if (!candidate.isTrusted()) {
savedNetworkAward = 0; // Saved networks are not untrusted, but clear anyway
unmeteredAward = 0; // Ignore metered for untrusted networks
if (candidate.isCarrierOrPrivileged()) {
trustedAward = HALF_TRUSTED_AWARD;
} else if (candidate.getNominatorId() == NOMINATOR_ID_SCORED) {
Log.e(TAG, "ScoredNetworkNominator is not carrier or privileged!");
trustedAward = 0;
} else {
trustedAward = 0;
}
}
int score = rssiBaseScore + throughputBonusScore
+ currentNetworkBoost + securityAward + unmeteredAward + savedNetworkAward
+ trustedAward;
if (candidate.getLastSelectionWeight() > 0.0) {
// Put a recently-selected network in a tier above everything else,
// but include rssi and throughput contributions for BSSID selection.
score = TOP_TIER_BASE_SCORE + rssiBaseScore + throughputBonusScore;
}
if (DBG) {
Log.d(TAG, " rssiScore: " + rssiBaseScore
+ " throughputScore: " + throughputBonusScore
+ " currentNetworkBoost: " + currentNetworkBoost
+ " securityAward: " + securityAward
+ " unmeteredAward: " + unmeteredAward
+ " savedNetworkAward: " + savedNetworkAward
+ " trustedAward: " + trustedAward
+ " final score: " + score);
}
// The old method breaks ties on the basis of RSSI, which we can
// emulate easily since our score does not need to be an integer.
double tieBreaker = candidate.getScanRssi() / 1000.0;
return new ScoredCandidate(score + tieBreaker, 10,
USE_USER_CONNECT_CHOICE, candidate);
}
private int calculateThroughputBonusScore(Candidate candidate) {
int throughputScoreRaw = candidate.getPredictedThroughputMbps()
* mScoringParams.getThroughputBonusNumerator()
/ mScoringParams.getThroughputBonusDenominator();
return Math.min(throughputScoreRaw, mScoringParams.getThroughputBonusLimit());
}