푸시 알림

푸시 알림은 앱이 꺼져 있는 사용자들에게도 새로운 메시지나 이벤트를 알려줄 수 있는 좋은 방법입니다. Android에서는 푸시 알림을 받을 때 앱 아이콘과 메시지가 상태줄에 나타나고, 사용자가 알림을 터치하면 앱으로 이동합니다. 카카오 Android SDK를 이용하면 카카오계정으로 로그인한 경우에 한해 푸시토큰의 등록과 삭제를 쉽게 할 수 있습니다. 카카오계정 로그인을 사용하지 않는다면 REST API를 통해 개발자가 직접 푸시토큰의 등록과 삭제를 구현해야 합니다. 여기에서는 카카오계정 로그인을 사용하는 앱의 푸시 알림 사용법을 설명합니다.

사용자들에게 푸시 메시지를 보내는 것은 REST API를 통해서만 가능하고 테스트를 위해 로그인한 자신에게 푸시메시지를 보내는 것은 SDK를 이용해서 가능합니다.

  • 시작하기 전에
    카카오 푸시 알림을 사용하기 위해 FCM 서비스 등록, 카카오 푸시 서비스 등록과 앱 설정을 설명합니다.
  • 푸시토큰 등록
    카카오 SDK를 이용하여 푸시토큰을 등록하는 방법을 설명합니다.
  • 푸시토큰 삭제
    카카오 SDK를 이용하여 푸시토큰을 삭제하는 방법을 설명합니다.
  • 푸시토큰 조회
    카카오 플랫폼 서비스에 올바로 푸시 토큰을 등록하였다면, 해당 토큰들을 조회할 수 있습니다.
  • 푸시 알림 받기
    받은 푸시 메시지를 처리하는 service 구현하는 방법을 설명합니다.

시작하기 전에

  1. 로그인 API를 사용할 수 있는 앱 설정이 되어있는 지 Kakao 계정 로그인이 필요한 앱설정을 참고하여 확인합니다.
  2. 앱 사인(sign)과정에 사용한 키를 설정 > 일반 > 플랫폼 > Android > 키 해시 에 등록한 상태인지 확인합니다.

    키 해시를 생성하는 방법은 앱 생성의 키 해시등록 과정을 참고 합니다.

3. 구글 개발자 센터에 FCM 서비스 사용 등록이 필요합니다.

이미 애플리케이션에서 푸시를 사용하고 있고, 기존 푸시 서비스를 카카오 플랫폼 서비스로 이전하기를 원한다면 이부분은 건너뜁니다.

4. 카카오 개발자 센터에 카카오 푸시 서비스 설정이 필요합니다. 설정 > 푸시 에서 푸시 사용을 켜고, GCM Key에 위 3번에서 Settings > Cloud Messaging을 통해 얻은 Server key*를 입력합니다. dev_015.png

5. 푸시 API를 사용하기 위해 추가로 설정이 필요합니다. 다운로드 받은 google-service.json을 app의 root directory에 (build.gradle, gradle.properties) 에 위치하도록 합니다. 위 파일은 다음과 같은 형태를 하고 있습니다.

{
  "project_info": {
    "project_number": "${project_number}",
    "firebase_url": "https://${project_id}.firebaseio.com",
    "project_id": "${project_id}",
    "storage_bucket": "${project_id}.appspot.com"
  },
  "client": [
    {
      "client_info": {
        "mobilesdk_app_id": "1:${sender_id}:android:${some number}",
        "android_client_info": {
          "package_name": "com.kakao.sdk.sample"
        }
      },
      "oauth_client": [
        {
          "client_id": "${sender_id}-${some_number}.apps.googleusercontent.com",
          "client_type": 3
        }
      ],
      "api_key": [
        {
          "current_key": "${api_key}"
        }
      ],
      "services": {
        "analytics_service": {
          "status": 1
        },
        "appinvite_service": {
          "status": 1,
          "other_platform_oauth_client": []
        },
        "ads_service": {
          "status": 2
        }
      }
    }
  ],
  "configuration_version": "1"
}

자세한 설정들은 Kakao-계정-로그인이-필요한-앱설정-AndroidManifest.xml을 참고하세요.

푸시토큰 등록

해당 기기에 설치된 애플리케이션으로 푸시 알림을 받기 위해서는 push token을 발급 받아야 합니다. 카카오계정 로그인을 사용하는 앱에 대해서는 카카오 Android SDK가 이과정을 대신해 줄 수 있습니다. SDK에서 제공하는 FirebaseInstanceIdService를 다음과 같이 AndroidManifest.xml에 정의해 주세요. KakaoFirebaseInstanceIdService는 FCM 토큰이 갱신될 때마다 만약 유저가 로그인되어 있다면 토큰을 등록하고, 로그인되어 있지 않다면 나중에 유저 로그인 후 등록을 위해 SharedPreferences에 임시 저장해둡니다.

[AndroidManifest.xml]

<manifest … package="com.kakao.sample.push">    <!-- a. package 이름 -->
   <uses-permission android:name="android.permission.INTERNET"/>

   <application>
       …
       <service android:name="com.kakao.push.KakaoFirebaseInstanceIdService">
           <intent-filter>
               <action android:name="com.google.firebase.INSTANCE_ID_EVENT" />
           </intent-filter>
       </service>

        <meta-data android:name="com.kakao.sdk.AppKey"
              android:value="@string/kakao_app_key"/>
    ...
    </application>
</manifest>
  • 또한 한 사용자가 여러기기를 사용하는 경우를 위해, 푸시 토큰 등록시 사용자 고유 ID외에 기기 고유 ID가 필요합니다. 기기 고유 ID를 생성하는 적절한 알고리즘을 KakaoAdapter를 구현할 때 abstract method인 getDeviceUUID()를 구현하면 이를 이용하여 푸시토큰이 기기별로 등록됩니다. 최대 64자 지원되므로 생성시 주의하도록 합니다.

[KakaoSDKAdapter]

public class KakaoSDKAdapter extends KakaoAdapter {
  ...
  @Override
  public IPushConfig getPushConfig() {
    return new IPushConfig() {
      /**
       * [주의!] 아래 예제는 샘플앱에서 사용되는 것으로 기기정보 일부가 포함될 수 있습니다. 실제 릴리즈 되는 앱에서 사용하기 위해서는 사용자로부터 개인정보 취급에 대한 동의를 받으셔야 합니다.
       *
       * 한 사용자에게 여러 기기를 허용하기 위해 기기별 id가 필요하다.
       * ANDROID_ID가 기기마다 다른 값을 준다고 보장할 수 없어, 보완된 로직이 포함되어 있다.
       * @return 기기의 unique id
       */
      @Override
      public String getDeviceUUID() {
          String deviceUUID;
          final SharedPreferencesCache cache = Session.getAppCache();
          final String id = cache.getString(PROPERTY_DEVICE_ID);

          if (id != null) {
              deviceUUID = id;
              return deviceUUID;
          } else {
              UUID uuid = null;
              Context context = getApplicationConfig().getApplicationContext();
              final String androidId = Settings.Secure.getString(context.getContentResolver(), Settings.Secure.ANDROID_ID);
              try {
                  if (!"9774d56d682e549c".equals(androidId)) {
                      uuid = UUID.nameUUIDFromBytes(androidId.getBytes("utf8"));
                  } else {
                      final String deviceId = ((TelephonyManager) context.getSystemService(context.TELEPHONY_SERVICE)).getDeviceId();
                      uuid = deviceId != null ? UUID.nameUUIDFromBytes(deviceId.getBytes("utf8")) : UUID.randomUUID();
                  }
              } catch (UnsupportedEncodingException e) {
                  throw new RuntimeException(e);
              }

              Bundle bundle = new Bundle();
              bundle.putString(PROPERTY_DEVICE_ID, uuid.toString());
              cache.save(bundle);

              deviceUUID = uuid.toString();
              return deviceUUID;
          }
      }

      @Override
      public ApiResponseCallback<Integer> getTokenRegisterCallback() {
        return new ApiResponseCallback<Integer>() {
          @Override
          public void onFailure(ErrorResult errorResult) {
            // FCM 토큰 등록 실패 처리.
          }

          @Override
          public void onSessionClosed(ErrorResult errorResult) {
            // 현재 로그인이 되어 있지 않은 상태.
          }

          @Override
          public void onNotSignedUp() {
            // 앱에 카카오톡 계정으로 가입이 되어있지 않은 상태.
          }

          @Override
          public void onSuccess(Integer result) {
            // 성공적으로 토큰이 등록된 상태
          }
        };
      }
    };
  }
}

만약 토큰 발급 후 처리를 직접 하고 싶다면

  1. 직접 FirebaseInstanceIdService를 상속하고 onTokenRefresh()를 구현합니다.
  2. 위 onTokenRefresh()에서 발급받은 FCM 토큰을 REST API를 통해 카카오 오픈 플랫폼 푸시 서버로 넘겨 줍니다.

    카카오계정 로그인을 사용하지 않아 REST API를 이용하여 직접 구현하는 경우는 토큰이 아닌 어드민키를 사용해야 하므로 서버에서 REST API를 호출해야 합니다.

푸시토큰 삭제

더이상 푸시토큰을 사용하고 싶지 않는 경우, 예를 들어 로그아웃이나 탈퇴시에 푸시토큰 삭제가 필요합니다. PushService#deregisterPushToken API를 호출합니다.

private void deregisterPushToken() {
    PushService.deregisterPushToken(new KakaoPushResponseCallback<Boolean>() {
        @Override
        public void onSuccess(Boolean result) {
            Logger.d("succeeded to deregister push token");
        }
    }, deviceUUID);
}

파라미터로 API 요청 결과에 따른 콜백 ApiResponseCallback을 넘깁니다.

푸시토큰 조회

카카오 플랫폼 서비스에 푸시 토큰이 올바로 등록되었는지, 해당 사용자와 관련된 다른 푸시 토큰들은 무엇인지 등의 정보를 확인할 수 있습니다. 해당 기능을 사용하기 위해서는 성공적인 로그인 후에 얻을 수 있는 사용자 토큰이 필요합니다. PushService#getPushTokens API를 호출합니다.

예제는 다음과 같습니다.

private void getPushTokens() {
     PushService.getPushTokens(new KakaoPushResponseCallback<List<PushTokenInfo>>() {
        @Override
        public void onSuccess(List<PushTokenInfo> result) {
            String message = "succeeded to get push tokens."
                + "\ncount="
                + result.size()
                + "\nstories="
                + Arrays.toString(result.toArray(new PushTokenInfo[result.size()]));

            new DialogBuilder(PushMainActivity.this)
                .setMessage(message)
                .setPositiveButton(R.string.ok, new OnClickListener() {
                    @Override
                    public void onClick(DialogInterface dialog, int which) {
                        dialog.dismiss();
                    }
                }).create().show();
        }
    });
}

성공 결과는 PushTokenInfo를 복수개 포함한 List 형태로 제공됩니다.

PushTokenInfo

Push Token의 정보를 기록한 class

Field Value Type Description
userId String 사용자의 고유 ID
deviceId String 기기의 고유한 ID
pushType String apns 혹은 gcm
pushToken String APNS, GCM으로부터 발급받은 Push Token
createdAt String 푸시 토큰을 처음 등록한 시각
updatedAt String 푸시 토큰을 업데이트한 시각

푸시 알림 받기

메시지를 받기 위해서는 앱 설정 파일에 설정한 FirebaseMessagingService를 구현해야합니다.

2.FirebaseMessagingService 구현 아래는 메시지를 받아 BigTextStyle로 알림센터에 보여주고 이를 클릭시 다시 앱으로 전환해 줍니다.

public class SampleFirebaseMessagingService extends FirebaseMessagingService {
    public static final int NOTIFICATION_ID = 1;
    public static int count = 0;
    @Override
    public void onMessageReceived(RemoteMessage remoteMessage) {
        super.onMessageReceived(remoteMessage);
        sendDataMessage(remoteMessage);
    }

    private void sendDataMessage(final RemoteMessage message) {
        NotificationManager mNotificationManager = (NotificationManager) this.getSystemService(Context.NOTIFICATION_SERVICE);

        Intent notifyIntent = new Intent(this, SampleLoginActivity.class);
        notifyIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
        PendingIntent contentIntent = PendingIntent.getActivity(this, 0, notifyIntent, PendingIntent.FLAG_UPDATE_CURRENT);

        NotificationCompat.Builder mBuilder = new NotificationCompat.Builder(this)
                .setSmallIcon(R.drawable.push_noti_icon)
                .setContentTitle(getApplicationContext().getString(R.string.push_notification_title))
                .setStyle(new NotificationCompat.BigTextStyle().bigText(message.getData().get("content")))
                .setContentText(message.getData().get("content"));

        mBuilder.setContentIntent(contentIntent);
        mNotificationManager.notify(NOTIFICATION_ID + count++, mBuilder.build());
    }
}
<manifest … package="com.kakao.sample.push">    <!-- a. package 이름 -->
    <uses-permission android:name="android.permission.INTERNET"/>

    <application>
        …
        <service android:name="com.kakao.push.KakaoFirebaseInstanceIdService">
            <intent-filter>
                <action android:name="com.google.firebase.INSTANCE_ID_EVENT" />
            </intent-filter>
        </service>
        <service android:name="push.SampleFirebaseMessagingService">
            <intent-filter>
                <action android:name="com.google.firebase.MESSAGING_EVENT" />
            </intent-filter>
        </service>

         <meta-data android:name="com.kakao.sdk.AppKey"
               android:value="@string/kakao_app_key"/>
     ...
     </application>
 </manifest>

Last Modified : 2017-07-28