adstage
Mobile SDKDeep Link

Android

คู่มือการเชื่อมต่อ AdStage DeepLink (Android)

สารบัญ

  1. ภาพรวม
  2. การตั้งค่าโปรเจกต์
  3. การตั้งค่า AndroidManifest.xml
  4. การตั้งค่าคลาส Application
  5. การตั้งค่า MainActivity
  6. การจัดการการรับ deep link
  7. การสร้าง deep link
  8. ฟีเจอร์ขั้นสูง
  9. การแก้ไขปัญหา

ภาพรวม

AdStage DeepLink SDK มีฟีเจอร์ดังต่อไปนี้:

  • Deep link แบบเรียลไทม์: ประมวลผลทันทีผ่าน URL Scheme และ App Link
  • Deferred deep link: กู้คืนในการเปิดแอปครั้งแรกหลังการติดตั้ง
  • การสร้าง deep link แบบไดนามิก: สร้างลิงก์ที่ติดตามได้ผ่าน API ของเซิร์ฟเวอร์
  • การติดตาม attribution: การวิเคราะห์การตลาดจากพารามิเตอร์ UTM

การตั้งค่าโปรเจกต์

การเพิ่ม Gradle dependency

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

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>
<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}
                    - 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에서 가져가기
    }
}

การลงทะเบียน Application ใน 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

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()
    }
}

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)
            }
        })
    }
}
// 자동으로 처리됨!
// AdStage.initialize() 시점에 Install Referrer를 자동으로 조회하고
// 저장된 딥링크가 있으면 onDeeplinkReceived가 자동 호출됨
 
// 수동 처리가 필요한 경우:
AdStage.handleInstallReferrer(context)
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)")
            // 온보딩 후 화면 전환 등
        }
    }
}

1. วิธีใช้ Request object

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)

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 (แบบอะซิงโครนัส)

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로 이동
            // 앱 설치 여부 무관
}
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. การตั้งค่าแอตทริบิวต์ผู้ใช้แบบ global

// Application onCreate()에서 설정
val userAttributes = UserAttributes(
    gender = "male",
    country = "KR",
    city = "Seoul",
    age = "28",
    language = "ko-KR"
)
AdStage.setUserAttributes(userAttributes)
 
// 이후 trackEvent 호출 시 자동으로 포함됨

2. การเรียกดูและล้างแอตทริบิวต์ผู้ใช้

// 현재 설정된 사용자 속성 조회
val current: UserAttributes? = AdStage.getUserAttributes()
 
// 로그아웃 시 사용자 속성 제거
AdStage.clearUserAttributes()
 
// 모든 전역 정보 초기화 (사용자 속성 제거)
AdStage.clearAll()

ข้อมูลอุปกรณ์ (รุ่น, เวอร์ชัน OS, เวอร์ชันแอป, ตัวระบุโฆษณา ฯลฯ) และเซสชันจะถูก เก็บรวบรวมและจัดการโดย SDK โดยอัตโนมัติ ไม่จำเป็นต้องเรียก API สำหรับการตั้งค่าข้อมูลอุปกรณ์ หรือการจัดการเซสชันแยกต่างหาก


การแก้ไขปัญหา

เช็คลิสต์:

  • ตั้งค่า intent-filter ใน AndroidManifest.xml ถูกต้อง
  • ตรวจสอบการตั้งค่า 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)
}

สาเหตุ:

  • ไม่มีสิทธิ์ Install Referrer
  • การติดตั้งที่ไม่ผ่าน Google Play Store (ติดตั้ง APK โดยตรง)
  • การเริ่มต้น Install Referrer API ล้มเหลว

วิธีแก้ไข:

// 수동으로 Install Referrer 처리
AdStage.handleInstallReferrer(applicationContext)
 
// 로그 확인
Log.d(TAG, "Install Referrer 처리 완료")

วิธีตรวจสอบ:

# 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. ปัญหา obfuscation ของ 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. สภาพแวดล้อมแบบ multi-process

<!-- 별도 프로세스에서 실행되는 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 log

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을 복사하여 브라우저나 메신저에서 테스트
}

เอกสารอ้างอิง


สารบัญ

คู่มือการเชื่อมต่อ AdStage DeepLink (Android)สารบัญภาพรวมการตั้งค่าโปรเจกต์การเพิ่ม Gradle dependencyการตั้งค่า AndroidManifest.xml1. การตั้งค่าสิทธิ์2. การตั้งค่า DeepLink Intent Filter3. ข้อควรระวังสำคัญเกี่ยวกับ launchModeการตั้งค่าคลาส ApplicationMyApplication.ktการลงทะเบียน Application ใน AndroidManifest.xmlการตั้งค่า MainActivityMainActivity.ktการจัดการการรับ deep link1. Deep link แบบเรียลไทม์ (ขณะที่แอปกำลังทำงาน)2. Deferred deep link (การเปิดครั้งแรกหลังการติดตั้ง)3. การแยกแยะแหล่งที่มาของ deep linkการสร้าง deep link1. วิธีใช้ Request object2. Builder pattern (สไตล์ DSL)3. วิธีใช้ callback (แบบอะซิงโครนัส)4. คำอธิบาย RedirectType5. ตัวอย่างการแชร์ deep linkฟีเจอร์ขั้นสูง1. การตั้งค่าแอตทริบิวต์ผู้ใช้แบบ global2. การเรียกดูและล้างแอตทริบิวต์ผู้ใช้การแก้ไขปัญหา1. ไม่ได้รับ deep link2. Deferred deep link ไม่ทำงาน3. การยืนยัน App Link ล้มเหลว4. ปัญหา obfuscation ของ ProGuard/R85. สภาพแวดล้อมแบบ multi-processวิธีการทดสอบ1. การทดสอบด้วย ADB2. การตรวจสอบ Intent log3. การทดสอบบนอุปกรณ์จริงเอกสารอ้างอิง