Google Cloud Messaging 的远程通知Remote Notifications With Google Cloud Messaging

警告

2018年4月10日起已弃用的 Google GCM。Google deprecated GCM as of April 10, 2018. 下面的文档和示例项目可能不再维护。The following docs and sample projects may no longer be maintained. Google 的 GCM 服务器和客户端 Api 将在 2019 5 月29日后删除。Google's GCM server and client APIs will be removed as soon as May 29, 2019. Google 建议将 GCM 应用迁移到 Firebase Cloud 消息(FCM)。Google recommends migrating GCM apps to Firebase Cloud Messaging (FCM). 有关 GCM 弃用和迁移的详细信息,请参阅Google Cloud Messaging-已弃用For more information about GCM deprecation and migration, see Google Cloud Messaging - DEPRECATED.

若要开始使用 Firebase Cloud 消息传递到 Xamarin 的远程通知,请参阅使用 FCM 的远程通知To get started with Remote Notifications using Firebase Cloud Messaging with Xamarin, see Remote Notifications with FCM.

本演练逐步说明如何使用 Google Cloud Messaging 来实现 Xamarin Android 应用程序中的远程通知(也称为推送通知)。它介绍了为了与 Google Cloud Messaging (GCM)进行通信而必须实现的各种类,并说明如何在 Android 清单中设置权限以访问 GCM,并使用示例测试程序演示端到端消息传送。This walkthrough provides a step-by-step explanation of how to use Google Cloud Messaging to implement remote notifications (also called push notifications) in a Xamarin.Android application. It describes the various classes that you must implement to communicate with Google Cloud Messaging (GCM), it explains how to set permissions in the Android Manifest for access to GCM, and it demonstrates end-to-end messaging with a sample test program.

GCM 通知概述GCM Notifications Overview

在本演练中,我们将创建一个 Xamarin Android 应用程序,该应用程序使用 Google Cloud Messaging (GCM)来实施远程通知(也称为推送通知)。In this walkthrough, we'll create a Xamarin.Android application that uses Google Cloud Messaging (GCM) to implement remote notifications (also known as push notifications). 我们将实现使用 GCM 进行远程消息传递的各种意向和侦听器服务,并使用可模拟应用程序服务器的命令行程序来测试实现。We'll implement the various intent and listener services that use GCM for remote messaging, and we'll test our implementation with a command-line program that simulates an application server.

在继续本演练之前,必须获取所需的凭据才能使用 Google 的 GCM 服务器;Google Cloud Messaging中对此过程进行了说明。Before you can proceed with this walkthrough, you must acquire the necessary credentials to use Google's GCM servers; this process is explained in Google Cloud Messaging. 特别是,您将需要一个API 密钥和一个发送程序 ID ,以插入本演练中提供的示例代码。In particular, you will need an API Key and a Sender ID to insert into the example code presented in this walkthrough.

我们将使用以下步骤创建支持 GCM 的 Xamarin 客户端应用:We'll use the following steps to create a GCM-enabled Xamarin.Android client app:

  1. 安装与 GCM 服务器进行通信所需的其他包。Install additional packages required for communications with GCM servers.
  2. 配置用于访问 GCM 服务器的应用权限。Configure app permissions for access to GCM servers.
  3. 实现代码以检查是否存在 Google Play Services。Implement code to check for the presence of Google Play Services.
  4. 实现一个注册意向服务,该服务与 GCM 协商注册令牌。Implement a registration intent service that negotiates with GCM for a registration token.
  5. 实现一个实例 ID 侦听器服务,该服务侦听 GCM 的注册令牌更新。Implement an instance ID listener service that listens for registration token updates from GCM.
  6. 实现一个 GCM 侦听器服务,该服务从应用服务器通过 GCM 接收远程消息。Implement a GCM listener service that receives remote messages from the app server through GCM.

此应用将使用称为主题消息传递的新 GCM 功能。This app will use a new GCM feature known as topic messaging. 在主题消息传递中,应用服务器将消息发送到主题,而不是发送到单个设备的列表。In topic messaging, the app server sends a message to a topic, rather than to a list of individual devices. 订阅该主题的设备可以接收主题消息作为推送通知。Devices that subscribe to that topic can receive topic messages as push notifications. 有关 GCM 主题消息传送的详细信息,请参阅 Google 的实现主题消息For more information about GCM topic messaging, see Google's Implementing Topic Messaging.

当客户端应用程序准备就绪时,我们将实现一个命令C#行应用程序,该应用程序通过 GCM 向我们的客户端应用程序发送推送通知。When the client app is ready, we'll implement a command-line C# application that sends a push notification to our client app via GCM.

演练Walkthrough

首先,让我们创建一个名为 " 的新的空解决方案。To begin, let's create a new empty Solution called RemoteNotifications. 接下来,让我们将新的 Android 项目添加到此解决方案,该解决方案基于Android 应用程序模板。Next, let's add a new Android project to this Solution that is based on the Android App template. 让我们调用此项目ClientAppLet's call this project ClientApp. (如果您不熟悉如何创建 Xamarin Android 项目,请参阅Hello、android。)ClientApp项目将包含 Xamarin Android 客户端应用程序的代码,该应用程序通过 GCM 接收远程通知。(If you're not familiar with creating Xamarin.Android projects, see Hello, Android.) The ClientApp project will contain the code for the Xamarin.Android client application that receives remote notifications via GCM.

添加所需包Add Required Packages

在我们可以实现客户端应用程序代码之前,必须安装多个包用于与 GCM 通信。Before we can implement our client app code, we must install several packages that we'll use for communication with GCM. 此外,还必须将 Google Play 商店应用程序添加到设备(如果尚未安装)。Also, we must add the Google Play Store application to our device if it is not already installed.

添加 Xamarin Google Play Services GCM 包Add the Xamarin Google Play Services GCM Package

若要从 Google Cloud Messaging 接收消息, Google Play Services框架必须存在于设备上。To receive messages from Google Cloud Messaging, the Google Play Services framework must be present on the device. 如果没有此框架,Android 应用程序将无法从 GCM 服务器接收消息。Without this framework, an Android application cannot receive messages from GCM servers. Google Play Services 在运行 Android 设备时,在后台运行,不知不觉地侦听来自 GCM 的消息。Google Play Services runs in the background while the Android device is powered on, quietly listening for messages from GCM. 当这些消息到达时,Google Play Services 将消息转换为意向,然后将这些意向广播给为其注册的应用程序。When these messages arrive, Google Play Services converts the messages into intents and then broadcasts these intents to applications that have registered for them.

在 Visual Studio 中,右键单击 "引用" > "管理 NuGet 包 ... "在 Visual Studio for Mac 中,右键单击 "包" > "添加包 ... "。搜索Xamarin GOOGLE PLAY SERVICES GCM ,并将此包安装到ClientApp项目中:In Visual Studio, right-click References > Manage NuGet Packages ...; in Visual Studio for Mac, right-click Packages > Add Packages.... Search for Xamarin Google Play Services - GCM and install this package into the ClientApp project:

安装 Google Play ServicesInstalling Google Play Services

安装xamarin GOOGLE PLAY SERVICES GCM时,会自动安装xamarin Google Play Services 基础When you install Xamarin Google Play Services - GCM, Xamarin Google Play Services - Base is automatically installed. 如果遇到错误,请将项目的最低 Android 目标设置更改为使用 SDK 版本的编译以外的值,然后再次尝试安装 NuGet。If you get an error, change the project's Minimum Android to target setting to a value other than Compile using SDK version and try the NuGet install again.

接下来,编辑MainActivity.cs并添加以下 using 语句:Next, edit MainActivity.cs and add the following using statements:

using Android.Gms.Common;
using Android.Util;

这使得 Google Play Services GMS 包中的类型可用于我们的代码,并添加了日志记录功能,我们将使用这些功能跟踪 GMS 的事务。This makes types in the Google Play Services GMS package available to our code, and it adds logging functionality that we will use to track our transactions with GMS.

Google Play 商店Google Play Store

若要从 GCM 接收消息,则必须在设备上安装 Google Play 商店应用程序。To receive messages from GCM, the Google Play Store application must be installed on the device. (每当在设备上安装 Google Play 应用程序时,也会安装 Google Play 商店,因此可能已在测试设备上安装了该应用程序。)如果没有 Google Play,Android 应用程序将无法从 GCM 接收消息。(Whenever a Google Play application is installed on a device, Google Play Store is also installed, so it's likely that it is already installed on your test device.) Without Google Play, an Android application cannot receive messages from GCM. 如果你尚未在设备上安装 Google Play 商店应用,请访问Google Play网站下载并安装 Google Play。If you do not yet have the Google Play Store app installed on your device, visit the Google Play web site to download and install Google Play.

或者,你可以使用运行 Android 2.2 或更高版本的 Android 仿真程序,而不是使用测试设备(无需在 Android 模拟器上安装 Google Play 商店)。Alternately, you can use an Android emulator running Android 2.2 or later instead of a test device (you do not have to install Google Play Store on an Android emulator). 但是,如果使用模拟器,则必须使用 Wi-fi 连接到 GCM,并且必须打开 Wi-fi 防火墙中的几个端口,如本演练稍后部分中所述。However, if you use an emulator, you must use Wi-Fi to connect to GCM and you must open several ports in your Wi-Fi firewall as explained later in this walkthrough.

设置包名称Set the Package Name

Google Cloud Messaging中,我们为启用了 GCM 的应用指定了包名称(此包名称还用作与我们的 API 密钥和发送方 id 相关联的应用程序 id )。In Google Cloud Messaging, we specified a package name for our GCM-enabled app (this package name also serves as the application ID that is associated with our API key and Sender ID). 接下来,打开ClientApp项目的属性,并将包名称设置为此字符串。Let's open the properties for the ClientApp project and set the package name to this string. 在此示例中,我们将包名称设置为 com.xamarin.gcmexampleIn this example, we set the package name to com.xamarin.gcmexample:

设置包名称Setting the package name

请注意,如果此包名称与我们在 Google 开发人员控制台中输入的包名称不完全匹配,则客户端应用将无法从 GCM 接收注册令牌。Note that the client app will be unable to receive a registration token from GCM if this package name does not exactly match the package name that we entered into the Google Developer console.

将权限添加到 Android 清单Add Permissions to the Android Manifest

Android 应用程序必须配置有以下权限,然后才能接收来自 Google Cloud Messaging 的通知:An Android application must have the following permissions configured before it can receive notifications from Google Cloud Messaging:

  • com.google.android.c2dm.permission.RECEIVE – 授予应用从 Google Cloud Messaging 注册和接收消息的权限。com.google.android.c2dm.permission.RECEIVE – Grants permission to our app to register and receive messages from Google Cloud Messaging. c2dm 的含义是什么?(What does c2dm mean? 这代表_云到设备的消息传送_,这是 GCM 的现不推荐使用的前置任务。This stands for Cloud to Device Messaging, which is the now-deprecated predecessor to GCM. GCM 仍使用其许多权限字符串中的 c2dm。)GCM still uses c2dm in many of its permission strings.)

  • android.permission.WAKE_LOCK – (可选)会阻止设备 CPU 在侦听消息时进入睡眠状态。android.permission.WAKE_LOCK – (Optional) Prevents the device CPU from going to sleep while listening for a message.

  • android.permission.INTERNET – 授予 internet 访问权限,以便客户端应用能够与 GCM 通信。android.permission.INTERNET – Grants internet access so the client app can communicate with GCM.

  • package_name.permission.C2D_MESSAGE – 将应用程序注册到 Android 并请求权限以独占接收所有 C2D (云到设备)消息。package_name.permission.C2D_MESSAGE – Registers the application with Android and requests permission to exclusively receive all C2D (cloud to device) messages. Package_name前缀与应用程序 ID 相同。The package_name prefix is the same as your application ID.

我们将在 Android 清单中设置这些权限。We'll set these permissions in the Android manifest. 让我们编辑androidmanifest.xml ,并将内容替换为以下 xml:Let's edit AndroidManifest.xml and replace the contents with the following XML:

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android" 
    package="YOUR_PACKAGE_NAME" 
    android:versionCode="1" 
    android:versionName="1.0" 
    android:installLocation="auto">
    <uses-permission android:name="com.google.android.c2dm.permission.RECEIVE" />
    <uses-permission android:name="android.permission.WAKE_LOCK" />
    <uses-permission android:name="android.permission.INTERNET" />
    <uses-permission android:name="YOUR_PACKAGE_NAME.permission.C2D_MESSAGE" />
    <permission android:name="YOUR_PACKAGE_NAME.permission.C2D_MESSAGE" 
                android:protectionLevel="signature" />
    <application android:label="ClientApp" android:icon="@drawable/Icon">
    </application>
</manifest>

在上面的 XML 中,将YOUR_PACKAGE_NAME更改为客户端应用程序项目的包名称。In the above XML, change YOUR_PACKAGE_NAME to the package name for your client app project. 例如 com.xamarin.gcmexampleFor example, com.xamarin.gcmexample.

检查 Google Play ServicesCheck for Google Play Services

对于本演练,我们将使用用户界面中的单个 TextView 来创建一个简单的应用。For this walkthrough, we're creating a bare-bones app with a single TextView in the UI. 此应用不直接指示与 GCM 交互。This app doesn't directly indicate interaction with GCM. 相反,我们将观看 "输出" 窗口,查看应用如何与 GCM 握手,并在通知托盘收到新通知时将其选中。Instead, we'll watch the output window to see how our app handshakes with GCM, and we'll check the notification tray for new notifications as they arrive.

首先,让我们为消息区域创建布局。First, let's create a layout for the message area. 编辑main.axml ,并将内容替换为以下 XML:Edit Resources.layout.Main.axml and replace the contents with the following XML:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:orientation="vertical"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:padding="10dp">
    <TextView
        android:text=" "
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:id="@+id/msgText"
        android:textAppearance="?android:attr/textAppearanceMedium"
        android:padding="10dp" />
</LinearLayout>

保存main.axml并关闭它。Save Main.axml and close it.

当客户端应用程序启动时,我们想要在尝试联系 GCM 之前验证 Google Play Services 是否可用。When the client app starts, we want it to verify that Google Play Services is available before we attempt to contact GCM. 编辑MainActivity.cs并将 count 实例变量声明替换为以下实例变量声明:Edit MainActivity.cs and replace the count instance variable declaration with the following instance variable declaration:

TextView msgText;

接下来,将以下方法添加到MainActivity类:Next, add the following method to the MainActivity class:

public bool IsPlayServicesAvailable ()
{
    int resultCode = GoogleApiAvailability.Instance.IsGooglePlayServicesAvailable (this);
    if (resultCode != ConnectionResult.Success)
    {
        if (GoogleApiAvailability.Instance.IsUserResolvableError (resultCode))
            msgText.Text = GoogleApiAvailability.Instance.GetErrorString (resultCode);
        else
        {
            msgText.Text = "Sorry, this device is not supported";
            Finish ();
        }
        return false;
    }
    else
    {
        msgText.Text = "Google Play Services is available.";
        return true;
    }
}

此代码检查设备以查看是否安装了 Google Play Services 的 APK。This code checks the device to see if the Google Play Services APK is installed. 如果未安装,则会在消息区域显示一条消息,指示用户从 Google Play 商店下载 APK (或在设备的系统设置中启用该程序)。If it is not installed, a message is displayed in the message area that instructs the user to download an APK from the Google Play Store (or enable it in the device's system settings). 由于我们希望在客户端应用程序启动时运行此检查,因此我们将在 OnCreate末尾添加对此方法的调用。Because we want to run this check when the client app starts, we'll add a call to this method at the end of OnCreate.

接下来,将 OnCreate 方法替换为以下代码:Next, replace the OnCreate method with the following code:

protected override void OnCreate (Bundle bundle)
{
    base.OnCreate (bundle);

    SetContentView (Resource.Layout.Main);
    msgText = FindViewById<TextView> (Resource.Id.msgText);

    IsPlayServicesAvailable ();
}

此代码检查是否存在 Google Play Services APK,并将结果写入消息区域。This code checks for the presence of the Google Play Services APK and writes the result to the message area.

让我们完全重新生成并运行该应用程序。Let's completely rebuild and run the app. 应会看到如下屏幕截图所示的屏幕:You should see a screen that looks like the following screenshot:

Google Play Services 可用Google Play Services is available

如果没有得到此结果,请验证是否在设备上安装了 Google Play Services APK,并将Xamarin GOOGLE PLAY SERVICES GCM包添加到ClientApp项目(如前文所述)。If you don't get this result, verify that the Google Play Services APK is installed on your device and that the Xamarin Google Play Services - GCM package is added to your ClientApp project as explained earlier. 如果遇到生成错误,请尝试清除解决方案并重新生成项目。If you get a build error, try cleaning the Solution and building the project again.

接下来,我们将编写代码来联系 GCM 并取回注册令牌。Next, we'll write code to contact GCM and get back a registration token.

向 GCM 注册Register with GCM

此应用必须在 GCM 上注册并获取注册令牌,然后应用才能从应用服务器接收远程通知。Before the app can receive remote notifications from the app server, it must register with GCM and get back a registration token. 使用 GCM 注册应用程序的工作通过我们创建的 IntentService 来处理。The work of registering our application with GCM is handled by an IntentService that we create. 我们的 IntentService 执行以下步骤:Our IntentService performs the following steps:

  1. 使用InstanceID API 生成授权客户端应用访问应用服务器的安全令牌。Uses the InstanceID API to generate security tokens that authorize our client app to access the app server. 返回后,我们从 GCM 获取注册令牌。In return, we get back a registration token from GCM.

  2. 将注册令牌转发到应用服务器(如果应用服务器需要)。Forwards the registration token to the app server (if the app server requires it).

  3. 订阅一个或多个通知主题通道。Subscribes to one or more notification topic channels.

实现此 IntentService后,我们将对其进行测试,看看我们是否从 GCM 获取注册令牌。After we implement this IntentService, we'll test it to see if we get back a registration token from GCM.

添加一个名为RegistrationIntentService.cs的新文件,并将模板代码替换为以下代码:Add a new file called RegistrationIntentService.cs and replace the template code with the following:

using System;
using Android.App;
using Android.Content;
using Android.Util;
using Android.Gms.Gcm;
using Android.Gms.Gcm.Iid;

namespace ClientApp
{
    [Service(Exported = false)]
    class RegistrationIntentService : IntentService
    {
        static object locker = new object();

        public RegistrationIntentService() : base("RegistrationIntentService") { }

        protected override void OnHandleIntent (Intent intent)
        {
            try
            {
                Log.Info ("RegistrationIntentService", "Calling InstanceID.GetToken");
                lock (locker)
                {
                    var instanceID = InstanceID.GetInstance (this);
                    var token = instanceID.GetToken (
                        "YOUR_SENDER_ID", GoogleCloudMessaging.InstanceIdScope, null);

                    Log.Info ("RegistrationIntentService", "GCM Registration Token: " + token);
                    SendRegistrationToAppServer (token);
                    Subscribe (token);
                }
            }
            catch (Exception e)
            {
                Log.Debug("RegistrationIntentService", "Failed to get a registration token");
                return;
            }
        }

        void SendRegistrationToAppServer (string token)
        {
            // Add custom implementation here as needed.
        }

        void Subscribe (string token)
        {
            var pubSub = GcmPubSub.GetInstance(this);
            pubSub.Subscribe(token, "/topics/global", null);
        }
    }
}

在上面的示例代码中,将YOUR_SENDER_ID更改为客户端应用程序项目的发送方 ID 号。In the above sample code, change YOUR_SENDER_ID to the Sender ID number for your client app project. 获取项目的发送方 ID:To get the Sender ID for your project:

  1. 登录到Google Cloud Console ,并从下拉菜单中选择你的项目名称。Log into the Google Cloud Console and select your project name from the pull down menu. 在为项目显示的 "项目信息" 窗格中,单击 "前往项目设置":In the Project info pane that is displayed for your project, click Go to project settings:

    选择 XamarinGCM 项目Selecting XamarinGCM project

  2. 在 "设置" 页上,找到项目编号– 这是你的项目的发送方 ID:On the Settings page, locate the Project number – this is the Sender ID for your project:

    显示项目编号Project number displayed

我们想要在应用程序开始运行时开始 RegistrationIntentServiceWe want to start our RegistrationIntentService when our app starts running. 编辑MainActivity.cs并修改 OnCreate 方法,以便在检查是否存在 Google Play Services 后启动 RegistrationIntentServiceEdit MainActivity.cs and modify the OnCreate method so that our RegistrationIntentService is started after we check for the presence of Google Play Services:

protected override void OnCreate (Bundle bundle)
{
    base.OnCreate (bundle);

    SetContentView(Resource.Layout.Main);
    msgText = FindViewById<TextView> (Resource.Id.msgText);

    if (IsPlayServicesAvailable ())
    {
        var intent = new Intent (this, typeof (RegistrationIntentService));
        StartService (intent);
    }
}

现在,让我们看一下 RegistrationIntentService 的每个部分,以了解它的工作原理。Now let's take a look at each section of RegistrationIntentService to understand how it works.

首先,我们使用以下属性为我们的 RegistrationIntentService 添加注释,指出我们的服务不会被系统实例化:First, we annotate our RegistrationIntentService with the following attribute to indicate that our service is not to be instantiated by the system:

[Service (Exported = false)]

RegistrationIntentService 构造函数将工作线程命名为RegistrationIntentService ,以便更轻松地进行调试。The RegistrationIntentService constructor names the worker thread RegistrationIntentService to make debugging easier.

public RegistrationIntentService() : base ("RegistrationIntentService") { }

RegistrationIntentService 的核心功能位于 OnHandleIntent 方法中。The core functionality of RegistrationIntentService resides in the OnHandleIntent method. 让我们演练一下此代码,了解它如何向 GCM 注册我们的应用程序。Let's walk through this code to see how it registers our app with GCM.

请求注册令牌Request a Registration Token

OnHandleIntent 首先调用 Google 的GetToken方法,以从 GCM 请求注册令牌。OnHandleIntent first calls Google's InstanceID.GetToken method to request a registration token from GCM. 我们将此代码包装在 lock 中,以防止多次注册,同时 – lock 确保按顺序处理这些意向。We wrap this code in a lock to guard against the possibility of multiple registration intents occurring simultaneously – the lock ensures that these intents are processed sequentially. 如果我们无法获取注册令牌,则会引发异常,并记录一个错误。If we fail to get a registration token, an exception is thrown and we log an error. 如果注册成功,token 设置为我们从 GCM 返回的注册令牌:If the registration succeeds, token is set to the registration token we got back from GCM:

static object locker = new object ();
...
try
{
    lock (locker)
    {
        var instanceID = InstanceID.GetInstance (this);
        var token = instanceID.GetToken (
            "YOUR_SENDER_ID", GoogleCloudMessaging.InstanceIdScope, null);
        ...
    }
}
catch (Exception e)
{
    Log.Debug ...

将注册令牌转发到应用服务器Forward the Registration Token to the App Server

如果我们获取注册令牌(即未引发异常),我们会调用 SendRegistrationToAppServer 将用户的注册令牌与我们的应用程序维护的服务器端帐户(如果有)相关联。If we get a registration token (that is, no exception was thrown), we call SendRegistrationToAppServer to associate the user's registration token with the server-side account (if any) that is maintained by our application. 由于此实现依赖于应用服务器的设计,因此在此处提供了一个空方法:Because this implementation depends on the design of the app server, an empty method is provided here:

void SendRegistrationToAppServer (string token)
{
    // Add custom implementation here as needed.
}

在某些情况下,应用服务器不需要用户的注册令牌;在这种情况下,可以省略此方法。In some cases, the app server does not need the user's registration token; in that case, this method can be omitted. 向应用服务器发送注册令牌时,SendRegistrationToAppServer 应维护一个布尔值以指示令牌是否已发送到服务器。When a registration token is sent to the app server, SendRegistrationToAppServer should maintain a boolean to indicate whether the token has been sent to the server. 如果此布尔值为 false,SendRegistrationToAppServer 会将令牌发送到应用服务器 – 否则,令牌已在以前的调用中发送到应用服务器。If this boolean is false, SendRegistrationToAppServer sends the token to the app server – otherwise, the token was already sent to the app server in a previous call.

订阅通知主题Subscribe to the Notification Topic

接下来,我们将我们的 Subscribe 方法告诉 GCM 要订阅的 GCM。Next, we call our Subscribe method to indicate to GCM that we want to subscribe to a notification topic. Subscribe中,我们将调用GcmPubSub API,以将我们的客户端应用订阅到 /topics/global下的所有消息:In Subscribe, we call the GcmPubSub.Subscribe API to subscribe our client app to all messages under /topics/global:

void Subscribe (string token)
{
    var pubSub = GcmPubSub.GetInstance(this);
    pubSub.Subscribe(token, "/topics/global", null);
}

如果要接收,应用服务器必须将通知消息发送到 /topics/globalThe app server must send notification messages to /topics/global if we are to receive them. 请注意,在 "/topics" 下的主题名称可以是你需要的任何内容,前提是应用服务器和客户端应用都对这些名称达成一致。Note that the topic name under /topics can be anything you want, as long as the app server and the client app both agree on these names. (在这里,我们选择名称 global 指示我们希望接收应用服务器支持的所有主题的消息。)(Here, we chose the name global to indicate that we want to receive messages on all topics supported by the app server.)

有关服务器端上的 GCM 主题消息传送的信息,请参阅 Google 的向主题发送消息For information about GCM topic messaging on the server side, see Google's Send Messaging to Topics.

实现实例 ID 侦听器服务Implement an Instance ID Listener Service

注册令牌唯一且安全;但是,在应用程序重新安装或安全问题的情况下,客户端应用程序(或 GCM)可能需要刷新注册令牌。Registration tokens are unique and secure; however, the client app (or GCM) may need to refresh the registration token in the event of app reinstallation or a security issue. 出于此原因,我们必须实现响应 GCM 的令牌刷新请求的 InstanceIdListenerServiceFor this reason, we must implement an InstanceIdListenerService that responds to token refresh requests from GCM.

添加一个名为InstanceIdListenerService.cs的新文件,并将模板代码替换为以下代码:Add a new file called InstanceIdListenerService.cs and replace the template code with the following:

using Android.App;
using Android.Content;
using Android.Gms.Gcm.Iid;

namespace ClientApp
{
    [Service(Exported = false), IntentFilter(new[] { "com.google.android.gms.iid.InstanceID" })]
    class MyInstanceIDListenerService : InstanceIDListenerService
    {
        public override void OnTokenRefresh()
        {
            var intent = new Intent (this, typeof (RegistrationIntentService));
            StartService (intent);
        }
    }
}

使用以下特性注释 InstanceIdListenerService,以指示该服务不是由系统实例化的,并且它可以接收 GCM 注册令牌(也称为实例 ID) refresh 请求:Annotate InstanceIdListenerService with the following attribute to indicate that the service is not to be instantiated by the system and that it can receive GCM registration token (also called instance ID) refresh requests:

[Service(Exported = false), IntentFilter(new[] { "com.google.android.gms.iid.InstanceID" })]

我们的服务中的 OnTokenRefresh 方法将启动 RegistrationIntentService,以便它能够截获新的注册令牌。The OnTokenRefresh method in our service starts the RegistrationIntentService so that it can intercept the new registration token.

通过 GCM 测试注册Test Registration with GCM

让我们完全重新生成并运行该应用程序。Let's completely rebuild and run the app. 如果成功接收到 GCM 的注册令牌,则应在 "输出" 窗口中显示注册令牌。If you successfully receive a registration token from GCM, the registration token should be displayed in the output window. 例如:For example:

D/Mono    ( 1934): Assembly Ref addref ClientApp[0xb4ac2400] -> Xamarin.GooglePlayServices.Gcm[0xb4ac2640]: 2
I/RegistrationIntentService( 1934): Calling InstanceID.GetToken
I/RegistrationIntentService( 1934): GCM Registration Token: f8LdveCvXig:APA91bFIsjUAbP-V8TPQdLR89qQbEJh1SYG38AcCbBUf34z5gSdUc5OsXrgs93YFiGcRSRafPfzkz23lf3-LvYV1CwrFheMjHgwPeFSh12MywnRIhz

处理下游消息Handle Downstream Messages

到目前为止,我们实现的代码只是 "设置" 代码;它将检查是否安装了 Google Play Services 并与 GCM 和应用服务器协商,以便为客户端应用程序准备接收远程通知。The code we have implemented thus far is only "set-up" code; it checks to see if Google Play Services is installed and negotiates with GCM and the app server to prepare our client app for receiving remote notifications. 但是,我们还在实现实际接收和处理下游通知消息的代码。However, we have yet to implement code that actually receives and processes downstream notification messages. 为此,必须实现GCM 侦听器服务To do this, we must implement a GCM Listener Service. 此服务从应用程序服务器接收主题消息,并在本地将其作为通知广播。This service receives topic messages from the app server and locally broadcasts them as notifications. 实现此服务后,我们将创建一个测试程序,用于将消息发送到 GCM,以便查看实现是否正常工作。After we implement this service, we'll create a test program to send messages to GCM so that we can see if our implementation works correctly.

添加通知图标Add a Notification Icon

首先添加一个小图标,该图标将在通知启动时出现在通知区域中。Let's first add a small icon that will appear in the notification area when our notification is launched. 您可以将此图标复制到您的项目或创建您自己的自定义图标。You can copy this icon to your project or create your own custom icon. 我们会将图标文件ic_stat_button_click ,并将其复制到资源/可绘制文件夹中。We'll name the icon file ic_stat_button_click.png and copy it to the Resources/drawable folder. 请记住,使用 "添加 > 现有项 ... " 将此图标文件包含在项目中。Remember to use Add > Existing Item ... to include this icon file in your project.

实现 GCM 侦听器服务Implement a GCM Listener Service

添加一个名为GcmListenerService.cs的新文件,并将模板代码替换为以下代码:Add a new file called GcmListenerService.cs and replace the template code with the following:

using Android.App;
using Android.Content;
using Android.OS;
using Android.Gms.Gcm;
using Android.Util;

namespace ClientApp
{
    [Service (Exported = false), IntentFilter (new [] { "com.google.android.c2dm.intent.RECEIVE" })]
    public class MyGcmListenerService : GcmListenerService
    {
        public override void OnMessageReceived (string from, Bundle data)
        {
            var message = data.GetString ("message");
            Log.Debug ("MyGcmListenerService", "From:    " + from);
            Log.Debug ("MyGcmListenerService", "Message: " + message);
            SendNotification (message);
        }

        void SendNotification (string message)
        {
            var intent = new Intent (this, typeof(MainActivity));
            intent.AddFlags (ActivityFlags.ClearTop);
            var pendingIntent = PendingIntent.GetActivity (this, 0, intent, PendingIntentFlags.OneShot);

            var notificationBuilder = new Notification.Builder(this)
                .SetSmallIcon (Resource.Drawable.ic_stat_ic_notification)
                .SetContentTitle ("GCM Message")
                .SetContentText (message)
                .SetAutoCancel (true)
                .SetContentIntent (pendingIntent);

            var notificationManager = (NotificationManager)GetSystemService(Context.NotificationService);
            notificationManager.Notify (0, notificationBuilder.Build());
        }
    }
}

让我们看看 GcmListenerService 的每个部分,以了解它的工作原理。Let's take a look at each section of our GcmListenerService to understand how it works.

首先,我们使用特性对 GcmListenerService 进行批注,以指示该服务不是由系统实例化的,并且我们包含一个意向筛选器来指示它接收 GCM 消息:First, we annotate GcmListenerService with an attribute to indicate that this service is not to be instantiated by the system, and we include an intent filter to indicate that it receives GCM messages:

[Service (Exported = false), IntentFilter (new [] { "com.google.android.c2dm.intent.RECEIVE" })]

GcmListenerService 从 GCM 收到消息时,将调用 OnMessageReceived 方法。When GcmListenerService receives a message from GCM, the OnMessageReceived method is invoked. 此方法从传入的 Bundle提取消息内容,记录消息内容(以便我们可以在 "输出" 窗口中查看该内容),并调用 SendNotification 以使用收到的消息内容来启动本地通知:This method extracts the message content from the passed-in Bundle, logs the message content (so we can view it in the output window), and calls SendNotification to launch a local notification with the received message content:

var message = data.GetString ("message");
Log.Debug ("MyGcmListenerService", "From:    " + from);
Log.Debug ("MyGcmListenerService", "Message: " + message);
SendNotification (message);

SendNotification 方法使用 Notification.Builder 来创建通知,然后使用 NotificationManager 启动通知。The SendNotification method uses Notification.Builder to create the notification, and then it uses the NotificationManager to launch the notification. 实际上,这会将远程通知消息转换为向用户显示的本地通知。Effectively, this converts the remote notification message into a local notification to be presented to the user. 有关使用 Notification.BuilderNotificationManager的详细信息,请参阅本地通知For more information about using Notification.Builder and NotificationManager, see Local Notifications.

在清单中声明接收方Declare the Receiver in the Manifest

我们必须在 Android 清单中声明 GCM 侦听器,然后才能从 GCM 接收消息。Before we can receive messages from GCM, we must declare the GCM listener in the Android manifest. 让我们编辑androidmanifest.xml ,并将 <application> 部分替换为以下 xml:Let's edit AndroidManifest.xml and replace the <application> section with the following XML:

<application android:label="RemoteNotifications" android:icon="@drawable/Icon">
    <receiver android:name="com.google.android.gms.gcm.GcmReceiver" 
              android:exported="true" 
              android:permission="com.google.android.c2dm.permission.SEND">
        <intent-filter>
            <action android:name="com.google.android.c2dm.intent.RECEIVE" />
            <action android:name="com.google.android.c2dm.intent.REGISTRATION" />
            <category android:name="YOUR_PACKAGE_NAME" />
        </intent-filter>
    </receiver>
</application>

在上面的 XML 中,将YOUR_PACKAGE_NAME更改为客户端应用程序项目的包名称。In the above XML, change YOUR_PACKAGE_NAME to the package name for your client app project. 在我们的演练示例中,包名称是 com.xamarin.gcmexampleIn our walkthrough example, the package name is com.xamarin.gcmexample.

让我们看看此 XML 中的每个设置的作用:Let's look at what each setting in this XML does:

设置Setting 描述Description
com.google.android.gms.gcm.GcmReceiver 声明我们的应用程序实现了捕获和处理传入推送通知消息的 GCM 接收方。Declares that our app implements a GCM receiver that captures and processes incoming push notification messages.
com.google.android.c2dm.permission.SEND 声明仅 GCM 服务器可以直接将消息发送到应用程序。Declares that only GCM servers can send messages directly to the app.
com.google.android.c2dm.intent.RECEIVE 意向筛选器广告,我们的应用处理来自 GCM 的广播消息。Intent filter advertising that our app handles broadcast messages from GCM.
com.google.android.c2dm.intent.REGISTRATION 意向筛选器广告:我们的应用程序处理新的注册意向(也就是说,我们实现了一个实例 ID 侦听器服务)。Intent filter advertising that our app handles new registration intents (that is, we have implemented an Instance ID Listener Service).

或者,你可以用这些特性来修饰 GcmListenerService,而不是在 XML 中指定它们;在这里,我们在androidmanifest.xml中指定这些代码示例,使其更易于理解。Alternatively, you can decorate GcmListenerService with these attributes rather than specifying them in XML; here we specify them in AndroidManifest.xml so that the code samples are easier to follow.

创建消息发件人来测试应用程序Create a Message Sender to Test the App

让我们将C#桌面控制台应用程序项目添加到解决方案,并将其称为MessageSenderLet's add a C# desktop console application project to the Solution and call it MessageSender. 我们将使用此控制台应用程序模拟应用程序服务器 – 它会通过 GCM 向ClientApp发送通知消息。We'll use this console application to simulate an application server – it will send notification messages to ClientApp via GCM.

添加 Json.NET 包Add the Json.NET Package

在此控制台应用中,我们将生成一个 JSON 有效负载,其中包含我们要发送到客户端应用程序的通知消息。In this console app, we're building a JSON payload that contains the notification message we want to send to the client app. 我们将使用MessageSender中的Json.NET包,以便更轻松地生成 GCM 所需的 Json 对象。We'll use the Json.NET package in MessageSender to make it easier to build the JSON object required by GCM. 在 Visual Studio 中,右键单击 "引用" > "管理 NuGet 包 ... "在 Visual Studio for Mac 中,右键单击 "包" > "添加包 ... "。In Visual Studio, right-click References > Manage NuGet Packages ...; in Visual Studio for Mac, right-click Packages > Add Packages....

让我们搜索Json.NET包,并将其安装在项目中:Let's search for the Json.NET package and install it in the project:

安装 Json.NET 包Installing the Json.NET package

添加对 System .Net. Http 的引用Add a Reference to System.Net.Http

还需要添加对 System.Net.Http 的引用,以便可以实例化一个用于将测试消息发送到 GCM 的 HttpClientWe'll also need to add a reference to System.Net.Http so that we can instantiate an HttpClient for sending our test message to GCM. MessageSender项目中,右键单击 "引用" > 添加引用并向下滚动,直到看到 ""。In the MessageSender project, Right-click References > Add Reference and scroll down until you see System.Net.Http. 选中 " System .net. Http " 旁边的复选标记,然后单击 "确定"Put a check mark next to System.Net.Http and click OK.

实现发送测试消息的代码Implement Code that Sends a Test Message

MessageSender中,编辑Program.cs ,并将内容替换为以下代码:In MessageSender, edit Program.cs and replace the contents with the following code:

using System;
using System.Net.Http;
using System.Net.Http.Headers;
using System.Text;
using System.Threading.Tasks;
using Newtonsoft.Json.Linq;

namespace MessageSender
{
    class MessageSender
    {
        public const string API_KEY = "YOUR_API_KEY";
        public const string MESSAGE = "Hello, Xamarin!";

        static void Main (string[] args)
        {
            var jGcmData = new JObject();
            var jData = new JObject();

            jData.Add ("message", MESSAGE);
            jGcmData.Add ("to", "/topics/global");
            jGcmData.Add ("data", jData);

            var url = new Uri ("https://gcm-http.googleapis.com/gcm/send");
            try
            {
                using (var client = new HttpClient())
                {
                    client.DefaultRequestHeaders.Accept.Add(
                        new MediaTypeWithQualityHeaderValue("application/json"));

                    client.DefaultRequestHeaders.TryAddWithoutValidation (
                        "Authorization", "key=" + API_KEY);

                    Task.WaitAll(client.PostAsync (url,
                        new StringContent(jGcmData.ToString(), Encoding.Default, "application/json"))
                            .ContinueWith(response =>
                            {
                                Console.WriteLine(response);
                                Console.WriteLine("Message sent: check the client device notification tray.");
                            }));
                }
            }
            catch (Exception e)
            {
                Console.WriteLine("Unable to send GCM message:");
                Console.Error.WriteLine(e.StackTrace);
            }
        }
    }
}

在上面的代码中,将YOUR_API_KEY更改为客户端应用程序项目的 API 密钥。In the above code, change YOUR_API_KEY to the API Key for your client app project.

此测试应用服务器将以下 JSON 格式的消息发送到 GCM:This test app server sends the following JSON-formatted message to GCM:

{
  "to": "/topics/global",
  "data": {
    "message": "Hello, Xamarin!"
  }
}

然后,GCM 会将此消息转发到客户端应用。GCM, in turn, forwards this message to your client app. 让我们生成MessageSender ,并打开一个控制台窗口,可在其中通过命令行运行该窗口。Let's build MessageSender and open a console window where we can run it from the command line.

尝试一下!Try It!

现在,我们已准备好测试客户端应用程序。Now we're ready to test our client app. 如果你使用的是仿真程序,或者如果你的设备通过 Wi-fi 与 GCM 通信,则必须在防火墙上打开以下 TCP 端口,以便 GCM 消息获得:5228、5229和5230。If you're using an emulator or if your device is communicating with GCM over Wi-Fi, you must open the following TCP ports on your firewall for GCM messages to get through: 5228, 5229, and 5230.

启动客户端应用程序并查看 "输出" 窗口。Start your client app and watch the output window. RegistrationIntentService 成功接收 GCM 的注册令牌后,输出窗口应显示类似于以下的日志输出的令牌:After the RegistrationIntentService successfully receives a registration token from GCM, the output window should display the token with log output resembling the following:

I/RegistrationIntentService(16103): GCM Registration Token: eX9ggabZV1Q:APA91bHjBnQXMUeBOT6JDiLpRt8m2YWtY ...

此时,客户端应用程序已准备好接收远程通知消息。At this point the client app is ready to receive a remote notification message. 在命令行中运行MessageSender程序,将 "Hello,Xamarin" 通知消息发送到客户端应用程序。From the command line, run the MessageSender.exe program to send a "Hello, Xamarin" notification message to the client app. 如果尚未生成MessageSender项目,请立即执行此操作。If you have not yet built the MessageSender project, do so now.

若要在 Visual Studio 下运行MessageSender ,请打开命令提示符,更改为MessageSender/bin/Debug目录,并直接运行命令:To run MessageSender.exe under Visual Studio, open a command prompt, change to the MessageSender/bin/Debug directory, and run the command directly:

MessageSender.exe

若要在 Visual Studio for Mac 下运行MessageSender ,请打开终端会话,更改为MessageSender/bin/Debug目录,并使用 mono 运行MessageSenderTo run MessageSender.exe under Visual Studio for Mac, open a Terminal session, change to MessageSender/bin/Debug the directory, and use mono to run MessageSender.exe

mono MessageSender.exe

消息通过 GCM 传播并返回到客户端应用可能需要长达一分钟的时间。It may take up to a minute for the message to propagate through GCM and back down to your client app. 如果成功接收消息,则应在输出窗口中看到类似于以下内容的输出:If the message is received successfully, we should see output resembling the following in the output window:

D/MyGcmListenerService(16103): From:    /topics/global
D/MyGcmListenerService(16103): Message: Hello, Xamarin!

此外,还应注意通知托盘中显示了新的通知图标:In addition, you should notice that a new notification icon has appeared in the notification tray:

设备上出现通知图标Notification icon appears on device

打开通知托盘以查看通知时,应会看到远程通知:When you open the notification tray to view notifications, you should see our remote notification:

显示通知消息Notification message is displayed

恭喜,你的应用程序已收到第一个远程通知!Congratulations, your app has received its first remote notification!

请注意,如果应用程序被强制停止,将不再接收 GCM 消息。Note that GCM messages will no longer be received if the app is force-stopped. 若要在强制停止后恢复通知,必须手动重新启动应用。To resume notifications after a force-stop, the app must be manually restarted. 有关此 Android 策略的详细信息,请参阅在已停止的应用程序上启动控件和此堆栈溢出 postFor more information about this Android policy, see Launch controls on stopped applications and this stack overflow post.

总结Summary

本演练详细介绍了在 Xamarin Android 应用程序中实施远程通知的步骤。This walkthrough detailed the steps for implementing remote notifications in a Xamarin.Android application. 本文介绍了如何安装 GCM 通信所需的其他程序包,并介绍了如何配置应用程序权限以访问 GCM 服务器。It described how to install additional packages needed for GCM communications, and it explained how to configure app permissions for access to GCM servers. 其中提供了示例代码,该代码演示了如何检查是否存在 Google Play Services,如何实现注册意向服务和实例 ID 侦听器服务,该服务与 GCM 协商以获取注册令牌,以及如何实现 GCM 侦听器接收和处理远程通知消息的服务。It provided example code that illustrates how to check for the presence of Google Play Services, how to implement a registration intent service and instance ID listener service that negotiates with GCM for a registration token, and how to implement a GCM listener service that receives and processes remote notification messages. 最后,我们实现了一个命令行测试程序,用于通过 GCM 将测试通知发送到客户端应用程序。Finally, we implemented a command-line test program to send test notifications to our client app through GCM.