PAYATHON 2026

如何在 Android 中将年度订阅价格换算为月价?

支付阿杰

结论

可以用 NumberFormat.getCurrencyInstance(locale) 格式化换算后的月价。它会按照指定的 Locale 处理货币符号的位置、小数分隔符和分组符,不需要自行判断货币符号应放在金额左侧还是右侧。

年度价格继续使用 mySkuDetails.getPrice(),因为它已经是 Google Play 返回的本地化价格字符串。计算金额时不要使用 float,建议改用 BigDecimal,以减少精度和舍入问题。

实现方式

先把年度价格从微单位转换为货币单位,再除以 12。最后根据订阅的货币代码和当前界面的语言环境进行格式化:

import android.content.Context;
import android.content.res.Configuration;
import android.os.Build;

import java.math.BigDecimal;
import java.math.RoundingMode;
import java.text.NumberFormat;
import java.util.Currency;
import java.util.Locale;

public static String formatMonthlyPrice(
        Context context,
        long yearlyPriceMicros,
        String currencyCode
) {
    Locale locale;

    Configuration configuration = context.getResources().getConfiguration();

    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
        locale = configuration.getLocales().get(0);
    } else {
        locale = configuration.locale;
    }

    Currency currency = Currency.getInstance(currencyCode);

    BigDecimal yearlyPrice = BigDecimal.valueOf(yearlyPriceMicros)
            .movePointLeft(6);

    int fractionDigits = Math.max(currency.getDefaultFractionDigits(), 0);

    BigDecimal monthlyPrice = yearlyPrice.divide(
            BigDecimal.valueOf(12),
            fractionDigits,
            RoundingMode.HALF_UP
    );

    NumberFormat formatter = NumberFormat.getCurrencyInstance(locale);
    formatter.setCurrency(currency);
    formatter.setMinimumFractionDigits(fractionDigits);
    formatter.setMaximumFractionDigits(fractionDigits);

    return formatter.format(monthlyPrice);
}

调用方式如下:

String monthlyPrice = formatMonthlyPrice(
        context,
        mySkuDetails.getPriceAmountMicros(),
        mySkuDetails.getPriceCurrencyCode()
);

String yearlyPrice = mySkuDetails.getPrice();

根据语言环境的不同,monthlyPrice 可能显示为:

$1.00
1,00 €
1,00 US$
¥100

最终格式由 Locale 和货币共同决定。

不要直接拼接货币代码

getPriceCurrencyCode() 返回的是 ISO 4217 货币代码,例如 USD、EUR,并不是已经格式化的货币符号。如果直接拼接:

sCurrencyCode + fMonthPrice

结果只会是类似 USD1.0 的字符串,而且无法正确处理这些差异:

  • 货币符号位于金额之前还是之后
  • 符号与金额之间是否需要空格
  • 小数点使用 . 还是 ,
  • 货币符号是否需要消歧,例如 $ 或 US$
  • 货币通常保留几位小数

这些格式规则应交给 NumberFormat 处理。

完整文案也应使用字符串资源

不要在 Java 代码中直接拼接 /month 等文本。不同语言的语序可能不同,应使用占位符,让翻译人员可以调整整句话:

<string name="subscription_price_message">
    仅需%1$s/月即可订阅(按年支付%2$s)
</string>

然后传入格式化后的月价,以及 Google Play 返回的年度价格:

String message = context.getString(
        R.string.subscription_price_message,
        monthlyPrice,
        yearlyPrice
);

其他语言可以使用不同的语序,例如英文资源:

<string name="subscription_price_message">
    Get this subscription for only %1$s/month (billed annually at %2$s)
</string>

注意事项

mySkuDetails.getPrice() 已经包含 Google Play 确定的本地化价格格式,应直接用来展示年度价格,不要解析后再次格式化。

月价只是根据年度总价换算出的展示值,并不表示用户会按月扣款。文案中应明确写明“折合每月”或“按年支付”,避免让用户误以为这是月度订阅。

年度价格不一定能被 12 整除,因此需要确定舍入规则。上面的示例按照货币常用的小数位数进行 HALF_UP 舍入。如果产品需要显示更多精度,可以适当提高 maximumFractionDigits。不过,即使增加显示精度,12 个月的月价之和仍可能因舍入而与年度价格存在差异。

备注:内容仅供参考。