CandidateScorer

文章目录

写在前面

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());
}
上一篇:调整Wifi满格信号的阀值


下一篇:无线网络技术 实验2 无线网络环境RSSI测试实验