딥 링크란?
- 딥 링크란 외부 URL이나 알림 등에서 앱 내 특정 화면으로 직접 진입할 수 있게 해주는 메커니즘
- 단순히 앱을 여는 것이 아니라 원하는 목적지까지 한 번에 도달하게 해준다.

| 사례 | 트리거 | 목적 |
| OAuth 콜백 | 외부 앱/브라우저 | 인증 완료 후 앱 복귀 |
| 이메일 인증 | 이메일/문자 링크 | 토큰 수신 및 자동 로그인 |
| 푸시 알림 | FCM 알림 클릭 | 특정 화면 직접 진입 |
| SNS 공유 | 카카오톡/인스타 등 | 콘텐츠/상품 화면 공유 |
| QR 코드 | 카메라 스캔 | 오프라인→온라인 연결 |
1. XML 방식
1-1. 매니페스트 선언
딥 링킹을 활성화하려면 딥 링크를 처리해야 하는 Activity에 대해 AndroidManifest.xml에서 intent filter를 선언해야 한다.
- android:scheme: URL 스키마(가령, https)를 지정합니다.
- android:host: 도메인(가령, example.com)을 지정합니다.
- android:pathPrefix: URL의 경로(가령, /deepLink)를 정의합니다.
이 설정을 통해 https://example.com/deepLink와 같은 URL이 액티비티를 열도록 허용합니다.
<!-- AnimalDiary - AndroidManifest.xml -->
<activity
android:name=".ui.login.AuthCallbackActivity"
android:exported="true"
android:launchMode="singleTask">
<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:host="auth"
android:scheme="animaldiary" />
</intent-filter>
</activity>
animaldiary://auth 형태의 URL이 들어오면 AuthCallbackActivity가 실행.
android:launchMode="standard" 모드라면 딥 링크가 들어올 때마다 Activity 인스턴스가 새로 쌓이는데, "singleTask”로 설정하면 이미 인스턴스가 존재할 경우 새로 만들지 않고 기존 인스턴스를 재사용.
1-2. Activity에서 직접 처리
// AnimalDiary - AuthCallbackActivity.kt
@AndroidEntryPoint
class AuthCallbackActivity : AppCompatActivity() {
@Inject
lateinit var tokenManager: TokenManager
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
// ① intent.data에서 쿼리 파라미터 추출
val token = intent?.data?.getQueryParameter("token")
if (!token.isNullOrEmpty()) {
lifecycleScope.launch {
// ② 토큰을 DataStore에 비동기 저장
tokenManager.saveToken(token)
// ③ 저장 완료 후 MainActivity로 이동, 백 스택 초기화
val mainIntent = Intent(
this@AuthCallbackActivity,
MainActivity::class.java
).apply {
flags = Intent.FLAG_ACTIVITY_NEW_TASK or
Intent.FLAG_ACTIVITY_CLEAR_TASK
}
startActivity(mainIntent)
}
} else {
// ④ 유효하지 않은 딥 링크 → 조용히 종료 (폴백 처리)
finish()
}
}
}
2. Compose 방식
2-1. 매니페스트 선언
<!-- Kiero - AndroidManifest.xml -->
<activity
android:name="com.kakao.sdk.auth.AuthCodeHandlerActivity"
android:exported="true">
<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:host="oauth"
android:scheme="kakao${NATIVE_APP_KEY}" />
</intent-filter>
</activity>
이전 프로젝트와 달리 SAA구조이기에 직접 Activity를 만들지 않음.
카카오 SDK가 제공하는 AuthCodeHandlerActivity를 매니페스트에 등록하는 것만으로 OAuth 콜백 처리 위임. kakaoXXXXXX://oauth 형태의 URL이 들어오면 SDK가 알아서 인증 코드를 파싱하고 결과를 앱으로 돌려준다.
2-2. Application: SDK 초기화
// KieroApplication.kt
@HiltAndroidApp
class KieroApplication : Application(), ImageLoaderFactory {
override fun onCreate() {
super.onCreate()
initKakaoSdk()
}
private fun initKakaoSdk() {
try {
KakaoSdk.init(this, BuildConfig.KAKAO_NATIVE_APP_KEY)
Timber.tag("KAKAO_INIT").d("✅ 카카오 SDK 초기화 성공")
} catch (e: Exception) {
Timber.tag("KAKAO_INIT").e(e, "❌ 카카오 SDK 초기화 실패")
}
}
}
Application.onCreate()에서 SDK를 초기화해두어야 이후 딥 링크로 AuthCodeHandlerActivity가 열렸을 때 SDK가 정상 동작한다. 초기화 없이 콜백을 받으면 처리할 수 없으므로, 딥 링크보다 반드시 먼저 실행되어야 하는 코드다.
2-3. ViewModel: 로그인 로직과 상태 관리
// AuthParentViewModel.kt
fun loginWithKakao(context: Context) = viewModelScope.launch {
_state.update { it.copy(uiState = UiState.Loading) }
authRepository.loginWithKakao(context) // 내부적으로 카카오 SDK 호출
.onSuccess { result ->
handleKakaoLoginResultUseCase(
name = result.name,
image = result.image
).onSuccess { kakaoLoginResult ->
when (kakaoLoginResult) {
is KakaoLoginResult.HasChildren ->
_sideEffect.emit(AuthSideEffect.NavigateToParentGraph)
is KakaoLoginResult.NoChildren ->
_sideEffect.emit(AuthSideEffect.NavigateToParentSignUp)
}
}
}
.onFailure { throwable ->
// 사용자가 직접 취소한 경우를 별도 분기
if (throwable is ClientError &&
throwable.reason == ClientErrorCause.Cancelled) {
handleError(message = "로그인이 취소되었습니다")
return@onFailure
}
handleError(throwable)
}
}
카카오 SDK가 딥 링크 콜백을 처리하고 결과를 반환하면, ViewModel이 그 결과에 따라 SideEffect로 네비게이션 이벤트를 emit한다.
사용자가 카카오 로그인 창을 직접 닫은 경우, ClientErrorCause.Cancelled로 구분해 별도 메시지를 보여주었다.
2-4. Composable: SideEffect 수신 및 화면 전환
// AuthParentScreen.kt
viewModel.sideEffect.collectSingleEvent {
when (it) {
AuthSideEffect.NavigateToParentGraph -> navigateToParentGraph()
is AuthSideEffect.NavigateToParentSignUp -> navigateToParentSignUp()
AuthSideEffect.NavigateToSelection -> navigateToSelection()
is AuthSideEffect.ShowSnackbar -> {
globalTrigger.showSnackbar(
SnackbarState(message = it.message, bottomPadding = 110)
)
}
else -> {}
}
}
Composable은 ViewModel의 SideEffect를 구독하고, 이벤트 종류에 따라 네비게이션 함수를 호출한다.
UI는 "어디로 갈지"만 실행하고, 비즈니스 로직은 ViewModel에 있다.
3. 테스트: ADB로 딥 링크 시뮬레이션
실제 링크를 기기에서 열어볼 필요 없이, 터미널에서 바로 테스트할 수 있다.
# AnimalDiary 딥 링크 테스트
adb shell am start -a android.intent.action.VIEW \\
-d "animaldiary://auth?token=test_token_123" \\
com.animaldiary.app
# 카카오 OAuth 콜백 테스트
adb shell am start -a android.intent.action.VIEW \\
-d "kakao{NATIVE_APP_KEY}://oauth?code=auth_code_here" \\
com.kiero
올바르게 설정되었다면 대상 Activity가 열리며 Intent 데이터가 파싱된다.
4. 추가 고려 사항
4-1 . 커스텀 스키마 vs HTTPS
앱 내부에서만 쓰는 딥 링크라면 myapp:// 같은 커스텀 스키마로 충분하다.
다만 앱이 설치되지 않은 기기에서는 브라우저가 myapp://을 해석하지 못해 "웹페이지를 찾을 수 없음" 오류가 뜬다.
외부 사용자를 대상으로 한다면 https:// 스키마가 더 안전하다.
앱이 설치되어 있으면 앱이 열리고, 없으면 웹 브라우저나 플레이스토어로 자연스럽게 유도할 수 있기 때문이다.
두 프로젝트 모두 커스텀 스키마(animaldiary://, kakaoXXX://)를 사용하고 있는데, 이는 앱↔앱 간 OAuth 콜백처럼 앱이 반드시 설치된 환경을 전제로 하는 내부 흐름이므로 적절한 선택이다.
4-2. 폴백 처리 (Fallback Handling)
딥 링크로 들어온 데이터가 유효하지 않거나 파라미터가 불완전한 경우를 반드시 처리해야 한다.
로그인 화면으로 보내거나 에러 메시지를 보여주는 것이 더 나은 UX다.
4-3. 내비게이션과 백 스택
딥 링크로 특정 화면에 바로 진입했을 때, 사용자가 뒤로 가기를 누르면 앱이 바로 종료되는 경우가 있다.
백 스택에 아무것도 없기 때문이다.
예를 들어 카카오톡 공유 링크로 상품 화면에 들어왔다가 뒤로 가기를 눌렀을 때 앱이 꺼진다면 최악의 UX다.
이를 해결하려면 해당 화면 아래에 홈 화면을 인위적으로 깔아두어야 한다.
4-4. App Links
https:// 딥 링크를 사용할 경우, 클릭 시 브라우저 선택 창이 뜰 수 있다.
App Links를 설정하면 브라우저를 거치지 않고 앱에서 직접 열린다.
animal diary
GitHub is where animal diary builds software.
github.com
Team-Kiero
Team-Kiero has 4 repositories available. Follow their code on GitHub.
github.com
딥 링크 만들기 | App architecture | Android Developers
Android에서 딥 링크를 구현하고 테스트하여 웹브라우저, 알림, 광고와 같은 다양한 외부 소스에서 앱으로 직접 이동할 수 있도록 합니다.
developer.android.com
GitHub - skydoves/manifest-android-interview: 🚀 Manifest Android Interview is the ultimate guide to cracking Android technica
🚀 Manifest Android Interview is the ultimate guide to cracking Android technical interviews. - skydoves/manifest-android-interview
github.com
'Android > Kotlin' 카테고리의 다른 글
| [Android/Kotlin] Locale.KOREAN을 줬는데 왜 AM/PM이 나올까? (0) | 2026.05.21 |
|---|---|
| [Android/Kotlin] Material Design 알아보기! (1) | 2026.05.07 |
| [Android/Kotlin] Dagger-Hilt로 의존성 주입하기 (0) | 2025.09.12 |
| [Android/Kotlin] 비동기 작업, Enqueue부터 코루틴까지 (1) | 2025.09.12 |
| # 코틀린OOP 실습 2주차 (0) | 2024.03.19 |