行動 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: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>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}
- 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 取用
}
}於 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())
// 將 Intent 傳遞給 AdStage SDK
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 -> {
// 處理一般網頁連結或自訂 scheme
val uri = intent.data
Log.d(TAG, "Regular deep link: $uri")
}
// 處理其他 action...
}
}
/**
* 本機深層連結監聽器(僅於 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.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. 延遲深層連結(應用程式安裝後首次啟動)
// 會自動處理!
// 於 AdStage.initialize() 時會自動查詢 Install Referrer,
// 若有已儲存的深層連結,則會自動呼叫 onDeeplinkReceived
// 需要手動處理時:
AdStage.handleInstallReferrer(context)3. 區分深層連結來源
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 物件方式
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 current: UserAttributes? = AdStage.getUserAttributes()
// 登出時移除使用者屬性
AdStage.clearUserAttributes()
// 重設所有全域資訊(移除使用者屬性)
AdStage.clearAll()裝置資訊(機型、OS 版本、應用程式版本、廣告識別碼等)與工作階段由 SDK 自動 收集與管理。無需另行設定裝置資訊或呼叫工作階段管理 API。
疑難排解
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,於瀏覽器或通訊軟體中測試
}
