다음을 통해 공유


자습서: Azure Notification Hubs를 사용하여 특정 Android 앱에 푸시 알림 보내기

참고 항목

Firebase Cloud Messaging 사용 중지 및 마이그레이션 단계에 대한 자세한 내용은 Google Firebase Cloud Messaging 마이그레이션을 참조하세요.

이 자습서에서는 Azure Notification Hubs를 사용하여 특정 디바이스의 특정 앱 사용자에게 푸시 알림을 보내는 방법을 보여 줍니다. 앱 백 엔드에서 등록 지침 문서에 나와 있는 대로 ASP.NET WebAPI 백 엔드는 클라이언트를 인증하고 알림을 생성하는 데 사용됩니다. 이 자습서는 자습서: Azure Notification Hubs 및 Firebase Cloud Messaging을 사용하여 Android 디바이스에 알림 푸시에서 만든 알림 허브를 기반으로 합니다.

이 자습서에서 수행하는 단계는 다음과 같습니다.

  • 사용자를 인증하는 백 엔드 Web API 프로젝트를 만듭니다.
  • Android 애플리케이션을 업데이트합니다.
  • 앱 테스트

필수 조건

이 자습서의 내용을 진행하기 전에 자습서: Azure Notification Hubs 및 Firebase Cloud Messaging을 사용하여 Android 디바이스에 알림 푸시를 완료합니다.

WebAPI 프로젝트 만들기

다음 섹션에서는 새 ASP.NET WebAPI 백 엔드 만들기를 설명합니다. 이 프로세스에는 세 가지 주요 목적이 있습니다.

  • 클라이언트 인증: 클라이언트 요청을 인증하고 사용자를 요청과 연결하는 메시지 처리기를 추가합니다.
  • WebAPI 백 엔드를 사용하여 알림 등록: 클라이언트 디바이스에서 알림을 받을 수 있도록 새 등록을 처리하는 컨트롤러를 추가합니다. 인증된 사용자 이름은 태그로 등록에 자동으로 추가됩니다.
  • 클라이언트로 알림 보내기: 사용자가 태그와 연결된 디바이스 및 클라이언트로 보안 푸시를 트리거할 수 있는 방법을 제공하는 컨트롤러를 추가합니다.

다음 작업을 수행하여 새 ASP.NET Core 6.0 웹 API 백 엔드를 만듭니다.

확인하려면 Visual Studio를 시작합니다. 도구 메뉴에서 확장 및 업데이트를 선택합니다. 사용하는 Visual Studio 버전에서 NuGet 패키지 관리자를 검색하고, 현재 버전이 최신 버전인지 확인합니다. 사용하는 버전이 최신 버전이 아닌 경우 해당 버전을 제거한 다음 NuGet 패키지 관리자를 다시 설치합니다.

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

참고 항목

웹 사이트 배포를 위해 Visual Studio Azure SDK를 설치했는지 확인합니다.

  1. Visual Studio 또는 Visual Studio Express를 시작합니다.

  2. 서버 탐색기를 선택하고 Azure 계정에 로그인합니다. 계정에 웹 사이트 리소스를 만들려면 로그인해야 합니다.

  3. Visual Studio의 파일 메뉴에서 새로 만들기>프로젝트를 선택합니다.

  4. 검색 상자에 Web API를 입력합니다.

  5. ASP.NET Core 웹 API 프로젝트 템플릿을 선택하고 다음을 선택합니다.

  6. 새 프로젝트 구성 대화 상자에서 AppBackend 프로젝트 이름을 지정하고 다음을 선택합니다.

  7. 추가 정보 대화 상자에서 다음을 수행합니다.

    • 프레임워크.Net 6.0(장기 지원)인지 확인합니다.
    • 컨트롤러 사용(최소 API를 사용하려면 선택 취소) 확인란을 선택합니다.
    • OpenAPI 지원 사용을 선택 취소합니다.
    • 만들기를 실행합니다.

WeatherForecast 템플릿 파일 제거

  1. AppBackend 프로젝트에서 WeatherForecast.csControllers/WeatherForecastController.cs 예제 파일을 제거합니다.
  2. Properties\launchSettings.json을 엽니다.
  3. launchUrl 속성을 weatherforcast에서 appbackend로 변경합니다.

Microsoft Azure 웹앱 구성 창에서 구독을 선택한 다음, App Service 계획 목록에서 다음 작업 중 하나를 수행합니다.

  • 이미 작성한 Azure App Service 요금제를 선택합니다.
  • 새 앱 서비스 계획 만들기를 선택한 다음 새로 만듭니다.

이 자습서를 위해 데이터베이스는 필요하지 않습니다. 앱 서비스 계획을 선택한 후 확인을 선택하여 프로젝트를 만듭니다.

The Configure Microsoft Azure Web App window

App Service 계획 구성에 사용되는 이 페이지가 보이지 않을 경우 자습서를 진행하세요. 나중에 앱을 게시할 때 구성할 수 있습니다.

WebAPI 백 엔드에 클라이언트 인증

이 섹션에서는 새 백 엔드에 대해 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 클래스 정의를 다음 코드로 바꿉니다.

    처리기는 다음 세 가지 조건이 충족될 때 요청을 인증합니다.

    • 요청에 Authorization 헤더가 포함되어 있습니다.
    • 요청이 기본 인증을 사용합니다.
    • 사용자 이름 문자열과 암호 문자열은 동일한 문자열입니다.

    그렇지 않으면 요청이 거부됩니다. 이 인증은 실제 인증 및 권한 부여 방법이 아닙니다. 이 자습서를 위한 간단한 예제일 뿐입니다.

    요청 메시지가 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. 메시지 처리기를 등록하려면 Program.cs 파일의 Register 메서드 끝에 다음 코드를 추가합니다.

    config.MessageHandlers.Add(new AuthenticationTestHandler());
    
  6. 변경 내용을 저장합니다.

WebAPI 백 엔드를 사용하여 알림 등록

이 섹션에서는 알림 허브에 클라이언트 라이브러리를 사용하여 알림을 위한 사용자 및 디바이스 등록 요청을 처리하는 새 컨트롤러를 WebAPI 백 엔드에 추가합니다. 이 컨트롤러는 AuthenticationTestHandler에 의해 인증되고 HttpContext에 연결된 사용자에 대한 사용자 태그를 추가합니다. 태그의 문자열 형식은 "username:<actual username>"입니다.

  1. 솔루션 탐색기에서 AppBackend 프로젝트를 마우스 오른쪽 단추로 클릭한 다음 NuGet 패키지 관리를 선택합니다.

  2. 왼쪽 창에서 온라인을 선택한 다음 검색 상자에 Microsoft.Azure.NotificationHubs를 입력합니다.

  3. 결과 목록에서 Microsoft Azure Notification Hubs를 선택한 다음 설치를 선택합니다. 설치를 완료한 다음, NuGet 패키지 관리자 창을 닫습니다.

    이 작업은 Microsoft.Azure.Notification Hubs NuGet 패키지를 사용하는 Azure Notification Hubs SDK에 대한 참조를 추가합니다.

  4. 알림을 보내는 데 사용되는 알림 허브와의 연결을 나타내는 새 클래스 파일을 만듭니다. 솔루션 탐색기에서 Models 폴더를 마우스 오른쪽 단추로 클릭한 후 추가, 클래스를 차례로 선택합니다. 새 클래스 이름을 Notifications.cs로 지정한 후 추가를 선택하여 클래스를 생성합니다.

    The Add New Item window

  5. Notifications.cs에서 파일의 맨 위에 using 문을 추가합니다.

    using Microsoft.Azure.NotificationHubs;
    
  6. Notifications 클래스 정의를 다음 코드로 바꾸고 두 개의 자리 표시자를 알림 허브에 대한 연결 문자열(모든 권한 사용) 및 허브 이름(Azure Portal에서 제공)으로 바꿉니다.

    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>");
        }
    }
    

    Important

    허브의 이름DefaultFullSharedAccessSignature를 입력하고 계속 진행합니다.

  7. 다음으로 RegisterController라는 새 컨트롤러를 만듭니다. 솔루션 탐색기에서 Controllers 폴더를 마우스 오른쪽 단추로 클릭한 후 추가, 컨트롤러를 차례로 선택합니다.

  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에 연결된 사용자에 대한 사용자 태그를 추가합니다. 사용자는 추가한 메시지 필터 AuthenticationTestHandler에 의해 인증되고 HttpContext에 연결되었습니다. 또한 선택적인 검사를 추가하여 사용자에게 요청된 태그에 등록할 수 있는 권한이 있는지 확인합니다.

    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. 변경 내용을 저장합니다.

WebAPI 백 엔드에서 알림 보내기

이 섹션에서는 클라이언트 디바이스에서 알림을 보내기 위한 방법을 노출하는 새 컨트롤러를 추가합니다. 알림은 ASP.NET WebAPI 백 엔드에서 Azure Notification Hubs .NET 라이브러리를 사용하는 사용자 이름 태그를 기반으로 합니다.

  1. 이전 섹션에서 RegisterController를 만들었던 동일한 방식으로 NotificationsController라는 다른 새 컨트롤러를 만듭니다.

  2. NotificationsController.cs에 다음 using 문을 추가합니다.

    using AppBackend.Models;
    using System.Threading.Tasks;
    using System.Web;
    
  3. NotificationsController 클래스에 다음 메서드를 추가합니다.

    이 코드는 PNS(Platform Notification Service) pns 매개 변수를 기반으로 알림 유형을 보냅니다. to_tag 값은 메시지에서 사용자 이름 태그를 지정하는 데 사용됩니다. 이 태그는 활성 알림 허브 등록의 사용자 이름 태그와 일치해야 합니다. 알림 메시지는 POST 요청의 본문에서 가져오고 대상 PNS에 맞게 형식이 지정됩니다.

    알림을 수신하기 위해 지원되는 디바이스가 사용하는 PNS에 따라 다양한 형식으로 알림을 지원합니다. 예를 들어 Windows 디바이스에서 다른 PNS에서 직접 지원되지 않는 WNS로 알림을 사용할 수 있습니다. 이러한 상황에서 백 엔드는 알림을 지원하려는 디바이스의 PNS에 지원되는 알림으로 포맷해야 합니다. 그런 다음 NotificationHubClient 클래스에서 적절한 보내기 API를 사용합니다.

    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 홈페이지에 표시됩니다.

새 WebAPI 백 엔드 게시

다음으로 모든 디바이스에서 액세스할 수 있도록 앱을 Azure 웹 사이트에 배포합니다.

  1. AppBackend 프로젝트를 마우스 오른쪽 단추로 클릭한 다음 게시를 선택합니다.

  2. Microsoft Azure App Service를 게시 대상으로 선택한 다음, \*\*[게시]를 선택합니다. App Service 만들기 창이 열립니다. 여기에서 Azure에서 ASP.NET 웹앱을 실행하는 데 필요한 모든 Azure 리소스를 만들 수 있습니다.

    The Microsoft Azure App Service tile

  3. App Service 만들기 창에서 Azure 계정을 선택합니다. 유형 변경>웹앱을 선택합니다. 기본 웹앱 이름을 유지한 다음 구독, 리소스 그룹, App Service 계획을 차례로 선택합니다.

  4. 만들기를 실행합니다.

  5. 요약 섹션의 사이트 URL 속성을 메모해 둡니다. 이 URL은 자습서의 뒷부분에서 백 엔드 엔드포인트입니다.

  6. 게시를 선택합니다.

마법사를 완료한 후 Azure에 ASP.NET 웹앱을 게시한 다음 기본 브라우저에서 앱을 엽니다. 애플리케이션을 Azure App Services에서 볼 수 있습니다.

URL은 http://<app_name>.azurewebsites.net 형식으로 이전에 지정한 웹앱 이름을 사용합니다.

Android 프로젝트 만들기

다음 단계는 자습서: Azure Notification Hubs 및 Firebase Cloud Messaging을 사용하여 Android 디바이스에 알림 푸시에서 만든 Android 애플리케이션을 업데이트하는 것입니다.

  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. MainActivity 클래스와 동일한 패키지에서 RegisterClient라는 새 클래스를 만듭니다. 새 클래스 파일에 아래 코드를 사용합니다.

    
    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 호출을 구현합니다. 또한 앱 백 엔드에서 등록 에 설명된 대로 알림 허브에서 생성된 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. 그런 다음, 로그인 단추 클릭 이벤트를 처리하고 푸시 알림을 보내는 다음 메서드를 추가합니다.

    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 클래스에서 사용자가 선택한 플랫폼 알림 서비스를 사용하여 sendPush 메서드를 호출하도록 sendNotificationButtonOnClick 메서드를 업데이트합니다.

    /**
    * 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 파일에서 다음 줄을 buildTypes 섹션 다음의 android 섹션에 추가합니다.

    useLibrary 'org.apache.http.legacy'
    
  12. 앱이 API 수준 28(Android 9.0) 이상을 대상으로 하는 경우 AndroidManifest.xml<application> 요소 내에 다음 선언을 포함합니다.

    <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. 사용자가 푸시 알림 메시지로 받을 메시지를 입력합니다.

  7. Send Notification을 클릭합니다. 일치하는 사용자 이름 태그로 등록된 각 디바이스에 푸시 알림이 수신됩니다.

다음 단계

이 자습서에서는 등록에 태그가 연결된 특정 사용자에게 알림을 푸시하는 방법을 배웠습니다. 위치 기반 알림을 푸시하는 방법을 알아보려면 다음 자습서를 계속 진행합니다.