본문 바로가기
Development/Error

[Error] Android Data-Scheme 설정 후 앱 아이콘 사라지는 이유와 해결 방법

by 은스타 2020. 3. 22.
반응형
Android Data-Scheme 설정 후 앱 아이콘 사라지는 이유와 해결 방법

Android Data-Scheme 설정 후 앱 아이콘 사라지는 이유와 해결 방법

Android 앱에서 특정 URL로 앱을 실행하려고 Data-Scheme을 설정했는데, 갑자기 앱 서랍(App Drawer)에서 앱 아이콘이 사라지는 경험을 해보셨나요? 앱은 분명히 설치되어 있고, 설정 메뉴에서도 보이는데 홈 화면에서만 찾을 수 없는 이 문제는 많은 개발자들이 겪는 흔한 실수입니다.

이 글에서는 왜 이런 문제가 발생하는지 원인을 쉽게 설명하고, AndroidManifest.xml 파일을 어떻게 수정해야 하는지 실제 코드 예제와 함께 단계별로 안내합니다. Data-Scheme 기능은 유지하면서 앱 아이콘도 정상적으로 표시되는 방법을 배워보세요.

목차
1. 문제 상황과 증상
2. 문제 발생 원인 분석
3. 해결 방법 - Intent-Filter 올바르게 설정하기
4. 다양한 상황별 구현 예제
5. 자주 묻는 질문 (FAQ)

#1. 문제 상황과 증상

Data-Scheme을 설정한 후 나타나는 전형적인 증상들을 살펴보겠습니다.

1) 어떤 문제가 발생하나요?

다음과 같은 상황이라면 이 글이 도움이 될 것입니다.

① 앱에 myapp://example 같은 커스텀 URL 스킴을 설정했습니다
② 앱을 설치하고 나니 홈 화면의 앱 서랍에서 앱 아이콘이 보이지 않습니다
③ 하지만 설정 → 앱 목록에서는 앱이 설치된 것을 확인할 수 있습니다
④ 해당 URL(myapp://example)을 사용하면 앱이 정상적으로 실행됩니다
⑤ 다만 일반적인 방법으로는 앱을 실행할 수 없습니다
. . . . .
2) 실제 개발자들이 겪는 사례

많은 개발자들이 다음과 같은 상황에서 이 문제를 경험합니다.

(1) 딥링크 기능 추가 시

웹사이트에서 앱으로 연결되는 딥링크 기능을 구현하다가 발생합니다. 예를 들어 myapp://product/123 같은 URL로 특정 상품 페이지를 열려고 할 때입니다.

(2) 다른 앱과 연동 시

다른 앱에서 내 앱을 호출할 수 있도록 커스텀 URL 스킴을 만들 때 발생합니다.

(3) 웹뷰에서 앱 실행 시

웹페이지의 특정 버튼을 눌렀을 때 네이티브 앱이 실행되도록 구현하는 과정에서 발생합니다.


#2. 문제 발생 원인 분석

이 문제는 AndroidManifest.xml 파일의 Intent-Filter 설정이 잘못되어 발생합니다. 쉽게 말하면, Android 시스템에게 "이 앱을 앱 서랍에 표시해주세요"라고 알려주지 않은 것입니다.

1) 문제가 되는 코드 예시

다음은 문제를 일으키는 전형적인 설정입니다.

<!-- ❌ 잘못된 설정: 앱 아이콘이 사라짐 -->
<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>
. . . . .
2) 왜 문제가 발생하나요?

Android 시스템이 앱을 앱 서랍에 표시하려면 특정한 "표시"가 필요합니다. 위 코드의 문제점을 하나씩 살펴보겠습니다.

(1) LAUNCHER 카테고리가 없음

android.intent.category.LAUNCHER가 없어서 Android 시스템이 "이 앱을 앱 서랍에 보여줘야 한다"는 것을 알 수 없습니다. 마치 가게 간판이 없는 것과 같습니다.

(2) MAIN 액션이 없음

android.intent.action.MAIN이 없어서 앱의 메인 진입점이 정의되지 않았습니다. 시스템이 "이 앱을 실행하려면 어디서부터 시작해야 하지?"라고 헷갈리는 상태입니다.

(3) URL 처리만 가능한 설정

현재 설정은 myapp://example 같은 특정 URL이 올 때만 앱을 실행하도록 되어 있습니다. 일반적인 앱 실행(아이콘 터치)은 고려되지 않은 것입니다.

. . . . .
3) Intent-Filter란 무엇인가요?

Intent-Filter는 "이 앱이 어떤 일을 할 수 있는지" Android 시스템에 알려주는 설명서입니다.

구성 요소 역할 예시
action 어떤 동작을 할 수 있는지 MAIN (앱 시작), VIEW (내용 보기)
category 어떤 종류의 컴포넌트인지 LAUNCHER (런처에 표시), DEFAULT (기본 카테고리)
data 어떤 데이터를 처리할 수 있는지 myapp:// (커스텀 URL), https:// (웹 URL)

#3. 해결 방법 - Intent-Filter 올바르게 설정하기

문제를 해결하는 방법은 간단합니다. 두 개의 Intent-Filter를 분리하여 작성하면 됩니다.

1) 가장 권장하는 해결 방법

URL 처리용과 앱 실행용 Intent-Filter를 분리하는 것이 가장 좋은 방법입니다.

<!-- ✅ 올바른 설정: URL 처리와 앱 실행을 분리 -->
<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>
(1) 코드 설명
첫 번째 Intent-Filter: 앱을 앱 서랍에 표시하고 일반적인 방법으로 실행할 수 있게 합니다
② 두 번째 Intent-Filter: myapp:// 형식의 URL을 처리합니다
③ BROWSABLE 카테고리: 웹 브라우저에서도 이 URL을 처리할 수 있게 합니다
④ android:exported="true": Android 12 이상에서 필수입니다
. . . . .
2) 대안 방법 (권장하지 않음)

하나의 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의 역할이 명확하지 않아 나중에 유지보수할 때 혼란스러울 수 있습니다.

. . . . .
3) 설정 후 테스트 방법

설정이 제대로 되었는지 확인하는 방법입니다.

(1) 앱 아이콘 확인
① 앱을 언인스톨한 후 다시 설치합니다
② 홈 화면의 앱 서랍에서 앱 아이콘이 보이는지 확인합니다
③ 아이콘을 눌러 앱이 정상적으로 실행되는지 확인합니다
(2) Data-Scheme 테스트

ADB 명령어로 커스텀 URL이 제대로 동작하는지 테스트할 수 있습니다.

# myapp://example 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"

#4. 다양한 상황별 구현 예제

실무에서 자주 사용되는 다양한 상황별 구현 방법을 살펴보겠습니다.

1) Host와 Path를 사용한 상세 URL 처리

단순히 myapp:// 뿐만 아니라 myapp://product/123 같은 구체적인 경로를 처리해야 할 때가 많습니다.

<!-- MainActivity: 기본 화면 -->
<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>
(1) Activity에서 URL 파라미터 받기
// ProductActivity.java
@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);
    }
}
. . . . .
2) 웹 URL (HTTPS) 처리하기 - App Links

커스텀 URL 대신 일반 웹 주소(https://example.com/product/123)로 앱을 실행하려면 App Links를 설정해야 합니다.

<!-- 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>
(1) App Links 설정 시 주의사항
android:autoVerify="true"는 Android 6.0 이상에서 필요합니다
② 웹사이트에 assetlinks.json 파일을 업로드해야 합니다
③ 이 파일은 "이 앱이 이 도메인을 사용할 권한이 있다"고 증명하는 역할을 합니다
. . . . .
3) 여러 개의 커스텀 URL 스킴 처리

한 앱에서 여러 개의 URL 스킴을 동시에 처리해야 하는 경우입니다.

<!-- 여러 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>
(1) Activity에서 어떤 스킴으로 실행되었는지 확인
// MainActivity.java
@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);
        }
    }
}
. . . . .
4) 웹에서 앱 실행하기 (HTML 예제)

웹페이지에서 커스텀 URL을 사용하여 앱을 실행하는 방법입니다.

<!-- HTML에서 앱 실행 링크 -->
<!-- 방법 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>

#5. 자주 묻는 질문 (FAQ)
1) Q: Android 12 이상에서 앱이 실행되지 않아요

A: Android 12(API 레벨 31) 이상에서는 android:exported 속성을 명시적으로 설정해야 합니다. Intent-Filter가 있는 모든 Activity에 이 속성을 추가하세요.

<!-- Android 12 이상에서 필수 -->
<activity android:name=".MainActivity"
    android:exported="true">  <!-- 이 부분 필수! -->
    <intent-filter>
        ...
    </intent-filter>
</activity>
. . . . .
2) Q: Data-Scheme과 App Links의 차이가 뭔가요?

A: Data-Scheme은 myapp:// 같은 커스텀 URL을 사용하고, App Links는 https://example.com 같은 일반 웹 URL을 사용합니다.

구분 Data-Scheme App Links
URL 형식 myapp://example https://example.com
설정 난이도 쉬움 어려움 (웹 서버 설정 필요)
사용 목적 앱 간 통신, 내부 딥링크 웹-앱 연동, SEO
검증 필요 불필요 필요 (assetlinks.json)
앱 미설치 시 에러 발생 웹페이지 열림
. . . . .
3) Q: 앱 서랍에는 보이는데 URL로는 실행이 안 돼요

A: Data-Scheme Intent-Filter에 BROWSABLE 카테고리가 빠졌을 가능성이 높습니다. 다음을 추가하세요.

<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>
. . . . .
4) Q: URL에서 한글이나 특수문자를 전달할 수 있나요?

A: 네, 가능합니다. 하지만 URL 인코딩이 필요합니다. 한글 "상품"은 "%EC%83%81%ED%92%88"으로 변환되어야 합니다.

// Java에서 URL 인코딩
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");
. . . . .
5) Q: 여러 Activity 중 어디로 연결할지 선택할 수 있나요?

A: 네, host나 path를 다르게 설정하면 각각 다른 Activity로 연결할 수 있습니다.

<!-- myapp://main → MainActivity -->
<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>
. . . . .
6) Q: 앱이 이미 실행 중일 때 URL을 받으려면?

A: onNewIntent() 메서드를 오버라이드하세요. 앱이 백그라운드에 있을 때 새로운 URL이 오면 이 메서드가 호출됩니다.

// MainActivity.java
@Override
protected void onNewIntent(Intent intent) {
    super.onNewIntent(intent);
    setIntent(intent);  // Intent 업데이트

    // 새로운 URL 처리
    Uri uri = intent.getData();
    if (uri != null) {
        handleDeepLink(uri);
    }
}
. . . . .
7) Q: 크롬 브라우저에서만 앱이 실행되지 않아요

A: Chrome 브라우저는 보안상의 이유로 커스텀 URL 스킴을 차단하는 경우가 있습니다. Intent URL 방식을 사용하세요.

<!-- Chrome에서 동작하는 Intent URL -->
<a href="intent://product/123#Intent;scheme=myapp;package=com.example.myapp;end">
    앱에서 열기
</a>
. . . . .
8) Q: iOS처럼 Universal Links를 Android에서도 사용할 수 있나요?

A: 네, Android의 App Links가 iOS의 Universal Links와 같은 기능입니다. 웹 URL을 사용하여 앱을 실행할 수 있으며, 앱이 없으면 웹페이지가 열립니다.

. . . . .
9) Q: Data-Scheme 설정 후 구글 플레이 스토어에 업로드할 수 있나요?

A: 네, 전혀 문제없습니다. Data-Scheme은 정상적인 Android 기능이며, 구글 플레이 정책에 위반되지 않습니다. 다만 Intent-Filter가 올바르게 설정되어 있어야 합니다.

. . . . .
10) Q: 보안상 주의할 점이 있나요?

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로 전달받은 데이터는 반드시 검증해야 합니다. 이러한 기본 원칙을 지키면서 다양한 딥링크 기능을 구현해보세요.

긴 글 읽어주셔서 감사합니다.

끝.
반응형