adstage
移动 SDK深度链接

Android

AdStage DeepLink 集成指南(Android)

目录

  1. 概述
  2. 项目设置
  3. AndroidManifest.xml 设置
  4. Application 类设置
  5. MainActivity 设置
  6. 深度链接接收处理
  7. 创建深度链接
  8. 高级功能
  9. 问题排查

概述

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>
<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 处理完成")

确认方法:

# 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,在浏览器或即时通讯应用中测试
}

参考资料


目录