GMS(Google Mobile Services)为 Android 应用提供了基于云服务的 API,包括广告、游戏、地图、视觉图像、身份验证、电子钱包、App 分析等等很多功能,GMS API 被国际化的 App 广泛使用。GMS 不是 AOSP(Android Open Source Project)的一部分,只存在于通过授权的设备里,如果设备没有 GMS 那么 App 运行时可能会像下面这样。

It's about the network
在没有 GMS 的设备上运行使用了 GMS API 的 App

上图左边是一开源 App,它调用了 GMS 的 Vision 功能来扫描二维码(稍后会详细分析)。右边则是一个知名网站(Quora)的 Android 客户端,它在打开时即弹出这样的对话框,估计是调用了 GMS 的身份验证功能。当然,应该有更多的国际化 App 在没有 GMS 的设备上运行会弹出类似的提示,我就不去尝试了。

接下来看那个开源 App(shadowsocks-android)是如何调用 GMS API 的,我是点击菜单的“Scan QR Code”弹出提示的,追踪代码进入ScannerActivity.kt,在开头处可以看到不少 GMS API 的 import,开源组件 xyz.belvi.mobilevisionbarcodescanner.BarcodeRetriever 也依赖于 GMS 功能。

...
import com.google.android.gms.common.GoogleApiAvailability
import com.google.android.gms.samples.vision.barcodereader.BarcodeCapture
import com.google.android.gms.samples.vision.barcodereader.BarcodeGraphic
import com.google.android.gms.vision.Frame
import com.google.android.gms.vision.barcode.Barcode
import com.google.android.gms.vision.barcode.BarcodeDetector
import xyz.belvi.mobilevisionbarcodescanner.BarcodeRetriever

跳到 OnCreate 处(第 70 行)。这里会调用 GMS 功能创建 QRCode 扫描器,后面打开相机扫码。如果 API 是不可用则会显示一些信息(开头图片)然后进入 fallback(第 62 行),跳转到应用商店的一页面。

private fun fallback() {
    try {
        startActivity(Intent(Intent.ACTION_VIEW, Uri.parse(
                "market://details?id=com.github.sumimakito.awesomeqrsample")))
    } catch (_: ActivityNotFoundException) { }
    finish()
}

...

override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)
    detector = BarcodeDetector.Builder(this)
            .setBarcodeFormats(Barcode.QR_CODE)
            .build()
    if (!detector.isOperational) {
        val availability = GoogleApiAvailability.getInstance()
        val dialog = availability.getErrorDialog(this, availability.isGooglePlayServicesAvailable(this),
                REQUEST_GOOGLE_API)
        if (dialog == null) {
            Toast.makeText(this, R.string.common_google_play_services_notification_ticker, Toast.LENGTH_SHORT)
                    .show()
            fallback()
        } else {
            dialog.setOnDismissListener { fallback() }
            dialog.show()
        }
        return
    }
...

逻辑很清晰,就是一 GMS 功能的使用,失败则会提示信息。国内手机很多没有 GMS,包括我手上这台手机也没有,这样就没法支持国际化的 App 正常运行。上面说的跳转到商店页面,国内手机系统都一般都要修改应用商店,不管是网页里打开 Google Play App 还是 App 内跳转都会跳到国内商店页面(系统半年没更新,不知道现在的情况),从而被完全的本土化了。

对于开源软件有另外一个办法就是把依赖于 GMS 的功能替换为本地模块,就是 App 直接集成相应能力不依赖于云端服务。像这个扫码功能,一般都是 zxing 实现的,集成 zxing 模块修改相应代码即可。而对于广泛的商业流行 App 来说没有 GMS 就比较麻烦,毕竟这些已经是全球应用生态系统的一部分。

It's about the network
App 集成扫码能力