Android 出现 Google Places API 配额错误
结论
Android 应用调用 Google Places API 时,并不会自动获得无限配额。是否计费、可用额度有多少、如何限流,取决于所用的 Places 产品、关联的 Google Cloud 项目以及当时生效的政策。
PLACES_API_QUOTA_FAILED 也不表示“当天累计请求量已经达到每日上限”。即使用量只有 74k/150k,请求仍可能因为短时间内速率过高、某项细分配额耗尽、实际计入了另一个项目,或者 billing 状态尚未完全生效而被拒绝。
过去没有配置 billing 时曾产生 80k~90k 次请求,只能说明当时系统没有按照控制台显示的每日红线拦截这些调用,不能证明 Android Places Search 曾有永久且无条件的 unlimited quota。
为什么没有达到每日额度也会失败
控制台显示的“150k daily quota”通常只是配额体系中的一个指标。实际请求还可能受到以下限制:
- 每日累计请求量。
- 每分钟或更短时间窗口内的请求量。
- 单个用户、设备、IP 地址或应用实例的请求速率。
- 单个 API method 或 SKU 的独立配额。
- Google Cloud 项目级别的总体配额。
- billing account、API key 与项目的关联状态。
- 服务端针对突发流量或异常调用设置的保护机制。
如果 74,000 次请求均匀分布在一天内,通常不会造成太大压力。但大量用户若在几分钟内集中发起搜索或自动补全请求,短时速率仍可能超出限制,而每日用量图无法直接显示这种峰值。
配额页面的数据也可能延迟更新,因此不能只凭图表中的实时数值判断服务端是否会限流。
首先确认调用的是哪个 Places 产品
“Places search API”可能指不同的接口:
- Places SDK for Android。
- Places API Web Service。
- Place Autocomplete。
- Nearby Search、Text Search 或 Place Details。
- 已废弃的旧版 Google Play services Places API。
这些产品使用的配额、计费 SKU 和错误形式并不完全相同。尤其要注意,不要在 Android 客户端直接调用原本面向 Web Service 的接口,也不要把 Web Service 的配额规则当成 Android SDK 的规则。
如果代码还在使用旧版 Places SDK,应先确认迁移情况。旧版 SDK 的历史政策不适用于当前 Places SDK,也不能根据多年前的文档推断现在仍有无限配额。
建议的排查步骤
1. 核对项目是否一致
逐项确认以下内容是否属于同一个 Google Cloud 项目:
- Android 应用使用的 API key。
- 已启用的 Places API 或 Places SDK for Android。
- billing account。
- 控制台中查看的 quota usage。
- 实际将 quota 提高到 150k 的项目。
一个常见问题是,项目 A 的配额已经调整,但安装包中的 API key 属于项目 B。不同 build variant、debug/release 配置和旧版应用也可能使用不同的 key。
2. 检查 billing 的真实状态
添加付款方式不一定意味着所有请求会立即获得更高额度。还要确认:
- 项目在 Cloud Billing 中处于已关联状态。
- billing account 没有暂停、关闭或付款异常。
- 相关 API 已在该项目中启用。
- 配额调整已经获批,并显示为有效值。
- 没有把预算通知或组织策略误认为配额设置。
billing 和 quota 变更有时需要一定的传播时间。如果问题刚好发生在启用 billing 后不久,这可能是原因之一,但没有日志和项目状态记录时,不能直接下结论。
3. 查看所有配额维度
不要只看单日请求总量。应在 Google Cloud Console 中按具体 API 和 method 检查:
- Requests per day。
- Requests per minute。
- Requests per minute per user。
- 各类 search、autocomplete 或 details 请求。
- 被拒绝的请求数量。
- 流量峰值时段的请求速率。
不同版本的控制台可能使用不同名称,应以当前项目显示的 quota metric 为准。
4. 对照失败时间和流量峰值
每次失败时,应记录以下信息:
- 精确时间和时区。
- API method。
- SDK 返回的状态码与状态消息。
- 使用的应用版本。
- 每分钟请求量。
- 是否集中出现在特定网络、地区或设备版本。
- 同一时段内成功与失败请求的比例。
如果失败主要发生在用户向搜索框输入文字时,原因很可能是自动补全请求过于频繁,而不是每日额度已经不足。
5. 检查 API key 限制
Android key 通常应配置 Android application restriction,并填写正确的 package name 和 SHA-1 certificate fingerprint。同时确认 API restriction 允许调用对应的 Places API。
签名配置错误通常更接近授权问题,但部分 SDK 或旧版服务返回的失败信息比较笼统,因此仍有必要检查。debug、release、Play App Signing 和企业分发版本使用的证书都要分别核对。
Android 端应记录完整错误
不要只记录 "PLACES_API_QUOTA_FAILED" 这段文字,还应保存 statusCode、statusMessage 和请求类型:
placesClient.findAutocompletePredictions(request)
.addOnSuccessListener { response ->
val predictions = response.autocompletePredictions
// 更新搜索结果
}
.addOnFailureListener { throwable ->
if (throwable is ApiException) {
Log.e(
"Places",
"Places request failed: " +
"statusCode=${throwable.statusCode}, " +
"statusMessage=${throwable.statusMessage}",
throwable
)
} else {
Log.e("Places", "Unexpected Places error", throwable)
}
}
不同 Places SDK 版本使用的状态码常量可能不同。应查阅对应版本的官方 API reference,不要只根据错误字符串推测原因。
控制自动补全请求频率
搜索框每输入一个字符就立即发送请求,很容易产生突发流量。可以设置 debounce,并取消已经过期的查询:
private var searchJob: Job? = null
fun onSearchTextChanged(query: String) {
searchJob?.cancel()
if (query.length < 3) {
return
}
searchJob = lifecycleScope.launch {
delay(350)
val request = FindAutocompletePredictionsRequest.builder()
.setQuery(query)
.build()
placesClient.findAutocompletePredictions(request)
.addOnSuccessListener { response ->
// 仅展示当前 query 对应的结果
}
.addOnFailureListener { error ->
if (error is ApiException) {
Log.w(
"Places",
"Autocomplete failed: ${error.statusCode}, " +
"${error.statusMessage}"
)
}
}
}
}
还应避免以下情况:
- 页面重建时重复发送相同请求。
- 列表滚动或 Compose recomposition 触发网络调用。
- 对空字符串或只有一两个字符的内容执行搜索。
- 多个监听器同时处理同一个输入框。
- 用户停止输入后继续发送旧查询。
- 请求失败后,所有设备立即进行无间隔重试。
只有明确的临时错误才适合重试,而且应采用指数退避并限制最大次数。持续出现的配额错误不会因高频重试而恢复,重试反而会继续增加请求量。
关于“Android 曾经有无限配额”的说法
这一说法不能作为当前政策的可靠依据。Google Maps Platform 历史上的不同移动端 SDK、API method 和计费方式,曾使用不同的免费额度或计数方式。“无需购买额外配额”“没有公开的每日上限”与“所有 Places Search 请求无限”不是一回事。
即使历史文档曾说明某个特定 Android SDK 调用限制较宽松,或者没有固定的每日上限,也不能由此推断:
- Places Web Service 同样无限。
- 所有 search、autocomplete 和 details 请求都无限。
- 请求不受每分钟配额或异常流量限制。
- 相关政策会永久保留。
- 当前项目仍然按照旧规则计费。
判断当前限制时,应查看项目中具体 API 的 quota metrics、现行 Places 文档以及 Google Cloud Billing 状态。
如果仍然出现 PLACES_API_QUOTA_FAILED
确认项目、billing、API key 和短时配额均无问题后,如果错误仍持续出现,应向 Google Maps Platform 支持渠道提交配额问题,并提供:
- Google Cloud project ID,但不要公开 API key。
- 失败发生的 UTC 时间范围。
- SDK 名称和版本。
- Android package name。
- 具体 API method。
- 完整状态码和经过脱敏的状态消息。
- 对应时间段的 quota metric 截图或导出数据。
- 是所有用户都失败,还是只有部分用户失败。
仅凭“74k/150k”不能证明服务端出现错误,因为每日额度只是配额指标之一。更有判断价值的是失败时段的分钟级配额、具体 method、实际 API key 所属项目,以及 SDK 返回的完整状态码。
备注:内容仅供参考。
