Android Data-Scheme 설정 후 앱 아이콘 사라지는 이유와 해결 방법
Android 앱에서 특정 URL로 앱을 실행하려고 Data-Scheme을 설정했는데, 갑자기 앱 서랍(App Drawer)에서 앱 아이콘이 사라지는 경험을 해보셨나요? 앱은 분명히 설치되어 있고, 설정 메뉴에서도 보이는데 홈 화면에서만 찾을 수 없는 이 문제는 많은 개발자들이 겪는 흔한 실수입니다.
이 글에서는 왜 이런 문제가 발생하는지 원인을 쉽게 설명하고, AndroidManifest.xml 파일을 어떻게 수정해야 하는지 실제 코드 예제와 함께 단계별로 안내합니다. Data-Scheme 기능은 유지하면서 앱 아이콘도 정상적으로 표시되는 방법을 배워보세요.
Data-Scheme을 설정한 후 나타나는 전형적인 증상들을 살펴보겠습니다.
다음과 같은 상황이라면 이 글이 도움이 될 것입니다.
많은 개발자들이 다음과 같은 상황에서 이 문제를 경험합니다.
웹사이트에서 앱으로 연결되는 딥링크 기능을 구현하다가 발생합니다. 예를 들어 myapp://product/123 같은 URL로 특정 상품 페이지를 열려고 할 때입니다.
다른 앱에서 내 앱을 호출할 수 있도록 커스텀 URL 스킴을 만들 때 발생합니다.
웹페이지의 특정 버튼을 눌렀을 때 네이티브 앱이 실행되도록 구현하는 과정에서 발생합니다.
이 문제는 AndroidManifest.xml 파일의 Intent-Filter 설정이 잘못되어 발생합니다. 쉽게 말하면, Android 시스템에게 "이 앱을 앱 서랍에 표시해주세요"라고 알려주지 않은 것입니다.
다음은 문제를 일으키는 전형적인 설정입니다.
<activity android:name=".MainActivity">
<intent-filter>
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<data android:scheme="myapp" />
</intent-filter>
</activity>
Android 시스템이 앱을 앱 서랍에 표시하려면 특정한 "표시"가 필요합니다. 위 코드의 문제점을 하나씩 살펴보겠습니다.
android.intent.category.LAUNCHER가 없어서 Android 시스템이 "이 앱을 앱 서랍에 보여줘야 한다"는 것을 알 수 없습니다. 마치 가게 간판이 없는 것과 같습니다.
android.intent.action.MAIN이 없어서 앱의 메인 진입점이 정의되지 않았습니다. 시스템이 "이 앱을 실행하려면 어디서부터 시작해야 하지?"라고 헷갈리는 상태입니다.
현재 설정은 myapp://example 같은 특정 URL이 올 때만 앱을 실행하도록 되어 있습니다. 일반적인 앱 실행(아이콘 터치)은 고려되지 않은 것입니다.
Intent-Filter는 "이 앱이 어떤 일을 할 수 있는지" Android 시스템에 알려주는 설명서입니다.
| 구성 요소 | 역할 | 예시 |
|---|---|---|
| action | 어떤 동작을 할 수 있는지 | MAIN (앱 시작), VIEW (내용 보기) |
| category | 어떤 종류의 컴포넌트인지 | LAUNCHER (런처에 표시), DEFAULT (기본 카테고리) |
| data | 어떤 데이터를 처리할 수 있는지 | myapp:// (커스텀 URL), https:// (웹 URL) |
문제를 해결하는 방법은 간단합니다. 두 개의 Intent-Filter를 분리하여 작성하면 됩니다.
URL 처리용과 앱 실행용 Intent-Filter를 분리하는 것이 가장 좋은 방법입니다.
<activity android:name=".MainActivity"
android:exported="true">
<!-- 1️⃣ 앱 서랍에 아이콘 표시하기 위한 Intent-Filter -->
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
<!-- 2️⃣ 커스텀 URL 처리하기 위한 Intent-Filter -->
<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" />
</intent-filter>
</activity>
하나의 Intent-Filter에 모든 것을 넣을 수도 있지만, 코드가 복잡해지고 관리하기 어려워 권장하지 않습니다.
<activity android:name=".MainActivity"
android:exported="true">
<intent-filter>
<action android:name="android.intent.action.VIEW" />
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" />
<category android:name="android.intent.category.LAUNCHER" />
<data android:scheme="myapp" />
</intent-filter>
</activity>
이 방법은 동작은 하지만, Intent-Filter의 역할이 명확하지 않아 나중에 유지보수할 때 혼란스러울 수 있습니다.
설정이 제대로 되었는지 확인하는 방법입니다.
ADB 명령어로 커스텀 URL이 제대로 동작하는지 테스트할 수 있습니다.
adb shell am start -a android.intent.action.VIEW -d "myapp://example"
# 특정 경로가 있는 URL 테스트
adb shell am start -a android.intent.action.VIEW -d "myapp://open/product/123"
실무에서 자주 사용되는 다양한 상황별 구현 방법을 살펴보겠습니다.
단순히 myapp:// 뿐만 아니라 myapp://product/123 같은 구체적인 경로를 처리해야 할 때가 많습니다.
<activity android:name=".MainActivity"
android:exported="true">
<!-- 앱 서랍에 표시 -->
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
<!-- myapp://main 처리 -->
<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="main" />
</intent-filter>
</activity>
<!-- ProductActivity: 상품 상세 화면 -->
<activity android:name=".ProductActivity"
android:exported="true">
<!-- myapp://product/123 처리 -->
<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="product"
android:pathPrefix="/detail/" />
</intent-filter>
</activity>
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_product);
// Intent에서 URL 데이터 가져오기
Intent intent = getIntent();
Uri uri = intent.getData();
if (uri != null) {
// myapp://product/detail/123?category=electronics
String scheme = uri.getScheme(); // "myapp"
String host = uri.getHost(); // "product"
String path = uri.getPath(); // "/detail/123"
String productId = uri.getLastPathSegment(); // "123"
// 쿼리 파라미터 가져오기
String category = uri.getQueryParameter("category"); // "electronics"
// 상품 ID로 상세 정보 로드
loadProductDetail(productId, category);
}
}
커스텀 URL 대신 일반 웹 주소(https://example.com/product/123)로 앱을 실행하려면 App Links를 설정해야 합니다.
<activity android:name=".MainActivity"
android:exported="true">
<!-- 런처에 표시 -->
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
<!-- HTTPS URL 처리 -->
<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" />
<!-- HTTP와 HTTPS 모두 처리 -->
<data android:scheme="http" android:host="www.example.com" />
<data android:scheme="https" android:host="www.example.com" />
</intent-filter>
</activity>
한 앱에서 여러 개의 URL 스킴을 동시에 처리해야 하는 경우입니다.
<activity android:name=".MainActivity"
android:exported="true">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
<!-- 방법 1: 하나의 Intent-Filter에 여러 data 요소 -->
<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" />
<data android:scheme="altapp" />
<data android:scheme="customapp" />
</intent-filter>
</activity>
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
Intent intent = getIntent();
Uri uri = intent.getData();
if (uri != null) {
String scheme = uri.getScheme();
// 어떤 스킴으로 실행되었는지에 따라 다른 처리
if ("myapp".equals(scheme)) {
handleMyAppScheme(uri);
} else if ("altapp".equals(scheme)) {
handleAltAppScheme(uri);
} else if ("customapp".equals(scheme)) {
handleCustomAppScheme(uri);
}
}
}
웹페이지에서 커스텀 URL을 사용하여 앱을 실행하는 방법입니다.
<!-- 방법 1: 일반 링크 -->
<a href="myapp://product/123?category=electronics">
앱에서 상품 보기
</a>
<!-- 방법 2: 버튼 클릭 시 실행 -->
<button onclick="openApp()">앱 열기</button>
<script>
function openApp() {
// 커스텀 URL로 앱 실행 시도
var appUrl = "myapp://product/123";
var storeUrl = "https://play.google.com/store/apps/details?id=com.example.myapp";
// 앱 실행 시도
window.location.href = appUrl;
// 2초 후에도 페이지가 바뀌지 않으면 스토어로 이동
setTimeout(function() {
window.location.href = storeUrl;
}, 2000);
}
</script>
A: Android 12(API 레벨 31) 이상에서는 android:exported 속성을 명시적으로 설정해야 합니다. Intent-Filter가 있는 모든 Activity에 이 속성을 추가하세요.
<activity android:name=".MainActivity"
android:exported="true"> <!-- 이 부분 필수! -->
<intent-filter>
...
</intent-filter>
</activity>
A: Data-Scheme은 myapp:// 같은 커스텀 URL을 사용하고, App Links는 https://example.com 같은 일반 웹 URL을 사용합니다.
| 구분 | Data-Scheme | App Links |
|---|---|---|
| URL 형식 | myapp://example | https://example.com |
| 설정 난이도 | 쉬움 | 어려움 (웹 서버 설정 필요) |
| 사용 목적 | 앱 간 통신, 내부 딥링크 | 웹-앱 연동, SEO |
| 검증 필요 | 불필요 | 필요 (assetlinks.json) |
| 앱 미설치 시 | 에러 발생 | 웹페이지 열림 |
A: Data-Scheme Intent-Filter에 BROWSABLE 카테고리가 빠졌을 가능성이 높습니다. 다음을 추가하세요.
<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" />
</intent-filter>
A: 네, 가능합니다. 하지만 URL 인코딩이 필요합니다. 한글 "상품"은 "%EC%83%81%ED%92%88"으로 변환되어야 합니다.
String productName = "전자제품";
String encoded = URLEncoder.encode(productName, "UTF-8");
String url = "myapp://product?name=" + encoded;
// 받는 쪽에서 디코딩
Uri uri = getIntent().getData();
String encodedName = uri.getQueryParameter("name");
String decodedName = URLDecoder.decode(encodedName, "UTF-8");
A: 네, host나 path를 다르게 설정하면 각각 다른 Activity로 연결할 수 있습니다.
<activity android:name=".MainActivity">
<intent-filter>
<data android:scheme="myapp" android:host="main" />
</intent-filter>
</activity>
<!-- myapp://product → ProductActivity -->
<activity android:name=".ProductActivity">
<intent-filter>
<data android:scheme="myapp" android:host="product" />
</intent-filter>
</activity>
A: onNewIntent() 메서드를 오버라이드하세요. 앱이 백그라운드에 있을 때 새로운 URL이 오면 이 메서드가 호출됩니다.
@Override
protected void onNewIntent(Intent intent) {
super.onNewIntent(intent);
setIntent(intent); // Intent 업데이트
// 새로운 URL 처리
Uri uri = intent.getData();
if (uri != null) {
handleDeepLink(uri);
}
}
A: Chrome 브라우저는 보안상의 이유로 커스텀 URL 스킴을 차단하는 경우가 있습니다. Intent URL 방식을 사용하세요.
<a href="intent://product/123#Intent;scheme=myapp;package=com.example.myapp;end">
앱에서 열기
</a>
A: 네, Android의 App Links가 iOS의 Universal Links와 같은 기능입니다. 웹 URL을 사용하여 앱을 실행할 수 있으며, 앱이 없으면 웹페이지가 열립니다.
A: 네, 전혀 문제없습니다. Data-Scheme은 정상적인 Android 기능이며, 구글 플레이 정책에 위반되지 않습니다. 다만 Intent-Filter가 올바르게 설정되어 있어야 합니다.
A: 있습니다. URL로 전달받은 데이터는 반드시 검증해야 합니다. 악의적인 앱이 위험한 데이터를 보낼 수 있습니다.
Uri uri = getIntent().getData();
if (uri != null) {
String productId = uri.getLastPathSegment();
// ✅ 데이터 검증
if (productId != null && productId.matches("\\d+")) {
// 숫자만 허용
loadProduct(productId);
} else {
// 잘못된 데이터 거부
Toast.makeText(this, "잘못된 요청입니다", Toast.LENGTH_SHORT).show();
finish();
}
}
Android에서 Data-Scheme 설정 후 앱 아이콘이 사라지는 문제는 Intent-Filter의 역할을 제대로 분리하지 않아서 발생합니다. 앱 서랍에 표시되기 위한 Intent-Filter와 URL을 처리하기 위한 Intent-Filter를 별도로 작성하면 간단히 해결됩니다.
핵심은 MAIN 액션과 LAUNCHER 카테고리를 포함한 Intent-Filter를 반드시 하나 이상 유지하는 것입니다. 이를 통해 Android 시스템이 앱을 앱 서랍에 표시하고 일반적인 방법으로 실행할 수 있습니다.
Data-Scheme은 앱 간 통신, 딥링크, 웹-앱 연동 등 다양한 용도로 활용할 수 있는 강력한 기능입니다. 올바른 설정을 통해 사용자 경험을 크게 향상시킬 수 있으며, App Links와 결합하면 웹과 앱을 매끄럽게 연결할 수 있습니다.
Android 12 이상에서는 android:exported 속성 설정이 필수이며, 보안을 위해 URL로 전달받은 데이터는 반드시 검증해야 합니다. 이러한 기본 원칙을 지키면서 다양한 딥링크 기능을 구현해보세요.
끝.
'Development > Error' 카테고리의 다른 글
| [Error] ORACLE 계정이 Lock 걸렸을 때 원인과 해결 방법 (0) | 2020.04.08 |
|---|---|
| [Error] Android OS 10 Target 시 파일 조회 원인과 해결 방법 (0) | 2020.03.24 |
| [Error] 큰 용량의 PDF 읽기 (0) | 2019.10.29 |
| [Error] ListView를 드래그 하면 검게 보이는 현상 (0) | 2019.10.01 |
| [Error] A SQLiteConnection object for database was leaked! (0) | 2019.09.23 |