Руководство. Отправка push-уведомлений в определенные приложения Android с помощью Центров уведомлений Azure

Примечание.

Сведения об отмене и миграции Firebase Cloud Messaging см. в статье о миграции Google Firebase Cloud Messaging.

Этот учебник демонстрирует, как использовать концентраторы уведомлений для отправки push-уведомлений конкретному пользователю приложения или на конкретное устройство. Серверная часть ASP.NET WebAPI используется для проверки подлинности клиентов и создания уведомлений, как показано в разделе Управление регистрацией из серверной части. В этом руководстве описывается центр уведомлений, созданный в руководстве. Push-уведомления на устройства Android с помощью Центров уведомлений Azure и Firebase Cloud Messaging.

При работе с этим руководством вы выполните следующие задачи:

  • Создание проекта веб-API серверной части, который выполняет проверку подлинности пользователей.
  • Обновление приложения Android.
  • Тестирование приложения

Необходимые компоненты

Выполните инструкции по отправке push-уведомлений на устройства Android с помощью Центров уведомлений Azure и Firebase Cloud Messaging перед выполнением этого руководства.

Создание проекта веб-API

В этой статье описывается создание серверной части веб-API ASP.NET. Этот процесс состоит из трех главных задач.

  • Аутентификация клиентов. Вы добавите обработчик сообщений для аутентификации клиентских запросов и связывания пользователя с запросом.
  • Регистрация для получения уведомлений с помощью серверной части веб-API. Вы добавите контроллер для обработки новых регистраций клиентских устройств для получения уведомлений. Имя пользователя, прошедшего аутентификацию, автоматически добавляется в регистрацию как тег.
  • Отправка уведомлений клиентам. Вы также добавите контроллер, который позволит активировать безопасную отправку push-уведомлений устройствам и клиентам, связанным с тегом.

Чтобы создать серверную часть веб-API ASP.NET Core 6.0, сделайте следующее:

Чтобы проверить, запустите Visual Studio. Откройте меню Средства и выберите пункт Расширения и обновления. Найдите диспетчер пакетов NuGet в своей версии Visual Studio и убедитесь, что у вас установлена последняя версия. Если вы используете не последнюю версию, удалите ее, а затем переустановите диспетчер пакетов NuGet.

Screenshot of the Extensions and Updates dialog box with the NuGet Package manage for Visual Studios package highlighted.

Примечание.

Убедитесь, что вы установили пакет SDK для Azure для Visual Studio, используемый для развертывания веб-сайта.

  1. Запустите Visual Studio или Visual Studio Express.

  2. Щелкните Обозреватель серверов и войдите в свою учетную запись Azure. Чтобы создать ресурсы веб-сайта в своей учетной записи, вы должны войти.

  3. В Visual Studio в меню Файл выберите пункты Создать>Проект.

  4. В поле поиска введите Веб-API.

  5. Выберите шаблон проекта Веб-API ASP.NET Core и нажмите кнопку Далее.

  6. В диалоговом окне Настроить новый проект присвойте проекту имя AppBackend и нажмите кнопку Далее.

  7. В диалоговом окне Дополнительные сведения выполните следующие действия.

    • Убедитесь, что для параметра Платформа выбрано значение .NET 6.0 (долгосрочная поддержка).
    • Убедитесь, что флажок Use controllers (uncheck to use minimal APIs) (Использовать контроллеры (снимите этот флажок для использования минимальных API)) установлен.
    • Снимите флажок Включить поддержку OpenAPI.
    • Нажмите кнопку создания.

Удаление файлов шаблонов WeatherForecast

  1. Удалите файлы примеров WeatherForecast.cs и Controllers/WeatherForecastController.cs из нового проекта AppBackend.
  2. Откройте файл Properties\launchSettings.json.
  3. Измените свойства launchUrl с weatherforcast на appbackend.

В окне Настройка веб-приложения Microsoft Azure выберите подписку, а затем в списке План службы приложений выполните одно из следующих действий:

  • Выберите план службы приложений Azure, который вы уже создали.
  • Выберите Создать план служб приложений, а затем создайте его.

База данных для этого руководства не требуется. Выбрав план служб приложений, нажмите кнопку ОК, чтобы создать проект.

The Configure Microsoft Azure Web App window

Если вы не видите эту страницу для настройки плана службы приложений, продолжайте выполнять руководство. Его можно настроить позднее при публикации приложения.

Аутентификация клиентов в серверной части веб-API.

В этом разделе вы создадите класс обработчика сообщений с именем AuthenticationTestHandler для новой серверной части. Этот класс является производным от DelegatingHandler. Он добавляется в качестве обработчика сообщений, чтобы обрабатывать все запросы, поступающие в серверную часть.

  1. В обозревателе решений щелкните правой кнопкой мыши проект AppBackend, выберите Добавить, а затем щелкните Класс.

  2. Присвойте новому классу имя AuthenticationTestHandler.cs и нажмите кнопку Добавить, чтобы создать класс. Этот класс используется для аутентификации пользователей с помощью обычной проверки подлинности для простоты. Ваше приложение может использовать любую схему аутентификации.

  3. В AuthenticationTestHandler.cs добавьте следующие операторы using :

    using System.Net.Http;
    using System.Threading;
    using System.Security.Principal;
    using System.Net;
    using System.Text;
    using System.Threading.Tasks;
    
  4. В классе AuthenticationTestHandler.cs замените определение AuthenticationTestHandler следующим кодом:

    Обработчик авторизует запрос, если выполнены все три следующих условия:

    • Запрос включен в заголовок авторизации.
    • Запрос использует обычную проверку подлинности.
    • Строка имени пользователя и строка пароля являются одной стройкой.

    В противном случае запрос отклоняется. Этот способ аутентификации нельзя назвать настоящим методом аутентификации и авторизации. Это простой пример для этого руководства.

    Если сообщение запроса аутентифицируется и авторизуется AuthenticationTestHandler, пользователь обычной проверки подлинности подключается к текущему запросу в HttpContext. Позднее информацию о пользователе в HttpContext будет использовать другой контроллер (RegisterController), чтобы добавить тег в запрос на регистрацию для получения уведомлений.

    public class AuthenticationTestHandler : DelegatingHandler
    {
        protected override Task<HttpResponseMessage> SendAsync(
        HttpRequestMessage request, CancellationToken cancellationToken)
        {
            var authorizationHeader = request.Headers.GetValues("Authorization").First();
    
            if (authorizationHeader != null && authorizationHeader
                .StartsWith("Basic ", StringComparison.InvariantCultureIgnoreCase))
            {
                string authorizationUserAndPwdBase64 =
                    authorizationHeader.Substring("Basic ".Length);
                string authorizationUserAndPwd = Encoding.Default
                    .GetString(Convert.FromBase64String(authorizationUserAndPwdBase64));
                string user = authorizationUserAndPwd.Split(':')[0];
                string password = authorizationUserAndPwd.Split(':')[1];
    
                if (VerifyUserAndPwd(user, password))
                {
                    // Attach the new principal object to the current HttpContext object
                    HttpContext.Current.User =
                        new GenericPrincipal(new GenericIdentity(user), new string[0]);
                    System.Threading.Thread.CurrentPrincipal =
                        System.Web.HttpContext.Current.User;
                }
                else return Unauthorized();
            }
            else return Unauthorized();
    
            return base.SendAsync(request, cancellationToken);
        }
    
        private bool VerifyUserAndPwd(string user, string password)
        {
            // This is not a real authentication scheme.
            return user == password;
        }
    
        private Task<HttpResponseMessage> Unauthorized()
        {
            var response = new HttpResponseMessage(HttpStatusCode.Forbidden);
            var tsc = new TaskCompletionSource<HttpResponseMessage>();
            tsc.SetResult(response);
            return tsc.Task;
        }
    }
    

    Примечание.

    Примечание о безопасности. Класс AuthenticationTestHandler не обеспечивает настоящую аутентификацию. Этот класс используется лишь для имитации базовой проверки подлинности и не является безопасным. В реальных приложениях и службах необходимо реализовать безопасный механизм проверки подлинности.

  5. Чтобы зарегистрировать обработчик событий, добавьте в конец метода Register в файле Program.cs следующий код:

    config.MessageHandlers.Add(new AuthenticationTestHandler());
    
  6. Сохранение изменений.

Регистрация для получения уведомлений с помощью серверной части веб-API.

В этом разделе вы добавите новый контроллер в серверную часть веб-API, которая будет обрабатывать запросы на регистрацию пользователя и устройства для получения уведомлений с помощью клиентской библиотеки центров уведомлений. Контроллер будет добавлять тег пользователя для пользователя, который прошел аутентификацию и подключен к HttpContext с помощью AuthenticationTestHandler. Тег имеет формат строки: "username:<actual username>".

  1. В обозревателе решений щелкните правой кнопкой мыши проект AppBackend и выберите пункт Управление пакетами NuGet.

  2. На панели слева выберите В сети, затем в поле поиска введите Microsoft.Azure.NotificationHubs.

  3. В списке результатов выберите Центры уведомлений Microsoft Azure, а затем нажмите кнопку Установить. Завершите установку и закройте окно диспетчера пакетов NuGet.

    Это действие добавляет ссылку на пакет SDK для Центров уведомлений Azure с помощью пакета NuGet Microsoft.Azure.Notification Hubs.

  4. Создайте файл класса, представляющий подключение к концентратору уведомлений, который используется для отправки уведомлений. В обозревателе решений щелкните правой кнопкой мыши папку Модели, выберите Добавить, а затем щелкните Класс. Назовите новый класс Notifications.cs и нажмите кнопку Добавить, чтобы создать класс.

    The Add New Item window

  5. В Notifications.cs добавьте следующий оператор using в начало файла:

    using Microsoft.Azure.NotificationHubs;
    
  6. Замените определение класса Notifications следующим кодом и замените два заполнителя строкой подключения (с полным доступом) для своего концентратора уведомлений и именем концентратора (доступно на портале Azure):

    public class Notifications
    {
        public static Notifications Instance = new Notifications();
    
        public NotificationHubClient Hub { get; set; }
    
        private Notifications() {
            Hub = NotificationHubClient.CreateClientFromConnectionString("<your hub's DefaultFullSharedAccessSignature>",
                                                                            "<hub name>");
        }
    }
    

    Важно!

    Прежде чем продолжить, введите имя и DefaultFullSharedAccessSignature вашего концентратора.

  7. Теперь создайте новый контроллер с именем RegisterController. В обозревателе решений щелкните правой кнопкой мыши папку Контроллеры, выберите Добавить, а затем щелкните Контроллер.

  8. Выберите Контроллер API — пустой, а затем нажмите кнопку Добавить.

  9. В поле Имя контроллера введите имя нового класса RegisterController, а затем нажмите кнопку Добавить.

    The Add Controller window.

  10. В RegiterController.cs добавьте следующие операторы using :

    using Microsoft.Azure.NotificationHubs;
    using Microsoft.Azure.NotificationHubs.Messaging;
    using AppBackend.Models;
    using System.Threading.Tasks;
    using System.Web;
    
  11. Добавьте в определение класса RegisterController следующий код: В этом коде вы добавите тег для пользователя, который подключен к HttpContext. Пользователь прошел аутентификацию и подключен к HttpContext с помощью добавленного фильтра сообщений AuthenticationTestHandler. Вы можете также добавить дополнительные проверки, чтобы убедиться, что у пользователя есть право регистрации запрошенных тегов.

    private NotificationHubClient hub;
    
    public RegisterController()
    {
        hub = Notifications.Instance.Hub;
    }
    
    public class DeviceRegistration
    {
        public string Platform { get; set; }
        public string Handle { get; set; }
        public string[] Tags { get; set; }
    }
    
    // POST api/register
    // This creates a registration id
    public async Task<string> Post(string handle = null)
    {
        string newRegistrationId = null;
    
        // make sure there are no existing registrations for this push handle (used for iOS and Android)
        if (handle != null)
        {
            var registrations = await hub.GetRegistrationsByChannelAsync(handle, 100);
    
            foreach (RegistrationDescription registration in registrations)
            {
                if (newRegistrationId == null)
                {
                    newRegistrationId = registration.RegistrationId;
                }
                else
                {
                    await hub.DeleteRegistrationAsync(registration);
                }
            }
        }
    
        if (newRegistrationId == null) 
            newRegistrationId = await hub.CreateRegistrationIdAsync();
    
        return newRegistrationId;
    }
    
    // PUT api/register/5
    // This creates or updates a registration (with provided channelURI) at the specified id
    public async Task<HttpResponseMessage> Put(string id, DeviceRegistration deviceUpdate)
    {
        RegistrationDescription registration = null;
        switch (deviceUpdate.Platform)
        {
            case "mpns":
                registration = new MpnsRegistrationDescription(deviceUpdate.Handle);
                break;
            case "wns":
                registration = new WindowsRegistrationDescription(deviceUpdate.Handle);
                break;
            case "apns":
                registration = new AppleRegistrationDescription(deviceUpdate.Handle);
                break;
            case "fcm":
                registration = new FcmRegistrationDescription(deviceUpdate.Handle);
                break;
            default:
                throw new HttpResponseException(HttpStatusCode.BadRequest);
        }
    
        registration.RegistrationId = id;
        var username = HttpContext.Current.User.Identity.Name;
    
        // add check if user is allowed to add these tags
        registration.Tags = new HashSet<string>(deviceUpdate.Tags);
        registration.Tags.Add("username:" + username);
    
        try
        {
            await hub.CreateOrUpdateRegistrationAsync(registration);
        }
        catch (MessagingException e)
        {
            ReturnGoneIfHubResponseIsGone(e);
        }
    
        return Request.CreateResponse(HttpStatusCode.OK);
    }
    
    // DELETE api/register/5
    public async Task<HttpResponseMessage> Delete(string id)
    {
        await hub.DeleteRegistrationAsync(id);
        return Request.CreateResponse(HttpStatusCode.OK);
    }
    
    private static void ReturnGoneIfHubResponseIsGone(MessagingException e)
    {
        var webex = e.InnerException as WebException;
        if (webex.Status == WebExceptionStatus.ProtocolError)
        {
            var response = (HttpWebResponse)webex.Response;
            if (response.StatusCode == HttpStatusCode.Gone)
                throw new HttpRequestException(HttpStatusCode.Gone.ToString());
        }
    }
    
  12. Сохранение изменений.

Отправка уведомлений из серверной части веб-API.

В этом разделе вы добавите новый контроллер, который позволяет клиентским устройствам отправлять уведомления. Уведомление основано на теге имени пользователя, который использует библиотеку .NET центров уведомлений Azure в серверной части веб-API ASP.NET.

  1. Создайте еще один контроллер с именем NotificationsController так же, как вы создали RegisterController в предыдущем разделе.

  2. В NotificationsController.cs добавьте следующие операторы using :

    using AppBackend.Models;
    using System.Threading.Tasks;
    using System.Web;
    
  3. Добавьте следующий метод в класс NotificationsController:

    Этот код отправляет тип уведомлений, основанный на параметре pns системы отправки уведомлений платформы (PNS). Значение to_tag используется для задания тега имени пользователя в сообщении. Этот тег должен соответствовать тегу имени пользователя активной регистрации центра уведомлений. Сообщение уведомления извлекается из текста запроса POST и форматируется для целевого PNS.

    Поддержка форматов уведомлений зависит от того, какую систему PNS используют поддерживаемые устройства. Например, на устройствах Windows можно использовать всплывающие уведомления с помощью WNS, которые не поддерживает другая система PNS. В этом случае серверная часть решения конвертирует уведомление в формат, соответствующий PNS устройств, которые вы планируете поддерживать. Затем используйте соответствующий API отправки для класса NotificationHubClient.

    public async Task<HttpResponseMessage> Post(string pns, [FromBody]string message, string to_tag)
    {
        var user = HttpContext.Current.User.Identity.Name;
        string[] userTag = new string[2];
        userTag[0] = "username:" + to_tag;
        userTag[1] = "from:" + user;
    
        Microsoft.Azure.NotificationHubs.NotificationOutcome outcome = null;
        HttpStatusCode ret = HttpStatusCode.InternalServerError;
    
        switch (pns.ToLower())
        {
            case "wns":
                // Windows 8.1 / Windows Phone 8.1
                var toast = @"<toast><visual><binding template=""ToastText01""><text id=""1"">" + 
                            "From " + user + ": " + message + "</text></binding></visual></toast>";
                outcome = await Notifications.Instance.Hub.SendWindowsNativeNotificationAsync(toast, userTag);
                break;
            case "apns":
                // iOS
                var alert = "{\"aps\":{\"alert\":\"" + "From " + user + ": " + message + "\"}}";
                outcome = await Notifications.Instance.Hub.SendAppleNativeNotificationAsync(alert, userTag);
                break;
            case "fcm":
                // Android
                var notif = "{ \"data\" : {\"message\":\"" + "From " + user + ": " + message + "\"}}";
                outcome = await Notifications.Instance.Hub.SendFcmNativeNotificationAsync(notif, userTag);
                break;
        }
    
        if (outcome != null)
        {
            if (!((outcome.State == Microsoft.Azure.NotificationHubs.NotificationOutcomeState.Abandoned) ||
                (outcome.State == Microsoft.Azure.NotificationHubs.NotificationOutcomeState.Unknown)))
            {
                ret = HttpStatusCode.OK;
            }
        }
    
        return Request.CreateResponse(ret);
    }
    
  4. Нажмите клавишу F5, чтобы запустить приложение и убедиться, что все правильно работает. Приложение откроется в веб-браузере и отобразится на домашней странице ASP.NET.

Публикация новой серверной части веб-API.

Теперь разверните приложение на веб-сайте Azure, чтобы сделать его доступным для всех устройств.

  1. Щелкните правой кнопкой мыши проект AppBackend и нажмите кнопку Опубликовать.

  2. Выберите службу приложений Microsoft Azure, в которой вы хотите опубликовать приложение, и нажмите кнопку \*\*Опубликовать. Откроется окно создания службы приложений. В этом окне можно создать все необходимые ресурсы Azure для запуска веб-приложения ASP.NET в Azure.

    The Microsoft Azure App Service tile

  3. В окне Создание службы приложений выберите свою учетную запись Azure. Выберите Изменить тип>Веб-приложение. Не изменяйте стандартное имя веб-приложения. Выберите подписку, группу ресурсов и план службы приложений.

  4. Нажмите кнопку создания.

  5. Запишите свойство URL-адрес сайта в разделе Сводка. Это URL-адрес конечной точки внутренней части.

  6. Выберите Опубликовать.

Когда мастер завершит работу, веб-приложение ASP.NET будет опубликовано в Azure и откроется в браузере по умолчанию. Приложение также будет отображаться в службах приложений Azure.

В URL-адресе используется имя веб-приложения, указанное ранее, в формате http://<имя_приложения>.azurewebsites.net.

Создание проекта Android

Следующим шагом является обновление приложения Android, созданного в руководстве. Push-уведомления на устройства Android с помощью Центров уведомлений Azure и Firebase Cloud Messaging.

  1. Откройте файл res/layout/activity_main.xml и замените его содержимое приведенными ниже определениями содержимого.

    Это добавляет новые элементы управления EditText для входа в систему как пользователь. Также добавляется поле для тега имени пользователя, который будет добавляться в отправляемые уведомления:

    <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".MainActivity">
    
    <EditText
        android:id="@+id/usernameText"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:ems="10"
        android:hint="@string/usernameHint"
        android:layout_above="@+id/passwordText"
        android:layout_alignParentEnd="true" />
    <EditText
        android:id="@+id/passwordText"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:ems="10"
        android:hint="@string/passwordHint"
        android:inputType="textPassword"
        android:layout_above="@+id/buttonLogin"
        android:layout_alignParentEnd="true" />
    <Button
        android:id="@+id/buttonLogin"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="@string/loginButton"
        android:onClick="login"
        android:layout_above="@+id/toggleButtonFCM"
        android:layout_centerHorizontal="true"
        android:layout_marginBottom="24dp" />
    <ToggleButton
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:textOn="WNS on"
        android:textOff="WNS off"
        android:id="@+id/toggleButtonWNS"
        android:layout_toLeftOf="@id/toggleButtonFCM"
        android:layout_centerVertical="true" />
    <ToggleButton
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:textOn="FCM on"
        android:textOff="FCM off"
        android:id="@+id/toggleButtonFCM"
        android:checked="true"
        android:layout_centerHorizontal="true"
        android:layout_centerVertical="true" />
    <ToggleButton
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:textOn="APNS on"
        android:textOff="APNS off"
        android:id="@+id/toggleButtonAPNS"
        android:layout_toRightOf="@id/toggleButtonFCM"
        android:layout_centerVertical="true" />
    <EditText
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:id="@+id/editTextNotificationMessageTag"
        android:layout_below="@id/toggleButtonFCM"
        android:layout_centerHorizontal="true"
        android:hint="@string/notification_message_tag_hint" />
    <EditText
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:id="@+id/editTextNotificationMessage"
        android:layout_below="@+id/editTextNotificationMessageTag"
        android:layout_centerHorizontal="true"
        android:hint="@string/notification_message_hint" />
    <Button
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="@string/send_button"
        android:id="@+id/sendbutton"
        android:onClick="sendNotificationButtonOnClick"
        android:layout_below="@+id/editTextNotificationMessage"
        android:layout_centerHorizontal="true" />
    <TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="Hello World!"
        android:id="@+id/text_hello"
        />
    </RelativeLayout>
    
  2. Откройте файл res/values/strings.xml и замените определение send_button следующими строками, переопределяющими строку для send_button, и добавьте строки для других элементов управления:

    <string name="usernameHint">Username</string>
    <string name="passwordHint">Password</string>
    <string name="loginButton">1. Sign in</string>
    <string name="send_button">2. Send Notification</string>
    <string name="notification_message_hint">Notification message</string>
    <string name="notification_message_tag_hint">Recipient username</string>
    

    Графическая структура файла main_activity.xml должна выглядеть следующим образом:

    Screenshot of an emulator displaying what the main activity X M L graphical layout will look like.

  3. Создайте класс RegisterClient в том же пакете, в котором создан класс MainActivity. Используйте приведенный ниже код для файла нового класса.

    
    import java.io.IOException;
    import java.io.UnsupportedEncodingException;
    import java.util.Set;
    
    import org.apache.http.HttpResponse;
    import org.apache.http.HttpStatus;
    import org.apache.http.client.ClientProtocolException;
    import org.apache.http.client.HttpClient;
    import org.apache.http.client.methods.HttpPost;
    import org.apache.http.client.methods.HttpPut;
    import org.apache.http.client.methods.HttpUriRequest;
    import org.apache.http.entity.StringEntity;
    import org.apache.http.impl.client.DefaultHttpClient;
    import org.apache.http.util.EntityUtils;
    import org.json.JSONArray;
    import org.json.JSONException;
    import org.json.JSONObject;
    
    import android.content.Context;
    import android.content.SharedPreferences;
    import android.util.Log;
    
    public class RegisterClient {
        private static final String PREFS_NAME = "ANHSettings";
        private static final String REGID_SETTING_NAME = "ANHRegistrationId";
        private String Backend_Endpoint;
        SharedPreferences settings;
        protected HttpClient httpClient;
        private String authorizationHeader;
    
        public RegisterClient(Context context, String backendEndpoint) {
            super();
            this.settings = context.getSharedPreferences(PREFS_NAME, 0);
            httpClient =  new DefaultHttpClient();
            Backend_Endpoint = backendEndpoint + "/api/register";
        }
    
        public String getAuthorizationHeader() {
            return authorizationHeader;
        }
    
        public void setAuthorizationHeader(String authorizationHeader) {
            this.authorizationHeader = authorizationHeader;
        }
    
        public void register(String handle, Set<String> tags) throws ClientProtocolException, IOException, JSONException {
            String registrationId = retrieveRegistrationIdOrRequestNewOne(handle);
    
            JSONObject deviceInfo = new JSONObject();
            deviceInfo.put("Platform", "fcm");
            deviceInfo.put("Handle", handle);
            deviceInfo.put("Tags", new JSONArray(tags));
    
            int statusCode = upsertRegistration(registrationId, deviceInfo);
    
            if (statusCode == HttpStatus.SC_OK) {
                return;
            } else if (statusCode == HttpStatus.SC_GONE){
                settings.edit().remove(REGID_SETTING_NAME).commit();
                registrationId = retrieveRegistrationIdOrRequestNewOne(handle);
                statusCode = upsertRegistration(registrationId, deviceInfo);
                if (statusCode != HttpStatus.SC_OK) {
                    Log.e("RegisterClient", "Error upserting registration: " + statusCode);
                    throw new RuntimeException("Error upserting registration");
                }
            } else {
                Log.e("RegisterClient", "Error upserting registration: " + statusCode);
                throw new RuntimeException("Error upserting registration");
            }
        }
    
        private int upsertRegistration(String registrationId, JSONObject deviceInfo)
                throws UnsupportedEncodingException, IOException,
                ClientProtocolException {
            HttpPut request = new HttpPut(Backend_Endpoint+"/"+registrationId);
            request.setEntity(new StringEntity(deviceInfo.toString()));
            request.addHeader("Authorization", "Basic "+authorizationHeader);
            request.addHeader("Content-Type", "application/json");
            HttpResponse response = httpClient.execute(request);
            int statusCode = response.getStatusLine().getStatusCode();
            return statusCode;
        }
    
        private String retrieveRegistrationIdOrRequestNewOne(String handle) throws ClientProtocolException, IOException {
            if (settings.contains(REGID_SETTING_NAME))
                return settings.getString(REGID_SETTING_NAME, null);
    
            HttpUriRequest request = new HttpPost(Backend_Endpoint+"?handle="+handle);
            request.addHeader("Authorization", "Basic "+authorizationHeader);
            HttpResponse response = httpClient.execute(request);
            if (response.getStatusLine().getStatusCode() != HttpStatus.SC_OK) {
                Log.e("RegisterClient", "Error creating registrationId: " + response.getStatusLine().getStatusCode());
                throw new RuntimeException("Error creating Notification Hubs registrationId");
            }
            String registrationId = EntityUtils.toString(response.getEntity());
            registrationId = registrationId.substring(1, registrationId.length()-1);
    
            settings.edit().putString(REGID_SETTING_NAME, registrationId).commit();
    
            return registrationId;
        }
    }
    

    Этот компонент реализует вызовы REST, чтобы обратиться к серверной части приложения для регистрации в службе push-уведомлений. В данном случае также происходит локальное сохранение идентификаторов registrationId , созданных концентратором уведомлений в соответствии с описанием в разделе Регистрация из серверной части приложения. В этом случае используется маркер авторизации, сохраненный в локальном хранилище при нажатии кнопки входа.

  4. В классе MainActivity добавьте поле для класса RegisterClient и строку для конечной точки серверной части ASP.NET. Обязательно замените <Enter Your Backend Endpoint> фактической конечной точкой серверной части, полученной ранее. Например, http://mybackend.azurewebsites.net.

    private RegisterClient registerClient;
    private static final String BACKEND_ENDPOINT = "<Enter Your Backend Endpoint>";
    FirebaseInstanceId fcm;
    String FCM_token = null;
    
  5. В классе MainActivity в методе onCreate удалите или преобразуйте в комментарий инициализацию поля hub и вызов метода registerWithNotificationHubs. Добавьте код для инициализации нового экземпляра класса RegisterClient . Метод должен содержать следующие строки:

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
    
        mainActivity = this;
        FirebaseService.createChannelAndHandleNotifications(getApplicationContext());
        fcm = FirebaseInstanceId.getInstance();
        registerClient = new RegisterClient(this, BACKEND_ENDPOINT);
        setContentView(R.layout.activity_main);
    }
    
  6. Добавьте следующие инструкции import в файл MainActivity.java.

    import android.util.Base64;
    import android.view.View;
    import android.widget.EditText;
    
    import android.widget.Button;
    import android.widget.ToggleButton;
    import java.io.UnsupportedEncodingException;
    import android.content.Context;
    import java.util.HashSet;
    import android.widget.Toast;
    import org.apache.http.client.ClientProtocolException;
    import java.io.IOException;
    import org.apache.http.HttpStatus;
    
    import android.os.AsyncTask;
    import org.apache.http.HttpResponse;
    import org.apache.http.client.methods.HttpPost;
    import org.apache.http.entity.StringEntity;
    import org.apache.http.impl.client.DefaultHttpClient;
    
    import android.app.AlertDialog;
    import android.content.DialogInterface;
    
    import com.google.firebase.iid.FirebaseInstanceId;
    import com.google.firebase.iid.InstanceIdResult;
    import com.google.android.gms.tasks.OnSuccessListener;
    import java.util.concurrent.TimeUnit;
    
  7. Замените код в методе onStart следующим кодом:

    super.onStart();
    Button sendPush = (Button) findViewById(R.id.sendbutton);
    sendPush.setEnabled(false);
    
  8. Затем добавьте следующие методы для обработки события нажатия кнопки входа и отправки push-уведомлений.

    public void login(View view) throws UnsupportedEncodingException {
        this.registerClient.setAuthorizationHeader(getAuthorizationHeader());
    
        final Context context = this;
        new AsyncTask<Object, Object, Object>() {
            @Override
            protected Object doInBackground(Object... params) {
                try {
    
                    FirebaseInstanceId.getInstance().getInstanceId().addOnSuccessListener(new OnSuccessListener<InstanceIdResult>() {
                        @Override
                        public void onSuccess(InstanceIdResult instanceIdResult) {
                            FCM_token = instanceIdResult.getToken();
                            Log.d(TAG, "FCM Registration Token: " + FCM_token);
                        }
                    });
                    TimeUnit.SECONDS.sleep(1);
                    registerClient.register(FCM_token, new HashSet<String>());
                } catch (Exception e) {
                    DialogNotify("MainActivity - Failed to register", e.getMessage());
                    return e;
                }
                return null;
            }
    
            protected void onPostExecute(Object result) {
                Button sendPush = (Button) findViewById(R.id.sendbutton);
                sendPush.setEnabled(true);
                Toast.makeText(context, "Signed in and registered.",
                        Toast.LENGTH_LONG).show();
            }
        }.execute(null, null, null);
    }
    
    private String getAuthorizationHeader() throws UnsupportedEncodingException {
        EditText username = (EditText) findViewById(R.id.usernameText);
        EditText password = (EditText) findViewById(R.id.passwordText);
        String basicAuthHeader = username.getText().toString()+":"+password.getText().toString();
        basicAuthHeader = Base64.encodeToString(basicAuthHeader.getBytes("UTF-8"), Base64.NO_WRAP);
        return basicAuthHeader;
    }
    
    /**
        * This method calls the ASP.NET WebAPI backend to send the notification message
        * to the platform notification service based on the pns parameter.
        *
        * @param pns     The platform notification service to send the notification message to. Must
        *                be one of the following ("wns", "fcm", "apns").
        * @param userTag The tag for the user who will receive the notification message. This string
        *                must not contain spaces or special characters.
        * @param message The notification message string. This string must include the double quotes
        *                to be used as JSON content.
        */
    public void sendPush(final String pns, final String userTag, final String message)
            throws ClientProtocolException, IOException {
        new AsyncTask<Object, Object, Object>() {
            @Override
            protected Object doInBackground(Object... params) {
                try {
    
                    String uri = BACKEND_ENDPOINT + "/api/notifications";
                    uri += "?pns=" + pns;
                    uri += "&to_tag=" + userTag;
    
                    HttpPost request = new HttpPost(uri);
                    request.addHeader("Authorization", "Basic "+ getAuthorizationHeader());
                    request.setEntity(new StringEntity(message));
                    request.addHeader("Content-Type", "application/json");
    
                    HttpResponse response = new DefaultHttpClient().execute(request);
    
                    if (response.getStatusLine().getStatusCode() != HttpStatus.SC_OK) {
                        DialogNotify("MainActivity - Error sending " + pns + " notification",
                                response.getStatusLine().toString());
                        throw new RuntimeException("Error sending notification");
                    }
                } catch (Exception e) {
                    DialogNotify("MainActivity - Failed to send " + pns + " notification ", e.getMessage());
                    return e;
                }
    
                return null;
            }
        }.execute(null, null, null);
    }
    

    Обработчик login кнопки входа создает маркер обычной проверки подлинности на основании введенного имени пользователя и пароля (это относится к любому маркеру, применяемому в этой схеме проверки подлинности), затем использует RegisterClient для вызова серверной части для регистрации.

    Метод sendPush вызывает серверную часть, чтобы инициировать безопасное уведомление пользователя по тегу пользователя. Служба уведомлений платформы, на которую нацеливается sendPush, зависит от переданной строки pns.

  9. Добавьте приведенный ниже метод DialogNotify в класс MainActivity.

    protected void DialogNotify(String title, String message)
    {
        AlertDialog alertDialog = new AlertDialog.Builder(MainActivity.this).create();
        alertDialog.setTitle(title);
        alertDialog.setMessage(message);
        alertDialog.setButton(AlertDialog.BUTTON_NEUTRAL, "OK",
                new DialogInterface.OnClickListener() {
                    public void onClick(DialogInterface dialog, int which) {
                        dialog.dismiss();
                    }
                });
        alertDialog.show();
    }
    
  10. В классе MainActivity измените метод sendNotificationButtonOnClick для вызова метода sendPush, указав выбранные пользователем службы уведомлений платформы следующим образом.

    /**
    * Send Notification button click handler. This method sends the push notification
    * message to each platform selected.
    *
    * @param v The view
    */
    public void sendNotificationButtonOnClick(View v)
            throws ClientProtocolException, IOException {
    
        String nhMessageTag = ((EditText) findViewById(R.id.editTextNotificationMessageTag))
                .getText().toString();
        String nhMessage = ((EditText) findViewById(R.id.editTextNotificationMessage))
                .getText().toString();
    
        // JSON String
        nhMessage = "\"" + nhMessage + "\"";
    
        if (((ToggleButton)findViewById(R.id.toggleButtonWNS)).isChecked())
        {
            sendPush("wns", nhMessageTag, nhMessage);
        }
        if (((ToggleButton)findViewById(R.id.toggleButtonFCM)).isChecked())
        {
            sendPush("fcm", nhMessageTag, nhMessage);
        }
        if (((ToggleButton)findViewById(R.id.toggleButtonAPNS)).isChecked())
        {
            sendPush("apns", nhMessageTag, nhMessage);
        }
    }
    
  11. В файле build.gradle добавьте следующую строку в раздел android после раздела buildTypes.

    useLibrary 'org.apache.http.legacy'
    
  12. Если приложение предназначено для уровня API 28 (Android 9.0) или последующей версии, включите следующее объявление в элемент <application> в AndroidManifest.xml.

    <uses-library
        android:name="org.apache.http.legacy"
        android:required="false" />
    
  13. Выполните сборку проекта.

Тестирование приложения

  1. Запустите приложение на устройстве или в эмуляторе с помощью Android Studio.

  2. В приложении Android введите имя пользователя и пароль. Они должны быть одинаковыми строковыми значениями без пробелов или специальных знаков.

  3. В приложении Android нажмите кнопку входа. Дождитесь появления всплывающего уведомления о выполнении входа и регистрации. Это активирует кнопку Отправить уведомление.

    Screenshot of an emulator showing what the Notification Hubs Notify Users app looks like after logging in.

  4. С помощью переключателей включите все платформы, на которых вы запускали приложение и регистрировали пользователя.

  5. Введите имя пользователя, который получит уведомление. Он должен быть зарегистрирован для получения уведомлений на целевых устройствах.

  6. Введите сообщение для пользователя, получающего push-уведомление.

  7. Нажмите кнопку Send Notification(Отправить уведомление). Каждое устройство с регистрацией, соответствующей тегу имени пользователя, получит push-уведомление.

Следующие шаги

В этом руководстве вы узнали, как отправлять push-уведомления определенным пользователям, с регистрацией которых связаны теги. Чтобы узнать, как отправлять push-уведомления на основе расположения, перейдите к следующему руководству: