본문 바로가기
Development/Error

[Error] Android Intent URI 오류 해결 방법

by 은스타 2019. 9. 6.
반응형
Android Intent URI 오류 해결 방법 - URISyntaxException 완벽 가이드

Android Intent URI 오류 해결 방법 - URISyntaxException 완벽 가이드

Android 앱 개발 중 WebView와 네이티브 간 데이터 통신을 구현할 때 Intent URI를 사용하여 데이터를 전달하는 경우가 많습니다. 하지만 URI에 공백이나 특수문자가 포함되면 java.net.URISyntaxException 오류가 발생하여 앱이 정상적으로 동작하지 않게 됩니다. 이번 포스팅에서는 Android Intent URI 오류의 원인과 해결 방법을 상세히 알아보고, 다양한 상황에서 적용할 수 있는 실전 코드를 제공합니다. 특히 WebView에서 Custom Scheme을 사용하는 하이브리드 앱 개발자라면 반드시 알아야 할 내용들을 담았습니다.
목차
1. URISyntaxException 오류의 이해
2. 오류 발생 원인 분석
3. 해결 방법과 실전 코드
4. 예방 및 디버깅 방법
5. 자주 묻는 질문 (FAQ)

#1. URISyntaxException 오류의 이해
Android 앱에서 Intent를 통해 데이터를 전달할 때 발생하는 URISyntaxException은 URI 문법 규칙을 위반했을 때 발생하는 예외입니다. 특히 WebView를 활용한 하이브리드 앱에서 JavaScript와 네이티브 코드 간 통신 시 자주 마주치게 되는 문제입니다.
1) 오류 메시지 예시
실제 개발 환경에서 발생하는 오류 메시지를 살펴보면 다음과 같습니다.
// URISyntaxException 전체 스택 트레이스
java.net.URISyntaxException: Illegal character in query at index 50:
androidData://androidScheme?requestCode=8&userAgent=function trim() { [native code] }&data=VEMgU2FtcGxlIFRleHQg7JuQ66y4IOuplOyLnOyngOyeheuLiOuLpC4=

W/System.err: at java.net.URI$Parser.fail(URI.java:2892)
W/System.err: at java.net.URI$Parser.checkChars(URI.java:3065)
W/System.err: at java.net.URI$Parser.parseHierarchical(URI.java:3155)
W/System.err: at java.net.URI$Parser.parse(URI.java:3097)
W/System.err: at java.net.URI.<init>(URI.java:583)
W/System.err: at com.android.sample.h2a.shared.UniScheme.getA2UAllParams(UniScheme.java:58)
. . . . .
2) 오류의 의미 해석
이 오류 메시지에서 알 수 있는 핵심 정보는 다음과 같습니다.
(1) 오류 발생 위치
"Illegal character in query at index 50"은 URI의 쿼리 파라미터 부분 중 50번째 인덱스에서 허용되지 않는 문자가 발견되었다는 의미입니다.
(2) 문제가 되는 URI 구조
URI를 구성 요소별로 분석하면 다음과 같습니다.
구성 요소 설명
Scheme androidData Custom Scheme 정의
Host androidScheme 앱 식별자
Query Parameters requestCode, userAgent, data 전달할 데이터 파라미터
문제 위치 userAgent 값의 공백 function trim() { [native code] }
. . . . .
3) URI 문법 규칙
URI는 RFC 3986 표준을 따르며, 특정 문자들은 반드시 인코딩되어야 합니다. 특히 쿼리 파라미터에서 허용되지 않는 문자는 다음과 같습니다.
공백 문자 (Space) - 가장 흔한 오류 원인
② 중괄호 { } - JavaScript 코드 포함 시 자주 발생
③ 대괄호 [ ] - 배열 표현 시 주의 필요
④ 파이프 | - 구분자로 사용 시 인코딩 필요
⑤ 백슬래시 \ - 경로 구분자 사용 시 주의

#2. 오류 발생 원인 분석
URISyntaxException이 발생하는 주요 원인을 실제 사례를 통해 상세히 분석해보겠습니다.
1) 공백 문자 문제
가장 빈번하게 발생하는 원인은 URI 파라미터 값에 포함된 공백 문자입니다. 위의 오류 예시에서도 userAgent 값에 "function trim() { [native code] }" 문자열이 포함되어 있는데, 여기에 여러 개의 공백이 존재합니다.
(1) 문제가 되는 코드
// JavaScript에서 WebView로 데이터 전달
String userAgent = webView.getSettings().getUserAgentString();
String uri = "androidData://androidScheme?requestCode=8&userAgent=" + userAgent;

// userAgent 값: "Mozilla/5.0 (Linux; Android 10) ..." - 공백 포함!
URI intentUri = new URI(uri); // URISyntaxException 발생!
(2) 공백이 문제가 되는 이유
URI 스펙에서 공백은 %20 또는 + 기호로 인코딩되어야 합니다. 인코딩되지 않은 공백은 URI 파서가 파라미터의 끝으로 인식하거나 불법 문자로 처리합니다.
. . . . .
2) 특수문자와 예약 문자
공백 외에도 다양한 특수문자가 문제를 일으킬 수 있습니다.
(1) JavaScript 함수 문자열
JavaScript 환경에서 toString()을 호출하면 "function trim() { [native code] }" 같은 문자열이 반환되는데, 이는 공백, 중괄호, 대괄호를 모두 포함하여 매우 위험합니다.
// 잘못된 예시 - JavaScript
var data = {
    name: "홍길동",
    method: String.prototype.trim // 함수 객체를 그대로 전달
};

var uri = "androidData://androidScheme?data=" + JSON.stringify(data);
window.location.href = uri; // 오류 발생!
(2) 한글 및 다국어 문자
한글이나 이모지 같은 UTF-8 멀티바이트 문자도 반드시 URL 인코딩이 필요합니다.
문자 유형 원본 인코딩 후
한글 안녕하세요 %EC%95%88%EB%85%95%ED%95%98%EC%84%B8%EC%9A%94
공백 " " (Space) %20 또는 +
특수문자 { } [ ] %7B %7D %5B %5D
. . . . .
3) Base64 인코딩 시 주의사항
오류 예시의 data 파라미터를 보면 "VEMgU2FtcGxlIFRleHQg..." 같은 Base64 인코딩된 문자열을 볼 수 있습니다. Base64 인코딩을 사용하더라도 주의해야 할 점이 있습니다.
(1) Base64 URL Safe 인코딩
일반 Base64 인코딩은 '+', '/', '=' 문자를 사용하는데, 이것들이 URI에서 특수한 의미를 가질 수 있습니다. 따라서 Base64 URL Safe 인코딩을 사용하는 것이 안전합니다.
// 일반 Base64 인코딩 (문제 가능성 있음)
String encoded = Base64.encodeToString(data, Base64.DEFAULT);

// Base64 URL Safe 인코딩 (권장)
String encodedSafe = Base64.encodeToString(data, Base64.URL_SAFE | Base64.NO_WRAP);

// URL_SAFE 옵션:
// '+' → '-'로 변환
// '/' → '_'로 변환
// '=' 패딩 제거 (NO_WRAP와 함께 사용)
. . . . .
4) WebView와 네이티브 코드 간 통신 오류
하이브리드 앱에서는 WebView의 shouldOverrideUrlLoading() 메서드를 통해 Custom Scheme을 처리하는데, 여기서 URI 파싱 오류가 자주 발생합니다.
(1) 전형적인 오류 발생 패턴
// WebViewClient 구현
@Override
public boolean shouldOverrideUrlLoading(WebView view, String url) {
    // JavaScript에서 전달된 URL을 직접 파싱
    if (url.startsWith("androidData://")) {
        try {
            URI uri = new URI(url); // 여기서 오류 발생!
            String requestCode = getQueryParameter(uri, "requestCode");
            String data = getQueryParameter(uri, "data");
            // 데이터 처리...
        } catch (URISyntaxException e) {
            Log.e("WebView", "URI parsing error", e);
        }
        return true;
    }
    return false;
}

#3. 해결 방법과 실전 코드
URISyntaxException을 해결하는 다양한 방법을 실전 코드와 함께 살펴보겠습니다. 상황에 따라 적절한 방법을 선택하여 적용할 수 있습니다.
1) URL 인코딩 방법 (권장)
가장 표준적이고 안전한 방법은 URLEncoder를 사용하여 파라미터 값을 인코딩하는 것입니다.
(1) Java/Android 네이티브 코드
import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;

// 방법 1: URLEncoder 사용 (권장)
String userAgent = "Mozilla/5.0 (Linux; Android 10)";
String encodedUserAgent = URLEncoder.encode(userAgent, StandardCharsets.UTF_8.toString());

String uri = "androidData://androidScheme?requestCode=8&userAgent=" + encodedUserAgent;

// 결과: androidData://androidScheme?requestCode=8&userAgent=Mozilla%2F5.0+%28Linux%3B+Android+10%29

try {
    URI intentUri = new URI(uri); // 정상 동작!
    // URI 처리 로직...
} catch (URISyntaxException e) {
    e.printStackTrace();
}
(2) JavaScript에서 인코딩하여 전달
WebView에서 JavaScript로 데이터를 전달할 때는 JavaScript 단에서 미리 인코딩하는 것이 더욱 안전합니다.
// JavaScript 코드
function callNative(requestCode, data) {
    // encodeURIComponent로 각 파라미터 값 인코딩
    var encodedData = encodeURIComponent(data);
    var userAgent = encodeURIComponent(navigator.userAgent);
    
    var uri = "androidData://androidScheme?"
        + "requestCode=" + requestCode
        + "&userAgent=" + userAgent
        + "&data=" + encodedData;
    
    window.location.href = uri;
}

// 사용 예시
var testData = "테스트 데이터 with 공백 and special chars!@#";
callNative(8, testData);
. . . . .
2) 문자열 치환 방법 (간단한 경우)
공백만 문제가 되는 단순한 경우라면 replaceAll()을 사용하여 공백을 %20으로 치환할 수 있습니다.
(1) 공백 문자 치환
// 방법 2: replaceAll 사용 (공백만 처리하는 간단한 경우)
String url = "androidData://androidScheme?data=TC Sample Text 원문 데이터입니다.";

// 공백을 %20으로 치환
String encodedUrl = url.replaceAll(" ", "%20");

try {
    URI uri = new URI(encodedUrl);
    // URI 처리...
} catch (URISyntaxException e) {
    e.printStackTrace();
}
주의: 이 방법은 공백만 문제가 될 때 사용 가능하며, 다른 특수문자가 포함된 경우에는 URLEncoder를 사용하는 것이 안전합니다.
. . . . .
3) Base64 인코딩 방법 (복잡한 데이터)
한글, 이모지, 복잡한 JSON 구조 등 복잡한 데이터를 전달할 때는 Base64 인코딩을 사용하는 것이 가장 안전합니다.
(1) Android 네이티브 코드
import android.util.Base64;
import java.nio.charset.StandardCharsets;

// 방법 3: Base64 인코딩 (복잡한 데이터에 권장)
String originalData = "TC Sample Text 원문 데이터입니다. 한글 포함!";

// Base64 URL Safe 인코딩
String base64Data = Base64.encodeToString(
    originalData.getBytes(StandardCharsets.UTF_8),
    Base64.URL_SAFE | Base64.NO_WRAP
);

String uri = "androidData://androidScheme?requestCode=8&data=" + base64Data;

try {
    URI intentUri = new URI(uri); // 안전하게 파싱!
    
    // 데이터 디코딩
    String decodedData = new String(
        Base64.decode(base64Data, Base64.URL_SAFE),
        StandardCharsets.UTF_8
    );
    
    Log.d("URI", "Decoded data: " + decodedData);
} catch (URISyntaxException e) {
    e.printStackTrace();
}
(2) JavaScript에서 Base64 인코딩
// JavaScript에서 Base64 인코딩하여 전달
function encodeBase64(str) {
    // UTF-8 바이트 배열로 변환 후 Base64 인코딩
    return btoa(unescape(encodeURIComponent(str)));
}

function callNativeWithBase64(requestCode, data) {
    var encodedData = encodeBase64(data);
    
    var uri = "androidData://androidScheme?"
        + "requestCode=" + requestCode
        + "&data=" + encodedData;
    
    window.location.href = uri;
}

// 사용 예시
var complexData = {
    name: "홍길동",
    message: "안녕하세요! 특수문자 테스트 @#$%"
};
callNativeWithBase64(8, JSON.stringify(complexData));
. . . . .
4) 통합 솔루션 - WebView 통신 헬퍼 클래스
실전 프로젝트에서 재사용 가능한 헬퍼 클래스를 구현하면 오류를 체계적으로 방지할 수 있습니다.
(1) UriHelper 클래스 구현
public class UriHelper {
    
    /** URI를 안전하게 파싱합니다 */
    public static Uri parseSafely(String url) {
        try {
            // 공백 문자를 %20으로 치환
            String encodedUrl = url.replaceAll(" ", "%20");
            return Uri.parse(encodedUrl);
        } catch (Exception e) {
            Log.e("UriHelper", "URI parsing failed", e);
            return null;
        }
    }
    
    /** 쿼리 파라미터를 안전하게 추출합니다 */
    public static String getQueryParameter(Uri uri, String key) {
        if (uri == null) return null;
        
        try {
            String value = uri.getQueryParameter(key);
            return value != null ? URLDecoder.decode(value, "UTF-8") : null;
        } catch (Exception e) {
            Log.e("UriHelper", "Failed to get parameter: " + key, e);
            return null;
        }
    }
    
    /** Base64로 인코딩된 데이터를 디코딩합니다 */
    public static String decodeBase64(String encodedData) {
        try {
            byte[] decodedBytes = Base64.decode(encodedData, Base64.URL_SAFE);
            return new String(decodedBytes, StandardCharsets.UTF_8);
        } catch (Exception e) {
            Log.e("UriHelper", "Base64 decoding failed", e);
            return null;
        }
    }
}
(2) WebViewClient에서 활용
public class CustomWebViewClient extends WebViewClient {
    
    @Override
    public boolean shouldOverrideUrlLoading(WebView view, String url) {
        if (url.startsWith("androidData://")) {
            // UriHelper를 사용하여 안전하게 파싱
            Uri uri = UriHelper.parseSafely(url);
            
            if (uri != null) {
                String requestCode = UriHelper.getQueryParameter(uri, "requestCode");
                String encodedData = UriHelper.getQueryParameter(uri, "data");
                
                // Base64 디코딩
                String data = UriHelper.decodeBase64(encodedData);
                
                Log.d("WebView", "RequestCode: " + requestCode);
                Log.d("WebView", "Data: " + data);
                
                // 비즈니스 로직 처리...
            }
            
            return true;
        }
        
        return super.shouldOverrideUrlLoading(view, url);
    }
}

#4. 예방 및 디버깅 방법
URISyntaxException을 미리 방지하고, 발생 시 효과적으로 디버깅하는 방법을 알아보겠습니다.
1) 예방적 코딩 패턴
오류를 사전에 방지하기 위한 방어적 프로그래밍 패턴을 적용합니다.
(1) URI 빌더 패턴 사용
// Uri.Builder를 사용한 안전한 URI 생성
Uri.Builder builder = new Uri.Builder();
builder.scheme("androidData")
       .authority("androidScheme")
       .appendQueryParameter("requestCode", "8")
       .appendQueryParameter("userAgent", webView.getSettings().getUserAgentString())
       .appendQueryParameter("data", data);

Uri uri = builder.build();
String url = uri.toString();

// Uri.Builder가 자동으로 인코딩 처리!
Log.d("URI", "Safe URI: " + url);
(2) 입력 데이터 검증
/** URI에 사용하기 전 데이터 검증 */
public static boolean isValidForUri(String data) {
    if (data == null || data.isEmpty()) {
        return false;
    }
    
    // 위험한 문자 패턴 체크
    String dangerousChars = "[\\s\\{\\}\\[\\]\\|\\\\]";
    if (data.matches(".*" + dangerousChars + ".*")) {
        Log.w("URI", "Data contains unsafe characters, encoding required");
        return false;
    }
    
    return true;
}
. . . . .
2) 로깅 및 디버깅 전략
문제 발생 시 상세한 로그를 남겨 원인을 빠르게 파악할 수 있도록 합니다.
(1) 상세 로깅 구현
public static void logUriDetails(String url) {
    Log.d("URI_DEBUG", "========== URI Details ==========");
    Log.d("URI_DEBUG", "Original URL: " + url);
    
    // 각 문자의 16진수 코드 출력
    StringBuilder hexDump = new StringBuilder();
    for (int i = 0; i < url.length(); i++) {
        char c = url.charAt(i);
        hexDump.append(String.format("%02X ", (int)c));
        
        if ((i + 1) % 20 == 0) {
            Log.d("URI_DEBUG", "Hex: " + hexDump.toString());
            hexDump = new StringBuilder();
        }
    }
    
    if (hexDump.length() > 0) {
        Log.d("URI_DEBUG", "Hex: " + hexDump.toString());
    }
    
    Log.d("URI_DEBUG", "================================");
}
(2) Try-Catch 블록 세분화
public void handleCustomScheme(String url) {
    try {
        // 1단계: URL 전처리
        String processedUrl = preprocessUrl(url);
        Log.d("URI", "Preprocessed: " + processedUrl);
        
        // 2단계: URI 파싱
        Uri uri = Uri.parse(processedUrl);
        Log.d("URI", "Parsed successfully");
        
        // 3단계: 파라미터 추출
        String data = uri.getQueryParameter("data");
        Log.d("URI", "Data parameter: " + data);
        
        // 비즈니스 로직...
        
    } catch (IllegalArgumentException e) {
        Log.e("URI", "Invalid URI format", e);
        showErrorToUser("잘못된 URI 형식입니다");
    } catch (Exception e) {
        Log.e("URI", "Unexpected error", e);
        showErrorToUser("오류가 발생했습니다");
    }
}

private String preprocessUrl(String url) {
    // 공백 처리
    return url.replaceAll(" ", "%20");
}
. . . . .
3) 테스트 케이스 작성
다양한 엣지 케이스를 테스트하여 안정성을 확보합니다.
(1) 단위 테스트 예시
@Test
public void testUriWithSpaces() {
    String data = "TC Sample Text 원문 데이터입니다.";
    String encoded = URLEncoder.encode(data, StandardCharsets.UTF_8.toString());
    
    String uri = "androidData://androidScheme?data=" + encoded;
    
    Uri result = Uri.parse(uri);
    assertNotNull(result);
    assertEquals("androidData", result.getScheme());
}

@Test
public void testUriWithSpecialCharacters() {
    String data = "{ [native code] }";
    String encoded = URLEncoder.encode(data, StandardCharsets.UTF_8.toString());
    
    String uri = "androidData://androidScheme?data=" + encoded;
    
    Uri result = Uri.parse(uri);
    assertNotNull(result);
    
    String decoded = URLDecoder.decode(
        result.getQueryParameter("data"),
        StandardCharsets.UTF_8.toString()
    );
    assertEquals(data, decoded);
}
. . . . .
4) 프로덕션 환경 모니터링
실제 서비스 환경에서 발생하는 오류를 추적하기 위해 크래시 리포팅 도구를 활용합니다.
(1) Firebase Crashlytics 연동
import com.google.firebase.crashlytics.FirebaseCrashlytics;

public void handleCustomScheme(String url) {
    try {
        // URI 파싱 로직...
        Uri uri = UriHelper.parseSafely(url);
        
    } catch (Exception e) {
        // Crashlytics에 상세 정보 전송
        FirebaseCrashlytics crashlytics = FirebaseCrashlytics.getInstance();
        crashlytics.setCustomKey("uri_original", url);
        crashlytics.setCustomKey("uri_length", url.length());
        crashlytics.log("URI parsing failed: " + url);
        crashlytics.recordException(e);
        
        Log.e("URI", "Failed to parse URI", e);
    }
}

#5. 자주 묻는 질문 (FAQ)
1) Q: URLEncoder와 Uri.Builder 중 어떤 것을 사용해야 하나요?
Uri.Builder를 사용하는 것이 가장 권장됩니다. Uri.Builder는 Android 플랫폼에서 제공하는 공식 API로, 자동으로 인코딩을 처리하고 URI 구조를 안전하게 생성합니다. URLEncoder는 개별 파라미터 값을 수동으로 인코딩할 때 사용하며, 실수로 인한 오류 가능성이 있습니다. 다만 JavaScript에서 전달받은 URL을 처리할 때는 URLEncoder나 replaceAll()을 사용할 수밖에 없습니다.
. . . . .
2) Q: JavaScript에서 encodeURIComponent와 encodeURI의 차이는 무엇인가요?
encodeURIComponent를 사용해야 합니다. encodeURI는 전체 URI를 인코딩하기 위한 것으로, 콜론(:), 슬래시(/), 물음표(?) 등 URI 구조에 필요한 문자는 인코딩하지 않습니다. 반면 encodeURIComponent는 파라미터 값을 인코딩하기 위한 것으로, 거의 모든 특수문자를 인코딩합니다. URI 쿼리 파라미터의 값을 인코딩할 때는 반드시 encodeURIComponent를 사용하세요.
함수 용도 인코딩 범위
encodeURI() 전체 URI 인코딩 알파벳, 숫자, URI 구조 문자(: / ? #) 제외
encodeURIComponent() 파라미터 값 인코딩 알파벳, 숫자, 일부 기호(- _ . ! ~ * ' ( ))만 제외
. . . . .
3) Q: Base64 인코딩을 사용하면 데이터 크기가 커지지 않나요?
네, Base64 인코딩은 원본 데이터 크기의 약 133% (4/3배) 크기로 증가합니다. 그러나 복잡한 데이터를 안전하게 전달하고 디버깅을 쉽게 하는 장점이 있습니다. 만약 데이터 크기가 중요한 경우 URL 인코딩을 사용하거나, 대용량 데이터는 다른 방식(파일 저장, 서버 통신 등)으로 전달하는 것을 고려하세요. 일반적으로 수 KB 이내의 데이터라면 Base64를 사용해도 성능에 큰 영향이 없습니다.
. . . . .
4) Q: 한글 데이터가 깨지는 문제는 어떻게 해결하나요?
한글 인코딩 문제는 반드시 UTF-8 인코딩을 명시하여 해결할 수 있습니다. URLEncoder.encode() 사용 시 두 번째 파라미터로 "UTF-8" 또는 StandardCharsets.UTF_8을 지정하고, 디코딩할 때도 동일한 인코딩을 사용해야 합니다. JavaScript에서는 encodeURIComponent가 자동으로 UTF-8을 사용하므로 별도 처리가 필요 없습니다.
// 올바른 한글 인코딩 방법
String koreanText = "안녕하세요";

// 인코딩 시 UTF-8 명시
String encoded = URLEncoder.encode(koreanText, StandardCharsets.UTF_8.toString());

// 디코딩 시에도 UTF-8 명시
String decoded = URLDecoder.decode(encoded, StandardCharsets.UTF_8.toString());
. . . . .
5) Q: Intent.parseUri()와 Uri.parse()의 차이는 무엇인가요?
Intent.parseUri()는 Intent 객체를 생성하고, Uri.parse()는 Uri 객체만 생성합니다. Intent.parseUri()는 URI 문자열에서 액션, 카테고리, 데이터 등 Intent의 모든 구성 요소를 추출하여 Intent 객체를 만듭니다. 반면 Uri.parse()는 단순히 URI 문자열을 파싱하여 스키마, 호스트, 쿼리 파라미터 등에 접근할 수 있게 합니다. WebView에서 Custom Scheme을 처리할 때는 일반적으로 Uri.parse()를 사용하고, 명시적으로 다른 앱을 실행할 때는 Intent.parseUri()를 사용합니다.
. . . . .
6) Q: 이미 배포된 앱에서 JavaScript 코드를 수정할 수 없는데 어떻게 해야 하나요?
네이티브 Android 코드에서 방어적으로 처리할 수 있습니다. shouldOverrideUrlLoading() 메서드에서 URL을 받았을 때, UriHelper 클래스의 parseSafely() 메서드처럼 공백을 %20으로 치환하거나, try-catch 블록으로 예외를 처리하여 앱이 크래시되지 않도록 합니다. 또한 WebView의 addJavascriptInterface()를 사용하여 새로운 인터페이스를 추가하고, 업데이트된 웹 페이지에서는 새 인터페이스를 사용하도록 유도할 수 있습니다.
. . . . .
7) Q: 디버그 빌드에서는 문제없는데 릴리즈 빌드에서만 오류가 발생합니다.
이는 ProGuard 또는 R8 난독화 설정 때문일 가능성이 높습니다. URI 처리와 관련된 클래스나 메서드가 난독화되면서 리플렉션을 사용하는 부분에서 오류가 발생할 수 있습니다. proguard-rules.pro 파일에 다음과 같이 keep 규칙을 추가하세요.
# URI 처리 클래스 keep
-keep class com.android.sample.h2a.shared.** { *; }

# WebView JavascriptInterface keep
-keepclassmembers class * {
    @android.webkit.JavascriptInterface <methods>;
}

# URI 관련 안드로이드 기본 클래스 keep
-keep class android.net.Uri { *; }
. . . . .
8) Q: 성능을 위해 인코딩을 생략할 수 있나요?
절대 생략하면 안 됩니다. URL 인코딩의 성능 오버헤드는 거의 무시할 수 있는 수준이며, 대부분의 경우 밀리초 단위입니다. 반면 인코딩을 생략하면 URISyntaxException으로 인한 앱 크래시, 데이터 손실, 보안 취약점 등 심각한 문제가 발생할 수 있습니다. 특히 사용자 입력 데이터를 처리할 때는 반드시 인코딩해야 합니다. 성능이 정말 중요하다면 인코딩 방법(URLEncoder vs Base64)을 선택하는 것으로 최적화하되, 인코딩 자체를 생략해서는 안 됩니다.
. . . . .
9) Q: iOS에서도 같은 문제가 발생하나요?
네, iOS의 WKWebView에서도 유사한 문제가 발생합니다. Swift에서 URL을 다룰 때도 addingPercentEncoding(withAllowedCharacters:) 메서드를 사용하여 인코딩해야 하며, JavaScript 측의 처리 방법은 Android와 동일합니다. 하이브리드 앱을 개발한다면 Android와 iOS 양쪽 모두에서 동일한 인코딩 정책을 적용하는 것이 중요합니다.
. . . . .
10) Q: Custom Scheme 대신 다른 통신 방법은 없나요?
네, 여러 대안이 있습니다. addJavascriptInterface()를 사용하면 JavaScript에서 직접 네이티브 메서드를 호출할 수 있어 URI 인코딩 문제를 피할 수 있습니다. 또한 postMessage API를 활용하거나, WebViewClient의 onPageFinished()에서 evaluateJavascript()로 양방향 통신을 구현할 수도 있습니다. 각 방법마다 장단점이 있으므로 프로젝트 요구사항에 맞는 방법을 선택하세요.
통신 방법 장점 단점
Custom Scheme 간단한 구현, 외부 브라우저 지원 URL 인코딩 필요, 데이터 크기 제한
JavascriptInterface 직접 메서드 호출, 인코딩 불필요 보안 주의 필요, WebView 전용
postMessage 표준 API, 안전한 통신 Android 4.4+ 필요, 복잡한 구현

마무리
Android Intent URI 오류는 WebView와 네이티브 코드 간 통신에서 자주 발생하는 문제이지만, 원인을 이해하고 적절한 해결 방법을 적용하면 충분히 예방할 수 있습니다.
이번 포스팅에서 다룬 핵심 내용을 정리하면 다음과 같습니다.
URISyntaxException의 주요 원인은 URI에 포함된 공백 및 특수문자입니다.
URLEncoder.encode() 또는 Uri.Builder를 사용하여 안전하게 인코딩하세요.
복잡한 데이터는 Base64 URL Safe 인코딩을 활용하면 가장 안전합니다.
JavaScript 측에서도 encodeURIComponent()로 미리 인코딩하는 것이 좋습니다.
방어적 프로그래밍과 상세한 로깅으로 오류를 예방하고 빠르게 대응할 수 있습니다.
특히 하이브리드 앱 개발 시에는 네이티브와 웹 양쪽 모두에서 일관된 인코딩 정책을 적용하는 것이 중요합니다. 또한 개발 단계에서 다양한 테스트 케이스를 작성하여 엣지 케이스를 미리 검증하면 프로덕션 환경에서의 오류를 크게 줄일 수 있습니다.
URI 인코딩은 번거로워 보일 수 있지만, 앱의 안정성과 사용자 경험을 위해 반드시 필요한 과정입니다. 이번 가이드가 여러분의 Android 앱 개발에 도움이 되기를 바랍니다.
긴 글 읽어주셔서 감사합니다.

끝.
반응형