二十九、在 Android 上使用谷歌云消息

当我们接近这本书的结尾时,当谈到处理设备外服务时,您将已经对 Android 中可用的许多通信协议和架构选项有了很好的理解和欣赏。在这一章中,我们将探索 Google 的云消息传递(或 GCM)平台,以及如何使用它来满足应用的远程通信和服务交互需求。

什么是谷歌云消息?

GCM 是 Google 提供的一项服务,它使你能够在不同的平台上编写多个应用,这些应用可以交换消息以增强它们的功能。多个应用的主要示例是与远程服务器应用交换消息的 Android 客户端应用。

作为开发人员,实际发送的消息及其目的取决于您。它可能是来自远程服务器的一条消息,让您的客户端应用知道(一条“下游”消息)新闻提要、音乐服务或类似订阅的新更新可用。从客户端到服务器的消息(“上游”消息)可能是发送聊天消息、图片缩略图或用户在客户端捕获或生成的其他新数据。这些只是例子——您可以自由想象在 GCM 中交换的消息的任何用途和任何负载。

了解 GCM 的关键构件

阅读了 GCM 简介之后,您已经知道了任何完整的 GCM 配置中的两个关键组件。完成这个画面的最后一部分是由 Google 托管的 GCM 服务器(实际上是服务器),它执行消息队列、转发等等。

概括来说,GCM 的三个关键组成部分如下:

  • 客户端应用—您编写的应用,例如 Android 应用,它发送和/或接收来自远程服务器的消息以帮助实现功能。
  • GCM Connection Servers—Google 的消息传递基础设施,管理所有消息传递流量、传递延迟时的消息队列、最终传递保证等。
  • 远程应用服务器—您编写的作为服务器的应用,以可访问互联网的方式托管,负责发送和/或接收来自客户端应用的消息。

我们也可以用视觉形式来表现这种架构。图 29-1 显示了一个完整的 GCM 设置中的组件和消息流。

9781430246800_Fig29-01.jpg

图 29-1 。 GCM 架构概述

准备在应用中使用 GCM

以前我们在构建示例应用时直接跳到了 Java 代码、布局 XML 等等,对于基于 GCM 的开发,我们需要采取一些准备步骤,以便让 Google 的服务器接受来自我们的客户机和服务器的流量。

在 Google 开发者控制台中创建或确认你的 GCM 项目

要使用谷歌的任何在线服务和 API,包括 GCM,你需要在谷歌开发者控制台中创建一个 API 项目,在 cloud.google.com/console.你可能已经有了一个可以重用的项目,但是让我们假设你是第一次创建一个项目。导航到控制台 URL,然后单击“创建项目”按钮。按照提示输入账户和账单信息等。,你应该会得到一个新的项目(或者确认一个现有的项目),如图 29-2 所示。

9781430246800_Fig29-02.jpg

图 29-2 。您的 Google 开发人员控制台已经安装了 API 项目

为您的项目激活 GCM APIs

API 项目就绪后,您现在需要激活特定的 GCM APIs。Google 支持几十种独立的 API,所有这些 API 在默认情况下都是禁用的,以确保您不会意外触发行为或招致您意想不到的成本。点击你的 api 项目(在我们的例子中,api-project-589435632025 ),在左侧的 APIs & Auth 部分,选择 API 并滚动,直到你看到 Google Cloud Messagingfor Android。使用“启用 API”按钮打开它。当按钮从启用 API 变为禁用 API 时,你就知道你已经成功启用了 GCM API ,如图图 29-3 所示。

9781430246800_Fig29-03.jpg

图 29-3 。启用了 GCM 的 Google 开发者控制台 API 项目

生成您的 API 密钥

与其他 Google APIs 一样,访问项目的 GCM API 需要您的密钥。这有助于确保从流量分离到计费、分析等各个方面,您的 GCM 消息不会无意中与其他应用的消息混合在一起。

要生成您的密钥,请选择 APIs & Auth 下的凭据选项。选择“创建新密钥”选项,当提示输入密钥属性时,选择“服务器”作为密钥类型。如果您知道目标服务器的公共 IP 地址,您可以在配置部分使用它,否则您可以使用 0.0.0.0/0 进行测试。然后选择“创建”来生成密钥。当您返回到控制台时,您应该在 Credentials 子菜单下看到您的 API 密钥。记下键值,因为您很快就会用到它。

认证 GCM 通信

API 密钥并不是用于在 GCM 环境中认证和授权消息传输的唯一标识信息。你可以在 developer.google.com 网站上找到更多关于 GCM 令牌和密钥使用的细节。简而言之,在 GCM 应用中使用了以下四种令牌类型:

  • 发送者 ID 可从谷歌开发者控制台获得的项目 ID 代码。您的服务器应用将使用它作为向 Google 的 GCM 服务器注册过程的一部分,以使它能够向 Android 客户端应用和您的用户发送消息。
  • Sender Auth Token 您的 API 密钥,在发送到 GCM 服务器的每条消息中使用,以证明消息的真实性及其来自您的服务器应用。
  • 对于您的 Android 应用,这是完全限定的 Java 包名,例如 com.androidbook.gcm 。因为这在所有 Android 应用中是唯一的,所以它允许 GCM 生态系统知道哪些应用接收哪些类型的消息。
  • 注册 ID 当您的客户端 Android 应用向 GCM 服务器注册消息传递时,分配给它的 ID。注册 ID 是敏感信息,应安全存储,不得泄露。

所有这些项目结合起来允许客户机和服务器应用向 GCM 注册并被它识别,还可以唯一地识别应用及其消息。

构建支持 Android GCM 的应用

构建一个有意义的基于 GCM 的 Android 应用和支持服务器端的第三方服务是一项大工程——大到我们几乎可以就这个主题写一本小书。下面我们将介绍构建应用时需要考虑的主要配置和编码要点,您可以查看图书网站,获得关于 GCM 的更深入的讨论和完整的示例。

为 GCM 编写客户端组件代码

客户端 Android 应用需要考虑三大领域。首先,正确设置您的开发环境。其次,配置 Android 项目,使其包含正确的依赖项和特权。最后,将 GCM 注册方法和消息处理方法写入活动的 Java 代码中。

为您的项目配置项目依赖关系

在我们可以为我们想要的基于 GCM 的应用编写实际的 Java 代码和任何相关的 XML 布局之前,我们需要配置我们的项目,使必要的 API 可用,并调用您的 IDE 的构建工具(例如 gradle)和必要的依赖项,以确保成功的构建。

你的开发环境(Android Studio,Eclipse 等。)需要安装 Google Play 服务 SDK。在 IDE 或命令提示符下用 SDK 管理器仔细检查这一点。

接下来,您的项目需要配置为使用 Google Play 服务 SDK 提供的 GoogleCloudMessaging API。例如,要将它添加到一个 Android Studio 项目中,打开你的项目的 build.gradle 文件,并确保该 API 作为一个依赖项被包含进来,如清单 29-1 所示。

清单 29-1 build.gradle 文件片段显示播放服务依赖关系

dependencies {
    // your other dependencies here
    compile "com.google.android.gms:play-services:3.1.+"
}

对于 Eclipse 用户,等效的任务是从 Google Play 服务库集合中添加 google-play-services.jar 作为项目的外部库依赖项。最后,任何 GCM 应用必须运行在 Android 2.2(安装了 Play Store)或更高版本上。更新你的清单的使用-sdk 元素来设置 android:minSdkVersion 至少为 8 。

为 GCM 设置清单属性

除了 GCM 所需的最低 SDK 版本之外,您的应用还需要特定的权限来执行以下操作:

  • 使用 com . Google . Android . c2dm . permission . receive 权限向 GCM 服务器注册以接收消息。
  • 使用设备的互联网连接发送信息,使用 Android . permission . internet 权限。
  • 专门保留给该应用的消息,并防止其他应用为它们注册。这使用了带有应用名称的自定义 C2D 消息权限块。
  • 您还将为接收者定义特定于 GCM 的权限,以便允许 GCM 服务器向您的应用发送消息。这使用 com . Google . Android . c2dm . permission . receive 设置。

注意GCM 的早期版本被称为 C2DM,即云到设备的消息传递。因此,参考 C2DM 和 C2D 的早期名称。

您定义的接收者应该声明它的 intent-filter 作用于 com . Google . Android . c2dm . intent . receive 并使用应用包名称作为它的类别。

仔细阅读一个示例 AndroidManifest.xml 文件的片段来查看所有这些设置通常会更好。清单 29-2 显示了 GCM 应用需要的四个关键权限的示例设置。

清单 29-2GCM Android 应用的示例 AndroidManifest.xml 条目

<manifest xmlns:android="[http://schemas.android.com/apk/res/android](http://schemas.android.com/apk/res/android)"
    package="com.androidbook.gcm">

    ...

    <uses-sdk android:minSdkVersion="8" android:targetSdkVersion="21"/>
    <uses-permission android:name="android.permission.INTERNET" />
    <uses-permission android:name="android.permission.GET_ACCOUNTS" />
    <uses-permission android:name="com.google.android.c2dm.permission.RECEIVE" />

    ...

    <permission android:name="com.androidbook.gcm.permission.C2D_MESSAGE"
        android:protectionLevel="signature" />
    <uses-permission android:name="com.androidbook.gcm.permission.C2D_MESSAGE" />

    ...

   <application ...>
        <receiver
            android:name=".GcmBroadcastReceiver"
            android:permission="com.google.android.c2dm.permission.SEND" >
            <intent-filter>
                <action android:name="com.google.android.c2dm.intent.RECEIVE" />
                <category android:name="com.androidbook.gcm" />
            </intent-filter>
        </receiver>
        <service android:name=".GcmIntentService" />
    </application>
...
</manifest>

编码您的主要活动以注册 GCM

在您的应用可以从 GCM 服务器(以及发送消息的服务器端应用)接收消息之前,在它可以通过 GCM 发送回自己的消息之前,您的应用必须向 GCM 服务器注册。这是为了让 GCM 基础设施知道如何路由您的消息,防止流量混淆,等等。清单 29-3 显示了一个在 onCreate() 覆盖中启动注册的示例活动片段。这个示例代码是仿照谷歌在 developer.android.com 发布的 github 示例 GCM 项目。

清单 29-3 从 Java 向 GCM 注册

package com.google.android.gcm.demo.app;
// imports from a default activity, and the GCM specific libraries

public class GCMExampleActivity extends Activity {

    public static final String EXTRA_MESSAGE = "message";
    public static final String PROPERTY_REG_ID = "registration_id";
    private static final int PLAY_SERVICES_RESOLUTION_REQUEST = 9000;

    String SENDER_ID = "a123b456c789d012"; // Remember to use your ID

    TextView myMessageDisplay;
    GoogleCloudMessaging gcm;
    AtomicInteger messageID = new AtomicInteger();
    Context context;
    String registrationID;

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        setContentView(R.layout.main);
        myMessageDisplay = (TextView) findViewById(R.id.display);
        context = getApplicationContext();

        // Register with GCM servers
        gcm = GoogleCloudMessaging.getInstance(this);
        final SharedPreferences myAppPrefs = getGcmPreferences(context);
        registrationID = myAppPrefs.getString(PROPERTY_REG_ID, "");
        // you could also perform version and other checks if desired

        if (registrationID.isEmpty()) {
            // Not registered, do so async so as not to block main thread
            try {
                registerAppInBackground();
            } catch (NameNotFoundException e)  {
                // log details here, as something failed during registration
            }
        }
    }

    private void registerAppInBackground() {
        new AsyncTask<Void, Void, String>() {
            @Override
            protected String doInBackground(Void... params) {
                String regStatus = "Unregistered";
                try {
                    if (gcm == null) {
                        gcm = GoogleCloudMessaging.getInstance(context);
                    }
                    registrationID = gcm.register(SENDER_ID);
                    regStatus = "Registered with ID: " + registrationID;
                    // add your call to securely store the registrationID for later reuse
                    // ...
                } catch (IOException ex) {
                    // perform your error handling here, e.g. retry
                }
                return regStatus;
            }

        }.execute(null, null, null);
    }
...

这是一个非常简化的例子,因此您可以专注于绝对强制的组件,并从那里开始构建。我们的 onCreate() 方法首先实例化一个 gcm 对象和一个 SharedPreferences 对象。然后,它从首选项中检索 registration_id 。此时,您的应用可能尚未注册,这意味着首选项的返回值将为空。我们测试这个空值,当检测到空的 registration_id 时,我们通过调用私有方法 registerAppInBackground()启动注册过程。

registerAppInBackground()的实现遵循 Google 的建议,异步执行首次注册。我们这样做是因为我们不想在等待握手和注册过程完成时阻塞主线程。该过程可能需要几秒钟或更长时间。您可以通过添加一系列中间状态更新、错误检查等等来增强应用。

一旦我们有了一个注册的应用,我们就可以执行消息交换,然后基于消息传递方面或由消息传递方面驱动,执行您可能希望您的应用拥有的所有其他逻辑。清单 29-4 展示了一个基于 Bundle 对象发送消息的示例方法,该对象用于保存消息细节。

清单 29-4 示例从您的 Android 客户端发送消息

private void sendMessage(Bundle messagePayload) {
    new AsyncTask<Void, Void, String>() {
        @Override
        protected String doInBackground(Void... params) {
            String status = "";
            try {
                String id = Integer.toString(messageID.incrementAndGet());
                gcm.send(SENDER_ID + "@gcm.googleapis.com", id, messagePayload);
                status = "message sent";
            } catch (IOException ex) {
                // your error handling here
                // set status string
            }
            return status;
        }
    }
}

sendMessage() 方法的逻辑完全与发送你在 messagePayload 参数中构造的内容有关。这个 Bundle 对象留给你自己去想象,但是它可以是即时消息、照片、语音消息,或者你的应用实际上在帮助用户的其他内容。

我们再次使用 AsyncTask 来确保我们不会阻塞消息传递。这是处理任何类型的消息总线或消息传递服务时的通用设计模式。在异步逻辑中,我们使用 messageid . incrementandget()方法生成唯一的消息标识符,然后调用 gcm.send() 方法,向其传递唯一的 ID 和我们的消息有效负载。

此时很容易添加错误捕获和重试逻辑。如果您打算对消息 ID 采用更高的值(通常不推荐),最好将重试逻辑放在 sendMessage() 方法中,以便能够在消息 ID 超出方法返回的范围之前重用它。

为 GCM 编写服务器组件代码

您的第三方服务基本上可以用任何语言编写,只要它可以调用 GCM 云端点并支持我们在本章前面部分描述的授权消息协议。

因为这样的服务不是严格意义上的 Android 产品或代码,我们将从书中保留一些珍贵的页面,并向您指出 Google 提供的优秀示例,以给你编写后端服务的灵感。

您可以在 developer.android.com/google/gcm/server.html 查看这项非 Android 第三方服务的选项和方法。

超越 GCM 简介

这么短的一章无法涵盖基于 GCM 的应用的巨大可能性和细微差别。关于什么是可能的更多细节,请查看 Android Stack Exhange 站点、【android.stackechange.com】【developer.android.com】站点。