모바일 SDK딥링크
Android
AdStage DeepLink 통합 가이드 (Android)
목차
개요
AdStage DeepLink SDK는 다음 기능을 제공합니다:
- 실시간 딥링크: URL Scheme, App Link를 통한 즉시 처리
- 디퍼드 딥링크: 앱 설치 후 첫 실행 시 복원
- 동적 딥링크 생성: 서버 API를 통한 추적 가능한 링크 생성
- 어트리뷰션 추적: UTM 파라미터 기반 마케팅 분석
프로젝트 설정
Gradle 의존성 추가
settings.gradle.kts
dependencyResolutionManagement {
repositories {
google()
mavenCentral()
maven { url = uri("https://repo.nbase.io/repository/nbase-releases") }
}
}build.gradle.kts (Module: app)
dependencies {
implementation("io.nbase:adapter-adstage:3.0.+")
// 필수 의존성
implementation("org.jetbrains.kotlinx:kotlinx-coroutines-android:1.7.3")
implementation("com.squareup.okhttp3:okhttp:4.11.0")
}AndroidManifest.xml 설정
1. 권한 설정
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
<!-- 필수 권한 -->
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<!-- Install Referrer 권한 (디퍼드 딥링크용) -->
<uses-permission android:name="com.google.android.finsky.permission.BIND_GET_INSTALL_REFERRER_SERVICE" />
<application>
<!-- ... -->
</application>
</manifest>2. DeepLink Intent Filter 설정
<activity
android:name=".MainActivity"
android:exported="true"
android:launchMode="singleTask">
<!-- 기본 런처 -->
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
<!-- URL Scheme 딥링크 (예: myapp://promo/summer) -->
<intent-filter>
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" />
<data
android:scheme="myapp"
android:host="promo" />
</intent-filter>
<!-- App Link (https://go.myapp.com/...) -->
<intent-filter android:autoVerify="true">
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" />
<data
android:scheme="https"
android:host="go.myapp.com" />
</intent-filter>
<!-- AdStage 딥링크 도메인 (예: https://go.adstage.net/...) -->
<intent-filter android:autoVerify="true">
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" />
<data
android:scheme="https"
android:host="go.adstage.net" />
</intent-filter>
</activity>3. launchMode 설정 중요 사항
<!-- 권장: singleTask -->
android:launchMode="singleTask"
<!-- singleTask 사용 시:
- 기존 Activity가 있으면 재사용
- onNewIntent()가 호출됨
- Task 최상단으로 이동
-->Application 클래스 설정
MyApplication.kt
package com.example.myapp
import android.app.Application
import io.nbase.adapter.adstage.AdStage
class MyApplication : Application() {
override fun onCreate() {
super.onCreate()
// AdStage SDK 초기화
AdStage.initialize(
context = this,
apiKey = "your-api-key-here",
serverUrl = "https://api.adstage.app" // 선택사항, 기본값 사용 가능
)
// 딥링크 리스너 설정
setupDeeplinkListener()
}
private fun setupDeeplinkListener() {
AdStage.setDeeplinkListener(object : io.nbase.adapter.adstage.models.DeeplinkListener {
override fun onDeeplinkReceived(data: io.nbase.adapter.adstage.models.DeeplinkData) {
android.util.Log.d("AdStage", """
✅ 딥링크 수신
- Short Path: ${data.shortPath}
- Link ID: ${data.linkId}
- Source: ${data.source}
- Parameters: ${data.parameters}
""".trimIndent())
// 전역적으로 처리하거나 이벤트 버스로 전달
handleGlobalDeeplink(data)
}
override fun onDeeplinkFailed(error: String, shortPath: String?) {
android.util.Log.e("AdStage", """
❌ 딥링크 실패
- Error: $error
- Short Path: $shortPath
""".trimIndent())
}
})
}
private fun handleGlobalDeeplink(data: io.nbase.adapter.adstage.models.DeeplinkData) {
// 전역 이벤트 버스나 SharedFlow를 통해 현재 Activity에 전달
// 또는 딥링크 매니저에 저장 후 Activity에서 가져가기
}
}AndroidManifest.xml에 Application 등록
<application
android:name=".MyApplication"
android:allowBackup="true"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:theme="@style/Theme.MyApp">
<!-- ... -->
</application>MainActivity 설정
MainActivity.kt
package com.example.myapp
import android.content.Intent
import android.os.Bundle
import android.util.Log
import androidx.appcompat.app.AppCompatActivity
import io.nbase.adapter.adstage.AdStage
import io.nbase.adapter.adstage.models.DeeplinkData
import io.nbase.adapter.adstage.models.DeeplinkListener
class MainActivity : AppCompatActivity() {
companion object {
private const val TAG = "MainActivity"
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
// 딥링크 처리 (앱이 처음 실행되거나 백그라운드에서 실행될 때)
handleIntent(intent)
// 로컬 딥링크 리스너 설정 (선택사항)
setupLocalDeeplinkListener()
}
override fun onNewIntent(intent: Intent?) {
super.onNewIntent(intent)
setIntent(intent) // 중요: 새 Intent로 교체
Log.d(TAG, "onNewIntent called")
// 딥링크 처리 (앱이 이미 실행 중일 때 새 딥링크 수신)
handleIntent(intent)
}
private fun handleIntent(intent: Intent?) {
if (intent == null) {
Log.w(TAG, "Intent is null")
return
}
Log.d(TAG, """
Intent received:
- Action: ${intent.action}
- Data: ${intent.data}
- Extras: ${intent.extras?.keySet()?.joinToString()}
""".trimIndent())
// AdStage SDK에 Intent 전달
val handled = AdStage.handleIntent(this, intent)
if (handled) {
Log.i(TAG, "✅ AdStage가 딥링크를 처리했습니다")
} else {
Log.d(TAG, "ℹ️ AdStage 딥링크가 아닙니다")
// 일반 Intent 처리
handleRegularIntent(intent)
}
}
private fun handleRegularIntent(intent: Intent) {
when (intent.action) {
Intent.ACTION_VIEW -> {
// 일반 웹 링크나 커스텀 스킴 처리
val uri = intent.data
Log.d(TAG, "Regular deep link: $uri")
}
// 다른 액션 처리...
}
}
/**
* 로컬 딥링크 리스너 (Activity에서만 처리)
* Application에서 전역 리스너를 설정했다면 선택사항
*/
private fun setupLocalDeeplinkListener() {
AdStage.setDeeplinkListener(object : DeeplinkListener {
override fun onDeeplinkReceived(data: DeeplinkData) {
Log.d(TAG, "📱 Activity에서 딥링크 수신: ${data.shortPath}")
// 비즈니스 로직 처리
when {
data.parameters.containsKey("campaign") -> {
handleCampaignDeeplink(data)
}
data.parameters.containsKey("promo") -> {
handlePromoDeeplink(data)
}
else -> {
handleDefaultDeeplink(data)
}
}
}
override fun onDeeplinkFailed(error: String, shortPath: String?) {
Log.e(TAG, "❌ 딥링크 실패: $error")
// 에러 UI 표시
}
})
}
private fun handleCampaignDeeplink(data: DeeplinkData) {
val campaign = data.parameters["campaign"]
val channel = data.parameters["channel"]
Log.d(TAG, """
🎯 캠페인 딥링크 처리
- Campaign: $campaign
- Channel: $channel
""".trimIndent())
// 캠페인 화면으로 이동
// startActivity(Intent(this, CampaignActivity::class.java).apply {
// putExtra("campaign", campaign)
// })
}
private fun handlePromoDeeplink(data: DeeplinkData) {
val promoCode = data.parameters["promo"]
Log.d(TAG, "🎁 프로모션 코드: $promoCode")
// 프로모션 적용
// applyPromoCode(promoCode)
}
private fun handleDefaultDeeplink(data: DeeplinkData) {
Log.d(TAG, "📋 기본 딥링크 처리: ${data.shortPath}")
// 메인 화면 유지하거나 특정 화면으로 이동
}
override fun onDestroy() {
super.onDestroy()
// 리스너 정리 (메모리 누수 방지)
// 전역 리스너를 사용한다면 여기서 clear하지 않음
// AdStage.clearDeeplinkListener()
}
}딥링크 수신 처리
1. 실시간 딥링크 (앱 실행 중)
class MyActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
AdStage.setDeeplinkListener(object : DeeplinkListener {
override fun onDeeplinkReceived(data: DeeplinkData) {
// data.shortPath: "SDGWNBB"
// data.linkId: "507f1f77bcf86cd799439011"
// data.source: REALTIME 또는 DEFERRED
// data.parameters: Map<String, String>
// UTM 파라미터 추출
val utmSource = data.parameters["utmSource"]
val utmMedium = data.parameters["utmMedium"]
val utmCampaign = data.parameters["utmCampaign"]
// 커스텀 파라미터 추출
val customParam = data.parameters["customKey"]
// 화면 이동
navigateToScreen(data)
}
override fun onDeeplinkFailed(error: String, shortPath: String?) {
// 에러 처리
showErrorDialog(error)
}
})
}
}2. 디퍼드 딥링크 (앱 설치 후 첫 실행)
// 자동으로 처리됨!
// AdStage.initialize() 시점에 Install Referrer를 자동으로 조회하고
// 저장된 딥링크가 있으면 onDeeplinkReceived가 자동 호출됨
// 수동 처리가 필요한 경우:
AdStage.handleInstallReferrer(context)3. 딥링크 소스 구분
override fun onDeeplinkReceived(data: DeeplinkData) {
when (data.source) {
DeepLinkSource.REALTIME -> {
Log.d(TAG, "🔗 실시간 딥링크 (앱 실행 중 수신)")
// 즉시 화면 전환 가능
}
DeepLinkSource.INSTALL -> {
Log.d(TAG, "📦 디퍼드 딥링크 (앱 설치 후 첫 실행)")
// 온보딩 후 화면 전환 등
}
else -> {
Log.d(TAG, "❓ 알 수 없는 소스")
}
}
}딥링크 생성
1. Request 객체 방식
import io.nbase.adapter.adstage.AdStage
import io.nbase.adapter.adstage.models.*
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
class DeeplinkManager {
fun createSimpleDeeplink() {
CoroutineScope(Dispatchers.Main).launch {
try {
val request = CreateDeeplinkRequest(
name = "여름 프로모션 링크",
description = "2024년 여름 시즌 프로모션",
channel = "google-ads",
campaign = "summer2024",
parameters = mapOf(
"promo" to "SUMMER20",
"discount" to 20
)
)
val response = AdStage.createDeeplink(request)
Log.d(TAG, """
✅ 딥링크 생성 완료
- Short URL: ${response.shortUrl}
- Short Path: ${response.shortPath}
- ID: ${response.id}
""".trimIndent())
// 생성된 URL 공유
shareUrl(response.shortUrl)
} catch (e: Exception) {
Log.e(TAG, "❌ 딥링크 생성 실패: ${e.message}")
}
}
}
}2. Builder 패턴 (DSL 스타일)
fun createDeeplinkWithBuilder() {
CoroutineScope(Dispatchers.Main).launch {
try {
val response = AdStage.createDeeplink("겨울 프로모션") {
description("2024-2025 겨울 시즌 프로모션")
shortPath("WINTER24") // 사용자 지정 경로
// 트래킹 파라미터
channel("facebook-ads")
subChannel("instagram")
campaign("winter2024")
adGroup("fashion-lovers")
creative("banner-001")
content("hero-image")
keyword("winter-sale")
// 리다이렉트 설정
redirectConfig {
type(RedirectType.APP) // STORE, APP, WEB
// Android 설정
android {
appScheme("myapp://promo/winter")
packageName("com.example.myapp")
webUrl("https://example.com/promo/winter")
}
// iOS 설정
ios {
appScheme("myapp://promo/winter")
bundleId("com.example.myapp")
appStoreId("123456789")
webUrl("https://example.com/promo/winter")
}
// 데스크톱 설정
desktop {
webUrl("https://example.com/promo/winter")
}
}
// 커스텀 파라미터
parameter("discount", 30)
parameter("promoCode", "WINTER30")
parameter("validUntil", "2025-03-31")
// 상태 설정
status(DeeplinkStatus.ACTIVE)
}
Log.d(TAG, "✅ Short URL: ${response.shortUrl}")
} catch (e: Exception) {
Log.e(TAG, "❌ 에러: ${e.message}")
}
}
}3. 콜백 방식 (비동기)
fun createDeeplinkWithCallback() {
val request = CreateDeeplinkRequest(
name = "앱 초대 링크",
channel = "referral",
campaign = "invite-friend",
parameters = mapOf(
"referrer" to "USER123",
"bonus" to 5000
)
)
// 콜백 방식은 suspend 함수를 코루틴으로 감싸서 사용
CoroutineScope(Dispatchers.Main).launch {
try {
val response = AdStage.createDeeplink(request)
onSuccess(response)
} catch (e: Exception) {
onError(e)
}
}
}
private fun onSuccess(response: CreateDeeplinkResponse) {
Log.d(TAG, "딥링크 생성 성공: ${response.shortUrl}")
// UI 업데이트
runOnUiThread {
textView.text = response.shortUrl
shareButton.isEnabled = true
}
}
private fun onError(error: Exception) {
Log.e(TAG, "딥링크 생성 실패: ${error.message}")
runOnUiThread {
Toast.makeText(this, "딥링크 생성 실패", Toast.LENGTH_SHORT).show()
}
}4. RedirectType 설명
enum class RedirectType {
STORE, // 앱 미설치 시 → 스토어로 이동
// 앱 설치 시 → 앱 실행 (디퍼드 딥링크)
APP, // 앱 미설치 시 → 웹 폴백 URL로 이동
// 앱 설치 시 → 앱 실행 (실시간 딥링크)
WEB // 항상 웹 URL로 이동
// 앱 설치 여부 무관
}5. 딥링크 공유 예제
fun shareDeeplink(shortUrl: String) {
val shareIntent = Intent(Intent.ACTION_SEND).apply {
type = "text/plain"
putExtra(Intent.EXTRA_TEXT, """
🎁 특별 프로모션 초대!
이 링크를 통해 가입하면 5,000원 할인 쿠폰을 드립니다.
$shortUrl
""".trimIndent())
}
startActivity(Intent.createChooser(shareIntent, "친구 초대하기"))
}고급 기능
1. 전역 사용자 속성 설정
// Application onCreate()에서 설정
val userAttributes = UserAttributes(
gender = "male",
country = "KR",
city = "Seoul",
age = "28",
language = "ko-KR"
)
AdStage.setUserAttributes(userAttributes)
// 이후 trackEvent 호출 시 자동으로 포함됨2. 전역 디바이스 정보 설정
val deviceInfo = DeviceInfo(
category = "mobile",
platform = "Android",
model = Build.MODEL,
appVersion = BuildConfig.VERSION_NAME,
osVersion = Build.VERSION.RELEASE
)
AdStage.setDeviceInfo(deviceInfo)3. 사용자 ID 및 세션 관리
// 로그인 시
AdStage.setUserId("user_123456")
// 로그아웃 시
AdStage.setUserId(null)
// 새 세션 시작
AdStage.startNewSession()
// 현재 세션 ID 조회
val sessionId = AdStage.getSessionId()
Log.d(TAG, "Current Session: $sessionId")4. 프로모션 배너 연동
// 프로모션 리스트 조회
CoroutineScope(Dispatchers.Main).launch {
val params = PromotionListParams.builder()
.bannerType("NATIVE")
.region("KR")
.limit(10)
.build()
val response = AdStage.getPromotionList(params)
response.promotions.forEach { promotion ->
Log.d(TAG, """
프로모션: ${promotion.appName}
배너: ${promotion.bannerUrl}
""".trimIndent())
}
}
// 프로모션 클릭 처리
CoroutineScope(Dispatchers.Main).launch {
val result = AdStage.handlePromotionClick(context, promotion)
when (result) {
is PromotionClickResult.Success -> {
Log.d(TAG, "스토어 열림: ${result.storeUrl}")
}
is PromotionClickResult.Failure -> {
Log.e(TAG, "실패: ${result.error}")
}
}
}트러블슈팅
1. 딥링크가 수신되지 않음
체크리스트:
- AndroidManifest.xml에 intent-filter 올바르게 설정
-
android:exported="true"설정 확인 -
android:launchMode="singleTask"설정 권장 - AdStage.initialize() 호출 확인
- setDeeplinkListener() 호출 확인
- handleIntent() 호출 확인
디버깅:
override fun onNewIntent(intent: Intent?) {
super.onNewIntent(intent)
Log.d(TAG, """
Intent Debug:
- Action: ${intent?.action}
- Data: ${intent?.data}
- Scheme: ${intent?.data?.scheme}
- Host: ${intent?.data?.host}
- Path: ${intent?.data?.path}
""".trimIndent())
handleIntent(intent)
}2. 디퍼드 딥링크가 작동하지 않음
원인:
- Install Referrer 권한 없음
- Google Play Store를 통하지 않은 설치 (APK 직접 설치)
- Install Referrer API 초기화 실패
해결:
// 수동으로 Install Referrer 처리
AdStage.handleInstallReferrer(applicationContext)
// 로그 확인
Log.d(TAG, "Install Referrer 처리 완료")3. App Link 검증 실패
확인 방법:
# 1. Digital Asset Links 파일 확인
https://go.myapp.com/.well-known/assetlinks.json
# 2. 검증 도구 사용
https://developers.google.com/digital-asset-links/tools/generator
# 3. ADB로 테스트
adb shell am start -a android.intent.action.VIEW -d "https://go.myapp.com/ABCDEF"assetlinks.json 예제:
[{
"relation": ["delegate_permission/common.handle_all_urls"],
"target": {
"namespace": "android_app",
"package_name": "com.example.myapp",
"sha256_cert_fingerprints": [
"14:6D:E9:83:C5:73:06:50:D8:EE:B9:95:2F:34:FC:64:16:A0:83:42:E6:1D:BE:A8:8A:04:96:B2:3F:CF:44:E5"
]
}
}]4. ProGuard/R8 난독화 문제
proguard-rules.pro:
# AdStage SDK
-keep class io.nbase.adapter.adstage.** { *; }
-keepclassmembers class io.nbase.adapter.adstage.** { *; }
# 모델 클래스
-keep class io.nbase.adapter.adstage.models.** { *; }
# OkHttp
-dontwarn okhttp3.**
-keep class okhttp3.** { *; }
# Kotlin Coroutines
-keepnames class kotlinx.coroutines.internal.MainDispatcherFactory {}
-keepnames class kotlinx.coroutines.CoroutineExceptionHandler {}
5. 멀티 프로세스 환경
<!-- 별도 프로세스에서 실행되는 Activity가 있는 경우 -->
<activity
android:name=".SomeActivity"
android:process=":separate">
<!-- 이 Activity에서도 딥링크를 처리하려면 별도 초기화 필요 -->
</activity>// 각 프로세스에서 AdStage.initialize() 호출 필요
class MyApplication : Application() {
override fun onCreate() {
super.onCreate()
// 모든 프로세스에서 초기화
AdStage.initialize(this, apiKey)
}
}테스트 방법
1. ADB 테스트
# URL Scheme 테스트
adb shell am start -a android.intent.action.VIEW -d "myapp://promo/summer"
# App Link 테스트
adb shell am start -a android.intent.action.VIEW -d "https://go.myapp.com/ABCDEF"
# 파라미터 포함
adb shell am start -a android.intent.action.VIEW -d "myapp://promo?campaign=summer&discount=20"2. Intent 로그 확인
override fun onNewIntent(intent: Intent?) {
super.onNewIntent(intent)
intent?.let {
Log.d(TAG, "=== Intent Debug ===")
Log.d(TAG, "Action: ${it.action}")
Log.d(TAG, "Data: ${it.data}")
Log.d(TAG, "Extras: ${it.extras?.keySet()?.joinToString()}")
it.data?.let { uri ->
Log.d(TAG, "URI Scheme: ${uri.scheme}")
Log.d(TAG, "URI Host: ${uri.host}")
Log.d(TAG, "URI Path: ${uri.path}")
Log.d(TAG, "URI Query: ${uri.query}")
}
}
}3. 실제 디바이스 테스트
// 테스트용 딥링크 생성
CoroutineScope(Dispatchers.Main).launch {
val response = AdStage.createDeeplink("테스트 링크") {
channel("test")
campaign("test-campaign")
parameter("test", "true")
}
Log.d(TAG, "테스트 URL: ${response.shortUrl}")
// URL을 복사하여 브라우저나 메신저에서 테스트
}
