안드로이드 WebView net::ERR_CACHE_MISS 에러, 원인부터 완벽 해결까지
TL;DR — 안드로이드 WebView에서 자주 마주치는 net::ERR_CACHE_MISS 에러의 근본 원인과 인터넷 권한, 캐시 모드 설정까지 단계별 해결법을 코드 예제와 함께 정리했습니다.
앱 안에 웹 페이지를 띄우려고 WebView를 붙였는데, 화면에 콘텐츠 대신 net::ERR_CACHE_MISS라는 빨간 에러 메시지가 뜬 경험이 한 번쯤 있을 것입니다. 분명 URL은 정확하고 브라우저에서는 잘 열리는데 앱 안에서만 막히니 당황스럽습니다. 이 글에서는 이 에러가 왜 생기는지, 그리고 어떤 순서로 점검하면 가장 빨리 해결할 수 있는지를 실무 관점에서 정리합니다.
1. 어떤 상황에서 이 에러가 나타나는가
net::ERR_CACHE_MISS는 보통 다음과 같은 상황에서 튀어나옵니다.
- 새로 만든 앱에 처음
WebView를 붙여 페이지를 로드할 때 - 오프라인 우선 정책(
LOAD_CACHE_ELSE_NETWORK등)으로 캐시를 강제했는데 정작 캐시가 비어 있을 때 POST요청으로 폼을 전송한 뒤 뒤로 가기를 눌렀을 때
즉, 에러 이름 그대로 "캐시에서 가져오려 했는데 캐시가 없다(miss)"는 신호입니다. 그런데 실제 현장에서 가장 흔한 원인은 캐시 정책이 아니라 의외로 단순합니다. 바로 앱이 인터넷에 나갈 권한 자체를 못 받은 경우입니다.
2. ERR_CACHE_MISS의 정체와 근본 원인
WebView 내부에는 크롬과 동일한 네트워크 스택이 들어 있습니다. 페이지를 요청하면 WebView는 먼저 "이걸 네트워크로 가져올까, 캐시에서 가져올까"를 캐시 모드에 따라 판단합니다. 이때 두 가지 갈래에서 문제가 생깁니다.
- 권한 부재: 앱에
INTERNET권한이 없으면 네트워크 요청 자체가 차단됩니다. 그러면 WebView는 어쩔 수 없이 캐시로 눈을 돌리는데, 첫 실행이라 캐시도 비어 있으니ERR_CACHE_MISS로 귀결됩니다. 원인은 캐시지만 진짜 범인은 권한인 셈입니다. - 캐시 모드 강제: 개발자가 의도적으로 캐시 우선 모드를 켜 둔 상태에서 네트워크가 끊겼거나 캐시가 비어 있으면 동일한 에러가 납니다.
따라서 해결도 이 두 갈래를 순서대로 점검하는 것이 가장 효율적입니다.
3. 1순위 해결: 인터넷 권한 추가
가장 먼저 확인할 것은 AndroidManifest.xml의 인터넷 권한입니다. <application> 태그 바깥, <manifest> 바로 아래에 다음 한 줄을 넣어 줍니다.
<!-- AndroidManifest.xml -->
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.example.myviewer">
<!-- 인터넷 접근 권한: 이 한 줄이 없으면 WebView가 네트워크에 못 나간다 -->
<uses-permission android:name="android.permission.INTERNET" />
<!-- 네트워크 상태 확인이 필요하면 함께 선언 -->
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<application
android:label="MyViewer"
android:theme="@style/Theme.MyViewer">
<!-- 액티비티 선언 -->
</application>
</manifest>
INTERNET은 위험 권한(runtime permission)이 아니라 일반 권한이므로, 매니페스트에 선언만 하면 별도의 런타임 요청 코드 없이 바로 적용됩니다. 대부분의 ERR_CACHE_MISS 사례는 이 한 줄로 끝납니다. 권한을 추가했다면 앱을 완전히 종료 후 다시 빌드해 보는 것이 좋습니다.
4. 2순위 해결: WebView 캐시 모드 조정
권한을 넣었는데도 같은 에러가 난다면 캐시 모드 설정을 의심해야 합니다. 코드 어딘가에서 캐시를 강제하고 있을 수 있습니다. 가장 안전한 기본값은 LOAD_DEFAULT이며, 네트워크를 항상 우선하고 싶다면 LOAD_NO_CACHE로 명시합니다.
코틀린 예제는 다음과 같습니다.
// WebView를 초기화하는 함수
private fun setupWebView(viewer: WebView, targetUrl: String) {
viewer.settings.apply {
javaScriptEnabled = true // 페이지가 JS를 쓰면 활성화
domStorageEnabled = true // localStorage 사용 페이지 대응
// 캐시 모드: 기본 동작으로 두면 캐시 miss로 막히지 않는다
cacheMode = WebSettings.LOAD_DEFAULT
// 네트워크를 무조건 우선시키려면 아래로 교체
// cacheMode = WebSettings.LOAD_NO_CACHE
}
// 외부 브라우저로 튕기지 않고 앱 내부에서 계속 로드
viewer.webViewClient = WebViewClient()
viewer.loadUrl(targetUrl)
}
자바로 작성한다면 동일한 흐름이 다음과 같이 됩니다.
// 자바 버전 WebView 초기화
private void setupWebView(WebView viewer, String targetUrl) {
WebSettings config = viewer.getSettings();
config.setJavaScriptEnabled(true);
config.setDomStorageEnabled(true);
// 캐시가 비어 ERR_CACHE_MISS가 나면 기본 모드로 되돌린다
config.setCacheMode(WebSettings.LOAD_DEFAULT);
viewer.setWebViewClient(new WebViewClient());
viewer.loadUrl(targetUrl);
}
핵심은 LOAD_CACHE_ELSE_NETWORK나 LOAD_CACHE_ONLY 같은 캐시 강제 모드를 무의식적으로 켜 두지 않는 것입니다. 오프라인 지원이 꼭 필요한 게 아니라면 LOAD_DEFAULT가 가장 무난합니다.
5. 그 밖의 점검 포인트
권한과 캐시를 손봤는데도 화면이 비어 있다면 다음을 차례로 확인합니다.
- HTTP 평문 차단: 안드로이드 9(API 28)부터는 보안상 평문 HTTP 트래픽이 기본 차단됩니다. 불러오려는 페이지가
http://라면 빈 화면이나 다른 에러가 날 수 있습니다. 가능하면https://로 바꾸고, 불가피하게 HTTP가 필요하면 네트워크 보안 설정 파일로 특정 도메인만 허용합니다.
<!-- res/xml/network_security_config.xml -->
<network-security-config>
<domain-config cleartextTrafficPermitted="true">
<domain includeSubdomains="true">legacy.example.com</domain>
</domain-config>
</network-security-config>
그리고 매니페스트의 <application>에 android:networkSecurityConfig="@xml/network_security_config"를 연결합니다.
- 혼합 콘텐츠: HTTPS 페이지 안에서 HTTP 리소스를 불러오면 막힐 수 있습니다. 필요 시
mixedContentMode를 조정합니다. - 네트워크 연결 상태: 에뮬레이터나 기기의 데이터/와이파이가 실제로 연결돼 있는지 확인합니다. VPN, 사내 프록시 환경에서도 캐시 miss가 유발될 수 있습니다.
6. 흔한 실수와 엣지 케이스
- 권한을
<application>안쪽에 넣는 실수:uses-permission은 반드시<manifest>직계 자식이어야 합니다. 위치가 틀리면 빌드 경고와 함께 무시됩니다. - 권한 추가 후 재빌드 누락: 매니페스트 변경은 핫 리로드로 반영되지 않을 때가 많습니다. 클린 빌드 후 다시 설치하세요.
webViewClient를 안 붙임: 이걸 설정하지 않으면 일부 페이지가 기본 브라우저로 빠져나가거나 리다이렉트 처리가 꼬일 수 있습니다.- POST 후 뒤로 가기: 폼 전송 직후 뒤로 가기를 누르면 캐시에 없는 응답을 다시 보여주려다 에러가 날 수 있습니다. 이 경우 페이지를 다시 로드하도록 처리하는 편이 안전합니다.
7. 요약
net::ERR_CACHE_MISS는 이름과 달리 캐시 문제가 아니라 인터넷 권한 누락이 가장 흔한 원인입니다. 점검 순서는 다음과 같이 기억해 두면 됩니다.
AndroidManifest.xml에INTERNET권한이 있는가- 캐시 모드를 강제하고 있지는 않은가 (
LOAD_DEFAULT권장) - HTTP 평문/혼합 콘텐츠/네트워크 연결 문제는 없는가
대부분은 1번에서 해결되고, 나머지는 보조 점검 항목입니다.
AI에게 물어볼 때 (프롬프트 팁)
이런 안드로이드 에러를 ChatGPT나 Claude에게 물을 때는, 단순히 "에러 고쳐줘"보다 맥락을 구조화해서 주면 정확도가 크게 올라갑니다. Prompt Architect가 권장하는 방식으로 예시를 들면 다음과 같습니다.
역할: 너는 안드로이드 WebView 디버깅 전문가다.
상황: minSdk 24, targetSdk 34 앱에서 WebView로 https 페이지를 로드하면
net::ERR_CACHE_MISS가 뜬다.
제공: 현재 AndroidManifest.xml 권한 목록과 WebView 초기화 코드를 첨부한다.
요청: 원인 후보를 가능성 높은 순으로 나열하고, 각 후보별 검증 방법과
수정 코드(코틀린)를 제시해줘.
내 WebView 설정 코드를 붙여넣을 테니, 캐시 모드와 권한 관점에서
ERR_CACHE_MISS를 유발할 수 있는 부분을 모두 찾아 표로 정리해줘.
각 항목에 (위험도 / 이유 / 수정안)을 포함해줘.
안드로이드 9 이상에서 HTTP 평문 차단으로 생기는 WebView 로딩 실패와
ERR_CACHE_MISS를 어떻게 구분하지? 두 에러의 증상 차이와
각각의 진단 체크리스트를 만들어줘.
역할, 상황, 제공 정보, 요청 형식을 분리해 주면 AI가 추측 대신 검증 가능한 답을 내놓습니다. 더 정교한 프롬프트 설계가 궁금하다면 Prompt Architect의 분석기로 자신의 질문을 점검해 보세요.