PUSH API · v1a
Android SDK
기준 확인: 2026-09-11 · 외부 콘솔의 메뉴 이름은 변경될 수 있어요. 아래 공식 가이드 링크와 실제 SDK 버전도 함께 확인해 주세요.
시작 전에: 세 가지 연결을 구분해요
SDK 설치, 푸시 서비스 연결, 기기 등록은 서로 다른 단계예요. 라이브러리 빌드만 성공했다고 알림이 전달되지는 않아요. 아래 순서대로 본인 기기 하나에서 확인해 주세요.
- 대시보드에서 사용할 조직·프로젝트·앱 환경을 선택해요. Android와 iOS는 각각의 앱과 푸시 서비스 설정을 사용해요.
- 설정 → 푸시 서비스 연결에서 FCM 또는 APNs를 등록하고 검증 상태를 확인해요.
/app-environments/{appEnvironmentId}/providers - 설정 → SDK 연결에서 같은 앱 환경의 공개 키를 준비해요. 기존 앱은 이전 storageNamespace를 유지해요. 고객 SDK 주소는 고정이며 서버 API 키는 모바일 앱에 넣지 않아요.
/app-environments/{appEnvironmentId}/settings/sdk
0. Firebase와 FCM 연결 준비하기
- Firebase Console에서 프로젝트를 선택하고 Android 앱을 등록해요. 실제 빌드 variant의 applicationId와 패키지 이름을 맞춰 주세요.
- 앱 설정 파일 google-services.json을 다운로드해 앱 모듈에 넣어요. Firebase 연결이 이미 있다면 먼저 프로젝트와 패키지 일치를 확인하고 기존 설정을 보존해요.
- Project settings → Service accounts → Firebase Admin SDK에서 승인된 서비스 계정 JSON을 준비해요. 새 키 생성은 비밀 자격증명을 만드는 작업이므로 조직의 권한·보관 정책을 따라요.
- Kits의 푸시 서비스 연결에 서비스 계정 JSON을 선택해요. project_id와 client_email은 파일에서 읽어요. 발신자 ID를 직접 추측할 필요는 없어요. 다른 Firebase 프로젝트의 자격증명은 현재 지원하지 않아요.
// Project build.gradle.kts: merge into the existing plugins block.
plugins {
id("com.google.gms.google-services") version "4.5.0" apply false
}
// app/build.gradle.kts: merge, do not replace the existing Android setup.
plugins {
id("com.google.gms.google-services")
}
dependencies {
// Reference version compiled by the Kits sample; not a latest-version claim.
implementation("com.google.firebase:firebase-messaging:25.1.1")
}위 버전은 Kits 샘플에서 컴파일하는 기준 버전이에요. 기존 Firebase BoM/version catalog가 있다면 그 정책과 호환성을 확인하고 중복 선언하지 마세요. Kits AAR 자체가 Firebase를 설치해 주지는 않아요.
새 관리형 SDK 간편 연동
import com.marcocorp.kits.push.fcm.Kits
// Once in Application.onCreate, with the push-fcm module:
Kits.initialize(this, publicKey)운영 주소와 기본 저장소는 SDK가 제공하고 인증 모드는 대시보드에서 읽어요. 새 SDK의 사용자 관측·이벤트·활동 집계는 기본으로 켜지며 저장된 수집 거부를 유지해요. 동의 전 수집은 collectionEnabled=false로 보류할 수 있어요. 기존 앱은 이전 namespace를 유지해야 하며 키 회전 때 바꾸지 마세요. iOS APNs 기본값은 production이며 실제 개발용 서명은 sandbox를 지정해요.
push-fcm이 Firebase Messaging 서비스와 토큰 갱신을 처리해요. 앱에는 google-services.json과 Google services 플러그인이 필요해요. 권한·클릭은 Kits API를 사용하고, 기존 Activity의 onNewIntent는 Kits.handleNotificationIntent로 전달해 주세요.
| 공통 선택 옵션 | 의미 · 기본값 |
|---|---|
collectionEnabled | 통합 수집 허용 · true. false로 시작하면 수집 보류. 저장된 거부는 true 기본값으로 해제되지 않아요 |
foregroundPresentationPolicy | 포그라운드 표시 정책 · PRESENT / .present |
shouldDisplay | 선택 표시 콜백 · 생략 시 허용, 오류 시 표시 안 함 |
identityAuthProvider | VERIFIED 계정 증명 공급자 · 생략 |
audienceCollectionEnabled | 기본 사용자 정보·세션 수집 · true. 명시적 false는 유지 |
notificationEventsEnabled | 실제 수신·클릭 보고 · true. 메시지 track_events도 필요 |
observationOptions | persistNotificationEvents · true / activityDistributionEnabled · true |
동의 전 수집을 보류하려면 초기화 옵션에 collectionEnabled=false를 지정해요. 동의 후 Kits.setCollectionEnabled(true)로 시작하고 false로 중단해요. 알림 수신 거부나 서버에 이미 저장한 기록의 삭제와는 별개예요. 각 기능 옵션을 명시적으로 false로 둔 경우 통합 허용으로 다시 켜지지 않아요. iOS 확장의 수신 보고는 별도 설정이에요.
Android 전용 설정은 android: KitsAndroidOptions(FCM 모드·알림 리소스), iOS 전용 설정은 ios: KitsIOSOptions(APNs 환경·delegate 연동)로 묶어요. shouldDisplay는 포그라운드 표시와 새 Android SDK 직접 표시를 제어해요. iOS와 구형 Android에서 OS가 직접 표시하는 백그라운드 알림은 차단하지 못해요. 초기화만으로 권한을 요청하지 않아요.
아래 core 전용 설정·OS 콜백 예제는 수동 저수준 연동을 유지할 때만 사용하세요. 위 관리형 초기화에서는 SDK가 그 연결을 맡아요.
지원 기준: Android API 24 이상. SDK 코어는 Kotlin 표준 라이브러리 외 Firebase·UI 프레임워크를 포함하지 않습니다. 앱이 Firebase 설정과 콜백을 연결해야 합니다.
1. 승인된 SDK 받기
관리형 연동은 com.marcocorp.kits:push-fcm과 해당 버전의 core가 필요해요. 아래 API를 지원하는 검증된 릴리스 버전을 approvedKitsVersion에 지정하세요. Maven Central 공개 배포를 전제하지 않아요. 검토된 소스에서만 아래 작업으로 로컬 Maven에 설치할 수 있어요.
./gradlew :push:publishToMavenLocal :push-fcm:publishToMavenLocal소비하는 앱의 기존 저장소 설정에 로컬 저장소를 추가하고 라이브러리를 연결합니다. 승인된 내부 배포 저장소를 쓰는 경우 조직의 저장소 설정을 따르세요.
// settings.gradle.kts — use only an approved local artifact
dependencyResolutionManagement {
repositories {
mavenLocal()
google()
mavenCentral()
}
}
// app/build.gradle.kts
dependencies {
// Set approvedKitsVersion to the reviewed managed SDK release.
implementation("com.marcocorp.kits:push-fcm:$approvedKitsVersion")
}2. 앱 시작 시 구성하기
import android.app.Application
import com.marcocorp.kits.push.KitsPush
import com.marcocorp.kits.push.KitsPushClient
import com.marcocorp.kits.push.KitsPushConfiguration
import com.marcocorp.kits.push.KitsFcmMode
// Legacy core integration only: preserve the exact namespace already used by this app.
// New integrations use Kits.initialize(this, publicKey); the SDK manages storage.
// Merge into your existing Application; define BuildConfig fields in the host.
class MyApp : Application() {
lateinit var kitsPush: KitsPushClient
private set
override fun onCreate() {
super.onCreate()
kitsPush = KitsPush.configure(
context = this,
configuration = KitsPushConfiguration(
publicKey = BuildConfig.KITS_PUBLIC_KEY,
storageNamespace = BuildConfig.KITS_STORAGE_NAMESPACE,
fcmMode = KitsFcmMode.FID,
),
)
}
}위 BuildConfig 필드는 호스트 앱에서 정의해요. 아래 core 예제는 직접 OS 연동을 유지하는 경우에만 사용하며, 관리형 Kits.initialize와 동시에 초기화하지 마세요. 같은 프로세스·저장소에는 일관된 설정을 사용하세요.
3. FCM 모드와 콜백 연결하기
예제는 FID 모드입니다. 호스트 앱에서 지원되는 Firebase Messaging을 설정하고 다음 manifest 항목을 사용합니다.
<meta-data
android:name="firebase_messaging_installation_id_enabled"
android:value="true" />앱이 FirebaseMessagingService와 FirebaseMessaging.register() 호출을 소유합니다. 서비스는 exported=false로 등록하세요. 아래 kitsPush는 Application에서 생성한 같은 인스턴스이고, 상태 관찰에는 KitsSyncState import가 필요합니다.
// FirebaseMessagingService callbacks in FID mode
override fun onRegistered(fid: String) {
kitsPush.registerFirebaseInstallationIdAsync(fid)
}
override fun onUnregistered(fid: String) {
kitsPush.unregisterFirebaseInstallationIdAsync(fid)
}
// Observe server acknowledgement; close this listener when its owner is finished.
val subscription = kitsPush.addSyncStateListener { state ->
if (state is KitsSyncState.Synced) {
// Latest desired device state acknowledged by Kits.
}
}연결 예제: Manifest와 실제 FCM 콜백
아래는 FID 모드의 부분 예제예요. .MyApp과 .AppMessagingService는 직접 추가한 클래스 이름과 맞춰요. 이미 Application이나 FirebaseMessagingService가 있다면 두 번째로 만들지 말고 기존 처리기에 연결해 주세요.
<!-- Merge into app/src/main/AndroidManifest.xml. -->
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
<uses-permission android:name="android.permission.POST_NOTIFICATIONS" />
<application android:name=".MyApp">
<meta-data
android:name="firebase_messaging_installation_id_enabled"
android:value="true" />
<service android:name=".AppMessagingService" android:exported="false">
<intent-filter>
<action android:name="com.google.firebase.MESSAGING_EVENT" />
</intent-filter>
</service>
</application>
</manifest>import com.google.firebase.messaging.FirebaseMessaging
import com.google.firebase.messaging.FirebaseMessagingService
import com.google.firebase.messaging.RemoteMessage
// MyApp is your existing Application subclass and owns kitsPush.
class AppMessagingService : FirebaseMessagingService() {
private val kitsPush get() = (application as MyApp).kitsPush
override fun onRegistered(installationId: String) {
kitsPush.registerFirebaseInstallationIdAsync(installationId)
}
override fun onUnregistered(installationId: String) {
kitsPush.unregisterFirebaseInstallationIdAsync(installationId)
}
override fun onMessageReceived(message: RemoteMessage) {
val payload = kitsPush.handleForegroundMessage(message.data)
// Route a recognized payload through your existing foreground UI.
// If null, continue the host's other-provider handling.
}
}
// After Firebase initialization and your host's consent decision:
// manual FID refresh; the callbacks above receive the identifier.
FirebaseMessaging.getInstance().register().addOnFailureListener {
// Show a safe retry state. Never log credentials or push addresses.
}권한 요청과 표시 확인
- Android 13 이상에서는 사용자 동작에 맞춰 POST_NOTIFICATIONS 런타임 권한을 요청해요. 허용 전후와 설정 앱에서 돌아올 때 실제 권한을 다시 읽어 reportPermissionAsync에 전달해요.
- 아직 요청하지 않은 상태와 거부된 상태를 구분해요. Android에는 iOS와 같은 전체 상태 enum이 없으므로 호스트가 권한 요청 이력을 보관하고 알림 허용 상태와 함께 판단해야 해요.
- Android 8 이상에서는 알림 채널도 확인해요. OS가 허용했어도 해당 채널이 꺼져 있으면 표시되지 않을 수 있어요. 기본 small icon, 채널, 클릭 처리와 foreground 표시 정책은 호스트가 관리해요.
5. 대시보드와 기기에서 끝까지 확인하기
- SDK 동기화 완료를 확인한 뒤 같은 앱 환경의 수신 대상 → 푸시 구독에서 앱 설치를 찾아요. 앱 설치 ID·푸시 구독 ID, 플랫폼, 권한, 수신 설정을 확인하고 토큰 원문은 공유하지 않아요.
- 본인 앱 설치만 테스트 기기로 지정하고 직접 대상 발송에서 선택해요. 테스트 발송으로 기록을 켜도 실제 알림이 가며 권한·동의·푸시 서비스 검증을 우회하지 않아요. 처음에는 이미지 없는 SERVICE 텍스트 한 건으로 확인해요.
- 발송 관리 → 발송한 메시지에서 요청과 개별 발송 결과를 확인해요. 대시보드/API 출처와 일반/테스트 목적은 별개예요. 접수 또는 푸시 서비스의 수락은 단말 표시·클릭 성공이 아니에요.
- 앱 foreground, background, 알림 탭 후 cold start, 거부 후 재허용을 각각 검사해요. 이미지·태그·다국어·수신 관측은 기본 발송이 확인된 뒤 하나씩 추가해요.
막히는 단계부터 확인해요
| 증상 | 먼저 확인할 내용 |
|---|---|
| 앱 설치가 안 보여요 | 앱 환경과 공개 키, API origin/IP 접근, SDK 상태 오류를 확인해요. configure/Queued는 서버 성공이 아니에요. DB나 앱 데이터를 지워서 해결하려 하지 마세요. |
| FCM_MODE_MISMATCH / Firebase callback | manifest flag와 KitsFcmMode를 맞추고 현재 variant의 google-services.json·Google Play services·서비스 등록을 확인해요. FID와 기존 토큰을 교환해 넣지 않아요. |
| 접수됐지만 알림이 없어요 | 개별 발송 결과 → 푸시 서비스 오류 → OS 권한과 앱 수신 설정 → 채널/집중 모드 → foreground 표시 처리 순서로 확인해요. 조회 결과 없이 반복 발송하지 마세요. |
| 알림이 두 번 와요 | 기존 OneSignal과 Kits 양쪽에서 발송했는지, OS 표시 알림을 앱이 다시 표시했는지 확인해요. 서버 이벤트마다 기존 플랫폼과 Kits 중 어느 쪽이 담당하는지 명시해요. |
4. 권한·수신 설정·알림 처리
- 현재 OS 상태를 읽고 reportPermissionAsync에 전달합니다. 알림 허용을 받았다고 가정하거나 SDK 호출로 OS 권한이 바뀐다고 생각하지 마세요.
- 사용자의 앱 수신 선택을 setPushPreferenceAsync로 전달합니다. 마케팅 동의와는 별도입니다.
- 실제 포그라운드 수신은 kitsPush.handleForegroundMessage(message.data), 알림 클릭은 Activity의 onCreate와 onNewIntent에서 handleNotificationIntent로 전달합니다. onNewIntent에서는 setIntent도 갱신합니다.
- 알 수 없는 payload는 기존 앱 처리기로 넘깁니다. 화면 이동과 링크 열기는 앱이 결정하며 SDK가 자동 실행하지 않습니다.
- Queued는 로컬 대기입니다. 서버 반영은 KitsSyncState.Synced로 확인하세요. 수동 동기화 요청은 synchronizeAsync를 사용합니다.
선택: 실제 수신·클릭 관측
수집 기본값 개편이 포함된 SDK 소스에서는 notificationEventsEnabled와 기록 재시도(persistNotificationEvents)가 기본 true예요. 기존 게시 버전은 다를 수 있으므로 사용하는 릴리스를 확인하세요. 메시지의 track_events=true는 별도로 필요하며, 생략한 서버 API 요청이나 저장된 초안의 false를 자동으로 바꾸지 않아요. 저장된 수집 거부와 collectionEnabled=false가 우선해요. 앱의 동의·개인정보 고지도 검토하세요.
// Optional argument in KitsPushConfiguration, after host consent:
notificationEventsEnabled = true
// Forward actual callbacks:
kitsPush.handleForegroundMessage(message.data)
kitsPush.handleNotificationIntent(intent)메시지의 추가 필드는 {"track_events": true}입니다. 서버가 넣은 kits_delivery_id와 정확한 버전 marker가 있어야 보고합니다. 이 예약 ID를 직접 만들지 마세요. CLICKED가 RECEIVED를 만들지 않으며 푸시 서비스의 수락이나 표시를 수신으로 추정하지 않습니다.
직접 연결하는 호스트는 reportNotificationReceivedAsync(data, callback) 또는 reportNotificationClickedAsync(data, callback)를 사용할 수 있습니다. 결과 콜백은 main looper로 옵니다. FCM background notification+data의 OS_DISPLAY는 수신 콜백 없이 OS가 표시하므로 클릭 extras만으로 수신을 만들지 마세요.
인증이 거부되는 401 같은 영구 실패는 무제한 재시도하지 않아요. 수집 기록은 알림 표시나 분석 완료 여부를 바꾸지 않아요.
메시지 상세의 SDK 수신·클릭 기록은 자동으로 불러오고, 화면을 보는 동안 최대 5분간 5초마다 갱신해요. 차트는 서버에 기록이 도착한 시각 기준이며 앱의 실제 클릭 시각과 다를 수 있어요. 새로고침하거나 나중에 다시 열어도 보관된 시간별 기록을 볼 수 있어요.
백그라운드 수신 기록
ANDROID_APP_DISPLAY_V1을 지원하는 서버와 push-fcm SDK를 함께 사용하면 이미지 없는 알림은 data-only로 보내고 SDK가 수신 후 표시해요. 이미지 알림은 기존 이미지 표시를 유지하기 위해 OS 표시 방식을 사용하며, 백그라운드 수신 기록이 빠질 수 있어요. 호스트 초기화는 Kits.initialize(this, publicKey) 그대로예요. 이전 SDK와 core 전용 수동 연동은 기존 OS 표시를 유지해요. 먼저 서버를 배포한 뒤 새 SDK를 포함한 앱을 배포하세요.
선택: 호스트 foreground 알림의 버튼
호스트가 실제 foreground 수신 후 직접 표시하기로 선택한 알림에만 사용합니다. category는 로컬에서 선택하며 새 Android wire 필드가 아닙니다. helper는 알림을 게시하거나 PendingIntent를 실행하지 않습니다.
val actions = KitsNotificationActions(listOf(KitsNotificationCategory("ORDER_V1",
listOf(KitsNotificationAction("VIEW_ORDER", "View order", R.drawable.ic_order)))))
actions.addToForegroundNotification(builder, message.data, "ORDER_V1", isAppForeground) { action ->
// Host-owned: unique per delivery/action, immutable explicit Activity PendingIntent.
hostPendingIntentFor(action.identifier, message.data)
}
// Host owns permission, channel, contentIntent, deduplication and notify().기존 actions가 없는 새 builder에 최대 3개의 로컬 버튼을 추가합니다. 다른 payload·미등록 category·false foreground에서는 실행하지 않습니다. UI 이동은 direct Activity PendingIntent를 사용하고 Receiver/Service trampoline을 쓰지 마세요. 실제 클릭에서 handleNotificationIntent로 전달하면 CLICKED만 보고하며 action ID 분석은 없습니다.
SDK 로그인과 로그아웃
- 인증 모드는 프로젝트 환경마다 하나이고 그 환경의 모든 앱에 적용돼요. 프로젝트 환경의 첫 앱은 BASIC으로 시작하고, 나중에 추가한 앱은 그 환경의 모드를 따라요. 새 SDK의 간편 초기화는 대시보드 정책을 자동 조회하므로 앱에 모드를 중복 지정하지 않아요. BASIC은 고객 인증 callback·JWT 발급 API·서명 키가 필요 없어요. BASIC은 external ID 소유권을 검증하지 않아서 같은 프로젝트 환경의 클라이언트가 다른 UID를 주장하고 그 알림을 받을 수 있어요. 이 제약을 받아들이는 환경에서 사용하세요. VERIFIED 인증 실패는 BASIC으로 자동 전환되지 않아요.
- VERIFIED는 신뢰할 수 있는 고객 백엔드의 JWT가 필요해요. 앱 시작 시 SDK의 인증 callback을 연결해요. 고객 서버는 세션·활성 계정을 검증하고 요청한 external_id가 세션 UID와 같은지 확인한 뒤 createIdentityTokenSigner로 JWT를 로컬 서명해요. Kits 토큰 발급 HTTP 요청은 없고 기본 TTL은 300초예요. 앱이 보낸 사용자 ID만 믿으면 안 돼요.
- generateIdentityKeyPair로 고객 환경에서 키쌍을 한 번 생성해요. keyId와 publicKeyPem만 Dashboard의 프로젝트 설정 → SDK 연결에서 프로젝트 환경에 등록하고 privateKeyPem은 고객 비밀 저장소에 보관해요. 등록한 공개 키는 그 프로젝트 환경의 모든 앱이 함께 써요. 개인 키는 Kits에 업로드하지 않아요. 새 공개 키 등록 → 고객 signer 키 교체 → 이전 키 폐기 순서로 회전해요. JWT 서명·검증 실패를 구형 발급이나 검증 없는 로그인으로 자동 우회하지 마세요.
- 두 모드 모두 자기 앱 설치의 deviceToken으로 GET /v1/installations/{id}/identity를 조회하고 POST /v1/installations/{id}/identity로 연결해요. VERIFIED LOGIN은 token만, BASIC LOGIN은 external_id만 보내요. LOGOUT은 token과 external_id를 모두 생략해요. SDK가 expected_binding_id·operation_id·보안 저장·직렬화·재시도를 관리해요. 증명과 기기 토큰을 로그에 남기지 마세요.
- SDK는 작업의 모드를 저장하며 구형 journal은 VERIFIED로 읽어요. 모드 변경 후 재시작하면 먼저 완료 상태를 조회하고, 다른 모드의 미완료 LOGIN은 IDENTITY_AUTH_MODE_CHANGED로 중단해요. 현재 세션의 login을 다시 호출하면 로그아웃 barrier를 거쳐 새 모드 작업을 만들어요.
- 새 AUTO 흐름은 GET /v1/installations/{id}/identity-policy에서 현재 binding과 정책 revision을 함께 읽어요. 새 LOGIN의 expected_policy_revision이 바뀌면 409 IDENTITY_REBIND_CONFLICT로 다시 확인해요. 조회 실패·잘못된 응답을 BASIC으로 추정하지 않아요. 이미 보낸 작업은 먼저 결과를 확인하고 VERIFIED 증명은 임의로 제거하지 않아요. LOGOUT은 정책 조회 없이 기존 identity 경로를 사용해요. 위 명시적 모드 방식과 구형 journal은 계속 호환돼요.
- JWT는 ES256, aud=kits:identity, iss=기기가 속한 앱 환경, identity.external_id와 정수 초 iat/exp를 사용해요. TTL은 1–3600초이며 기기·binding·operation에 묶이지 않아요. 탈취 시 유효 기간 내 다른 인증된 설치에서 재사용될 수 있고 내용은 암호화되지 않아요. HTTPS와 Cache-Control: no-store를 사용하고 개인 키·JWT·요청 본문을 로그에 남기지 마세요. 메시지마다 JWT를 발급하지 않아요.
- 구형 호환: identity.createToken과 POST /v1/identity-tokens는 기존 kitid1 증명을 위해 유지하지만 새 연동에서는 deprecated예요. 이 구형 HTTP 경로만 identity:write 서버 키와 설치·binding·operation 문맥을 요구해요. 로컬 JWT 서명은 Kits API 키가 필요 없고 messages.create 발송은 별도의 messages:send 서버 키를 사용해요.
- 로컬 큐에 저장됐다는 사실은 서버에서 로그아웃이 완료됐다는 뜻이 아니에요. identityState로 원격 확인 결과를 확인하세요. 이 흐름과 기존 서버 identify/logout 호출을 동시에 실행하지 마세요.
아래 키 설정과 서명 예시는 VERIFIED에만 필요해요.
import { createIdentityTokenSigner } from '@marco-inc/kits-node';
// VERIFIED backend only: load the private key from your secret store.
const signer = createIdentityTokenSigner({
appEnvironmentId, keyId, privateKeyPem, ttlSeconds: 300,
});
// In an authenticated handler, after checking the active account:
if (request.external_id !== verifiedUid) throw new Error('Session changed');
const result = signer.sign(verifiedUid); // {token, expires_at}; no HTTP call
// Return only to the authenticated app with Cache-Control: no-store.아래 호출은 각각 로그인 이벤트, 로그아웃 이벤트, 상태 조회에 사용하는 별도 예시예요. 순서대로 한 번에 실행하지 마세요.
// In Application.onCreate, once (managed push-fcm module):
Kits.initialize(this, publicKey)
// On authenticated login:
Kits.login(userID)
// On logout (a separate event):
Kits.logout()
// Remote confirmation, not just local queuing:
Kits.identityState()알림 상태표시줄 아이콘
관리형 push-fcm SDK는 kits_notification_small_icon 리소스를 기본 작은 아이콘으로 사용해요. 앱에 같은 이름의 리소스를 넣으면 SDK 기본 아이콘을 바꿀 수 있어요. 대시보드에 파일을 업로드하는 기능이 아니라 앱 빌드에 포함해야 하는 리소스예요.
app/src/main/res/drawable-mdpi/kits_notification_small_icon.png // 24 × 24 px
app/src/main/res/drawable-hdpi/kits_notification_small_icon.png // 36 × 36 px
app/src/main/res/drawable-xhdpi/kits_notification_small_icon.png // 48 × 48 px
app/src/main/res/drawable-xxhdpi/kits_notification_small_icon.png // 72 × 72 px
app/src/main/res/drawable-xxxhdpi/kits_notification_small_icon.png // 96 × 96 px투명 배경에 흰색 단색 실루엣을 사용하세요. Android는 투명도를 기준으로 표시하므로 배경이 채워진 컬러 로고나 adaptive launcher icon을 그대로 쓰면 네모처럼 보이거나 올바르게 표시되지 않을 수 있어요. 단색 vector drawable도 사용할 수 있어요. 기본 작은 아이콘은 SDK manifest에 이미 연결돼 있어 앱 manifest에 같은 설정을 다시 적을 필요는 없어요.
메시지별 변경은 푸시 작성 → 고급 옵션 → Android 설정 → 작은 아이콘 리소스에서 확장자 없는 이름을 입력하세요. 예: ic_stat_analysis. API 필드는 android_small_icon이에요. 해당 리소스가 수신 앱 버전에 이미 포함돼 있어야 하며 URL은 사용할 수 없어요. 포그라운드에서 메시지별 아이콘을 적용하려면 이를 지원하는 새 SDK 릴리스가 필요해요.
리소스 축소를 사용하는 릴리스 빌드에서는 이름으로만 참조하는 추가 아이콘·소리를 보존하세요. 기존 keep 설정과 합쳐 실제 파일 이름만 넣고, 최종 릴리스 APK에서도 확인하세요.
<!-- app/src/main/res/raw/kits_resource_keep.xml -->
<resources xmlns:tools="http://schemas.android.com/tools"
tools:keep="@drawable/ic_stat_analysis,@raw/analysis_done" />대시보드에서 Android 알림 채널 관리
- Android 앱 환경 → 앱 설정 → 알림 채널에서 채널 또는 그룹을 추가하세요. 이름·설명은 기기 알림 설정에 표시돼요.
- 중요도·소리·진동·LED·배지·잠금 화면 기본값을 정하고 저장하세요. 채널 관리 서버와 동기화를 지원하는 Android SDK 릴리스가 모두 필요해요. 기존 초기화 호출에 별도 채널 코드는 추가하지 않아도 돼요.
- 푸시 작성 → 고급 옵션 → Android 설정 → 알림 채널에서 이름으로 선택하세요. 선택하지 않으면 앱 기본 채널을 사용해요. 앱 코드에 직접 만든 채널 ID도 입력할 수 있어요.
기존 채널의 이름·설명·보관 상태는 바꿀 수 있어요. 등록 후 소리·중요도 등 동작을 바꾸려면 새 채널을 만드세요. 사용자가 끈 채널을 다시 켜기 위한 용도로 새 채널을 만들지는 마세요. 보관은 새 발송 선택 목록에서 숨기며 기기 채널을 삭제하지 않아요. 채널 그룹은 Android 설정 정리용이고 알림 묶음 표시의 group key와는 달라요.
대화·게시글별 알림 묶기
android_group을 지원하는 서버와 Android SDK 0.7.3 이상이 필요해요. 메시지마다 서버에서 묶음 ID를 지정할 수 있어요. 같은 ID의 개별 알림은 유지하면서 함께 표시해요. 채널 설정이나 기존 알림을 교체하는 android_notification_tag와는 달라요.
"android_group": "chat:room-1"
// Optional app fallback when the server does not specify android_group:
<string name="kits_notification_group_key_field" translatable="false">chatId</string>서버 ID가 우선이고, 없으면 앱에 설정한 데이터 필드의 값(예: data.chatId)을 사용해요. 둘 다 없으면 묶지 않아요. 알림 하나는 묶음 하나에 속하지만 묶음 ID는 메시지마다 달라질 수 있어요. 새 대화·게시글이 생겨도 앱을 업데이트할 필요는 없어요. chat:와 post:처럼 접두어를 구분하면 같은 숫자 ID가 섞이지 않아요.
SDK가 직접 표시하는 Android 텍스트 알림에 적용돼요. 이미지 알림과 OS 직접 표시 경로에는 적용되지 않아요. iOS 묶음은 기존 ios_thread_id를 사용하세요.

