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())
        
        // 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 -> {
                // 通常の Web リンクやカスタムスキームの処理
                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.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,    // アプリ未インストール時 → Web のフォールバック URL へ移動
            // アプリインストール済み時 → アプリを起動(リアルタイムディープリンク)
    
    WEB     // 常に 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 をコピーしてブラウザやメッセンジャーでテスト
}

参考資料


目次