Mobile SDKDeep Link
Android
AdStage DeepLink Integration Guide (Android)
Table of Contents
- Overview
- Project Setup
- AndroidManifest.xml Configuration
- Application Class Configuration
- MainActivity Configuration
- Handling Incoming Deep Links
- Creating Deep Links
- Advanced Features
- Troubleshooting
Overview
The AdStage DeepLink SDK provides the following features:
- Real-time deep links: Instant handling via URL Scheme and App Link
- Deferred deep links: Restored on the first launch after app installation
- Dynamic deep link creation: Trackable link generation through the server API
- Attribution tracking: Marketing analytics based on UTM parameters
Project Setup
Adding Gradle Dependencies
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:nbase-adapter-adstage:3.0.9")
// 필수 의존성
implementation("org.jetbrains.kotlinx:kotlinx-coroutines-android:1.7.3")
implementation("com.squareup.okhttp3:okhttp:4.11.0")
}AndroidManifest.xml Configuration
1. Permission Setup
<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 Setup
<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. Important Notes on launchMode
<!-- 권장: singleTask -->
android:launchMode="singleTask"
<!-- singleTask 사용 시:
- 기존 Activity가 있으면 재사용
- onNewIntent()가 호출됨
- Task 최상단으로 이동
-->Application Class Configuration
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}
- Deeplink ID: ${data.deeplinkId}
- 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에서 가져가기
}
}Registering the Application in AndroidManifest.xml
<application
android:name=".MyApplication"
android:allowBackup="true"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:theme="@style/Theme.MyApp">
<!-- ... -->
</application>MainActivity Configuration
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()
}
}Handling Incoming Deep Links
1. Real-time Deep Links (While the App Is Running)
class MyActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
AdStage.setDeeplinkListener(object : DeeplinkListener {
override fun onDeeplinkReceived(data: DeeplinkData) {
// data.shortPath: "SDGWNBB"
// data.deeplinkId: "507f1f77bcf86cd799439011"
// data.source: DIRECT_LINK 또는 INSTALL_REFERRER
// data.eventType: OPEN 또는 INSTALL
// data.parameters: Map<String, String>
// 트래킹 파라미터 추출 (서버 응답 키 기준)
val channel = data.parameters["channel"]
val subChannel = data.parameters["subChannel"]
val campaign = data.parameters["campaign"]
// 커스텀 파라미터 추출
val customParam = data.parameters["customKey"]
// 화면 이동
navigateToScreen(data)
}
override fun onDeeplinkFailed(error: String, shortPath: String?) {
// 에러 처리
showErrorDialog(error)
}
})
}
}2. Deferred Deep Links (First Launch After Installation)
// 자동으로 처리됨!
// AdStage.initialize() 시점에 Install Referrer를 자동으로 조회하고
// 저장된 딥링크가 있으면 onDeeplinkReceived가 자동 호출됨
// 수동 처리가 필요한 경우:
AdStage.handleInstallReferrer(context)3. Distinguishing Deep Link Sources
import io.nbase.adapter.adstage.models.DeeplinkSource
override fun onDeeplinkReceived(data: DeeplinkData) {
when (data.source) {
DeeplinkSource.DIRECT_LINK -> {
Log.d(TAG, "🔗 직접 딥링크 클릭 (실시간)")
// 즉시 화면 전환 가능
}
DeeplinkSource.INSTALL_REFERRER -> {
Log.d(TAG, "📦 디퍼드 딥링크 (Play Store Install Referrer)")
// 온보딩 후 화면 전환 등
}
}
}Creating Deep Links
1. Request Object Approach
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 Pattern (DSL Style)
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. Callback Approach (Asynchronous)
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 Explanation
enum class RedirectType {
STORE, // 앱 미설치 시 → 스토어로 이동
// 앱 설치 시 → 앱 실행 (디퍼드 딥링크)
APP, // 앱 미설치 시 → 웹 폴백 URL로 이동
// 앱 설치 시 → 앱 실행 (실시간 딥링크)
WEB // 항상 웹 URL로 이동
// 앱 설치 여부 무관
}5. Deep Link Sharing Example
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, "친구 초대하기"))
}Advanced Features
1. Setting Global User Attributes
// Application onCreate()에서 설정
val userAttributes = UserAttributes(
gender = "male",
country = "KR",
city = "Seoul",
age = "28",
language = "ko-KR"
)
AdStage.setUserAttributes(userAttributes)
// 이후 trackEvent 호출 시 자동으로 포함됨2. Retrieving and Clearing User Attributes
// 현재 설정된 사용자 속성 조회
val current: UserAttributes? = AdStage.getUserAttributes()
// 로그아웃 시 사용자 속성 제거
AdStage.clearUserAttributes()
// 모든 전역 정보 초기화 (사용자 속성 제거)
AdStage.clearAll()Device information (model, OS version, app version, advertising identifier, etc.) and sessions are automatically collected and managed by the SDK. There is no need to call any separate device information setup or session management API.
Troubleshooting
1. Deep Links Are Not Received
Checklist:
- intent-filter is correctly configured in AndroidManifest.xml
-
android:exported="true"is set -
android:launchMode="singleTask"is recommended - AdStage.initialize() is called
- setDeeplinkListener() is called
- handleIntent() is called
Debugging:
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. Deferred Deep Links Are Not Working
Causes:
- No Install Referrer permission
- Installation not through Google Play Store (direct APK installation)
- Install Referrer API initialization failure
Solution:
// 수동으로 Install Referrer 처리
AdStage.handleInstallReferrer(applicationContext)
// 로그 확인
Log.d(TAG, "Install Referrer 처리 완료")3. App Link Verification Failure
How to Check:
# 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 Example:
[{
"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 Obfuscation Issues
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. Multi-Process Environment
<!-- 별도 프로세스에서 실행되는 Activity가 있는 경우 -->
<activity
android:name=".SomeActivity"
android:process=":separate">
<!-- 이 Activity에서도 딥링크를 처리하려면 별도 초기화 필요 -->
</activity>// 각 프로세스에서 AdStage.initialize() 호출 필요
class MyApplication : Application() {
override fun onCreate() {
super.onCreate()
// 모든 프로세스에서 초기화
AdStage.initialize(this, apiKey)
}
}Testing Methods
1. ADB Testing
# 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. Checking Intent Logs
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. Testing on a Real Device
// 테스트용 딥링크 생성
CoroutineScope(Dispatchers.Main).launch {
val response = AdStage.createDeeplink("테스트 링크") {
channel("test")
campaign("test-campaign")
parameter("test", "true")
}
Log.d(TAG, "테스트 URL: ${response.shortUrl}")
// URL을 복사하여 브라우저나 메신저에서 테스트
}References
- AdStage Official Documentation
- Android App Links Guide
- Install Referrer API
- Deep Links Best Practices

