モバイル SDKディープリンク
Android
AdStage DeepLink 統合ガイド(Android)
目次
- 概要
- プロジェクトの設定
- AndroidManifest.xml の設定
- Application クラスの設定
- MainActivity の設定
- ディープリンクの受信処理
- ディープリンクの作成
- 高度な機能
- トラブルシューティング
概要
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())
// 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 の処理完了")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 をコピーしてブラウザやメッセンジャーでテスト
}
