Android WebView JavaScript 통신 완벽 가이드 — evaluateJavascript, addJavascriptInterface, 로그인 상태 감지와 보안

Android 앱에서 웹페이지를 표시하거나 웹서비스와 연동해야 할 때 가장 많이 사용하는 컴포넌트 중 하나가 WebView입니다.

단순히 웹페이지를 보여주는 기능이라면 구현이 어렵지 않습니다. 하지만 WebView 내부의 데이터를 가져오거나, JavaScript를 실행하거나, 로그인 상태를 확인하려면 고려해야 할 사항이 많아집니다.

특히 다음과 같은 문제가 자주 발생합니다.

  • evaluateJavascript()를 호출했는데 결과가 비어 있음

  • onPageFinished()가 호출되었지만 실제 화면은 아직 로딩 중임

  • JavaScript에서 Android 함수를 호출하지 못함

  • 로그인 후 페이지가 변경되었는데 상태를 감지하지 못함

  • 여러 WebView를 사용하면서 쿠키와 세션 관리가 복잡해짐

  • JavaScript 결과를 처리하는 동안 UI가 느려짐

  • 외부 웹페이지에서 JavaScript 인터페이스에 접근하는 보안 문제

이번 글에서는 Kotlin + XML 기반 Android 앱에서 WebViewClient, WebChromeClient, evaluateJavascript(), addJavascriptInterface()를 활용하는 방법을 정리하겠습니다.

또한 로그인 상태 감지와 다중 WebView 운영 시 주의사항도 함께 살펴보겠습니다.

작성 기준: 2026년 10월


1. Android WebView의 기본 구조

WebView는 Android 앱 내부에서 웹 콘텐츠를 표시하는 컴포넌트입니다.

일반적인 웹 브라우저처럼 HTML, CSS, JavaScript를 처리할 수 있으며 Android 코드와 JavaScript 사이의 통신 기능도 제공합니다.

기본 구조는 다음과 같습니다.

Android Activity / Fragment
          │
          ▼
        WebView
          │
          ├── WebViewClient
          │      ├── 페이지 이동 감지
          │      └── 로딩 완료 감지
          │
          ├── WebChromeClient
          │      ├── JavaScript 콘솔
          │      └── 웹 UI 관련 이벤트
          │
          ├── evaluateJavascript()
          │      └── Android → JavaScript
          │
          └── addJavascriptInterface()
                 └── JavaScript → Android

각 컴포넌트는 서로 다른 역할을 담당합니다.

기능주요 API
웹페이지 표시loadUrl()
페이지 이동 감지WebViewClient
페이지 로딩 완료onPageFinished()
JavaScript 콘솔 확인WebChromeClient
Android에서 JS 실행evaluateJavascript()
JS에서 Android 호출addJavascriptInterface()
쿠키 관리CookieManager

2. WebView XML 레이아웃 구성

먼저 XML에 WebView를 추가합니다.

res/layout/activity_web.xml

<?xml version="1.0" encoding="utf-8"?>
<FrameLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <WebView
        android:id="@+id/webView"
        android:layout_width="match_parent"
        android:layout_height="match_parent" />

</FrameLayout>

인터넷에 접근하려면 AndroidManifest.xml에 권한이 필요합니다.

<uses-permission
    android:name="android.permission.INTERNET" />

일반적인 HTTPS 페이지를 사용하는 경우 별도의 HTTP 허용 설정은 필요하지 않습니다.

보안상 특별한 이유가 없다면 HTTP 평문 통신을 허용하지 않는 것이 좋습니다.

3. Kotlin에서 WebView 초기화하기

다음은 WebView의 기본 설정입니다.

class WebActivity : AppCompatActivity() {

    private lateinit var webView: WebView

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_web)

        webView = findViewById(R.id.webView)

        setupWebView()

        webView.loadUrl("https://example.com")
    }

    private fun setupWebView() {
        webView.settings.apply {
            javaScriptEnabled = true
            domStorageEnabled = true
        }

        webView.webViewClient = WebViewClient()
        webView.webChromeClient = WebChromeClient()
    }
}

javaScriptEnabled는 JavaScript 실행을 허용하는 설정입니다.

WebView에서는 기본적으로 JavaScript가 비활성화되어 있으므로 필요한 경우에만 활성화해야 합니다.

domStorageEnabled는 웹페이지에서 DOM Storage를 사용할 수 있도록 설정합니다.

다만 JavaScript와 DOM Storage를 활성화한다고 해서 모든 웹사이트가 정상적으로 동작하는 것은 아닙니다.

사이트의 인증 방식, 쿠키 정책, 보안 헤더, 브라우저 환경 검사 등에 따라 동작이 달라질 수 있습니다.

4. WebViewClient로 페이지 이동 감지하기

WebViewClient는 페이지 로딩과 이동 상태를 확인할 때 사용합니다.

webView.webViewClient = object : WebViewClient() {

    override fun onPageStarted(
        view: WebView,
        url: String?,
        favicon: Bitmap?
    ) {
        super.onPageStarted(view, url, favicon)

        Log.d("WebView", "Page started: $url")
    }

    override fun onPageFinished(
        view: WebView,
        url: String?
    ) {
        super.onPageFinished(view, url)

        Log.d("WebView", "Page finished: $url")
    }
}

onPageStarted()는 메인 프레임의 페이지 로딩이 시작될 때 호출됩니다.

onPageFinished()는 메인 프레임의 페이지 로딩이 완료되었을 때 호출됩니다.

하지만 여기서 중요한 점이 있습니다.

onPageFinished()가 호출되었다고 해서 JavaScript로 생성되는 모든 콘텐츠까지 준비되었다는 의미는 아닙니다.

React, Vue 등으로 구성된 웹사이트에서는 초기 문서 로딩이 끝난 후에도 JavaScript가 데이터를 가져와 화면을 구성할 수 있습니다.

따라서 동적으로 생성되는 요소를 읽으려면 별도의 준비 상태 확인이 필요할 수 있습니다.

5. evaluateJavascript()로 JavaScript 실행하기

Android에서 WebView 내부의 JavaScript를 실행하려면 evaluateJavascript()를 사용합니다.

예를 들어 현재 페이지의 제목을 가져오겠습니다.

webView.evaluateJavascript(
    "document.title"
) { result ->
    Log.d("WebView", "Title: $result")
}

JavaScript의 실행 결과는 콜백으로 전달됩니다.

여기서 반환값은 JavaScript 결과를 JSON 형태로 표현한 문자열입니다.

예를 들어 실제 페이지 제목이 다음과 같다고 가정하겠습니다.

My Website

Android 콜백에는 다음과 같은 값이 전달될 수 있습니다.

"My Website"

문자열을 감싸는 따옴표도 결과에 포함됩니다.

따라서 결과를 단순히 replace("\"", "")로 처리하기보다 JSON 파싱을 사용하는 것이 안전합니다.

webView.evaluateJavascript(
    "document.title"
) { result ->

    val title = try {
        JSONArray("[$result]").getString(0)
    } catch (e: JSONException) {
        Log.e("WebView", "Invalid JS result", e)
        null
    }

    Log.d("WebView", "Parsed title: $title")
}

이 예시는 JSON 문자열 결과를 처리하는 방법입니다.

실제 JavaScript 결과가 null, 숫자, 객체 또는 배열인 경우에는 반환 형식에 맞춰 처리해야 합니다.

6. HTML 요소의 텍스트 가져오기

웹페이지의 특정 요소에서 텍스트를 가져오려면 document.querySelector()를 사용할 수 있습니다.

예를 들어 다음 HTML이 있다고 가정하겠습니다.

<div class="user-name">홍길동</div>

Android에서는 다음과 같이 실행할 수 있습니다.

val script = """
    (function() {
        const element = document.querySelector('.user-name');
        return element ? element.textContent.trim() : null;
    })();
""".trimIndent()

webView.evaluateJavascript(script) { result ->
    Log.d("WebView", "User name: $result")
}

이 방식은 특정 CSS 선택자에 해당하는 요소를 읽을 때 사용할 수 있습니다.

하지만 실제 웹사이트의 HTML 구조는 언제든 변경될 수 있습니다.

따라서 외부 사이트의 DOM 구조에 의존하는 기능은 유지보수 비용이 높아질 수 있습니다.

또한 다른 출처의 iframe 내부 콘텐츠는 동일 출처 정책에 따라 접근이 제한될 수 있습니다.

7. JavaScript 결과를 JSON 객체로 전달하기

여러 값을 가져와야 한다면 각각 evaluateJavascript()를 호출하기보다 객체 형태로 반환하는 것이 편리할 수 있습니다.

val script = """
    (function() {
        return JSON.stringify({
            title: document.title,
            url: location.href,
            readyState: document.readyState
        });
    })();
""".trimIndent()

webView.evaluateJavascript(script) { result ->
    Log.d("WebView", result)
}

여기서 주의할 점은 JSON.stringify()가 반환한 문자열이 다시 JavaScript 실행 결과의 JSON 문자열로 인코딩된다는 것입니다.

따라서 Android에서는 먼저 외부 JSON 문자열을 디코딩한 후 내부 JSON 객체를 파싱해야 합니다.

webView.evaluateJavascript(script) { result ->
    try {
        val jsonText = JSONArray("[$result]").getString(0)
        val json = JSONObject(jsonText)

        val title = json.optString("title")
        val url = json.optString("url")
        val readyState = json.optString("readyState")

        Log.d("WebView", "Title: $title")
        Log.d("WebView", "URL: $url")
        Log.d("WebView", "State: $readyState")

    } catch (e: JSONException) {
        Log.e("WebView", "JSON parsing failed", e)
    }
}

이렇게 하면 JavaScript에서 여러 값을 하나의 결과로 전달할 수 있습니다.

8. JavaScript에서 Android 함수 호출하기

지금까지는 Android에서 JavaScript를 실행하는 방법을 살펴봤습니다.

반대로 JavaScript에서 Android 함수를 호출해야 하는 경우도 있습니다.

이때 사용하는 기능이 addJavascriptInterface()입니다.

먼저 JavaScript 인터페이스 클래스를 작성합니다.

class AndroidBridge(
    private val onMessage: (String) -> Unit
) {

    @JavascriptInterface
    fun sendMessage(message: String) {
        onMessage(message)
    }
}

WebView에 인터페이스를 등록합니다.

webView.addJavascriptInterface(
    AndroidBridge { message ->
        runOnUiThread {
            Log.d("WebView", "JS message: $message")
        }
    },
    "AndroidBridge"
)

웹페이지의 JavaScript에서는 다음과 같이 호출할 수 있습니다.

window.AndroidBridge.sendMessage("Hello Android");

이렇게 하면 JavaScript에서 Android 코드로 문자열을 전달할 수 있습니다.

단, JavaScript 인터페이스 메서드는 별도의 스레드에서 호출되므로 UI를 직접 조작하지 않아야 합니다.

UI를 갱신해야 한다면 메인 스레드로 전환해야 합니다.

9. addJavascriptInterface의 보안 문제

addJavascriptInterface()는 편리하지만 보안상 주의해야 하는 기능입니다.

특히 신뢰할 수 없는 웹페이지에 인터페이스를 등록하면 해당 페이지의 JavaScript가 Android에 노출된 메서드를 호출할 수 있습니다.

더 중요한 점은 인터페이스가 페이지의 프레임에도 노출될 수 있으며, Android 측에서는 호출한 프레임의 출처를 신뢰성 있게 확인하기 어렵다는 것입니다.

따라서 다음과 같은 기능을 무분별하게 노출해서는 안 됩니다.

  • 임의 파일 읽기 또는 쓰기

  • 사용자 인증 정보 반환

  • 민감한 데이터베이스 조회

  • 외부 앱 실행

  • 임의 URL 요청

  • 시스템 설정 변경

특히 여러 외부 사이트를 로딩하는 WebView에 동일한 JavaScript 인터페이스를 등록하는 구조는 위험할 수 있습니다.

가능하면 직접 관리하는 신뢰할 수 있는 웹페이지에서만 사용하고, 노출하는 기능도 최소화해야 합니다.

외부 웹사이트를 수집하거나 탐색하는 용도라면 JavaScript 인터페이스 대신 evaluateJavascript()를 사용하는 방식이 더 적절할 수 있습니다.

다만 evaluateJavascript() 역시 실행하는 스크립트와 결과를 안전하게 처리해야 합니다.

10. 로그인 상태 감지하기

WebView에서 특정 사이트의 로그인 여부를 확인해야 하는 경우가 있습니다.

로그인 상태는 사이트마다 구현 방식이 다릅니다.

예를 들어 다음과 같은 방법을 사용할 수 있습니다.

  • 로그인 후 URL 변경 확인

  • 로그아웃 버튼 존재 여부 확인

  • 사용자 프로필 요소 존재 여부 확인

  • 쿠키 존재 여부 확인

  • 공식 인증 API를 통한 세션 상태 확인

여기서 가장 중요한 점은 단일 요소나 쿠키 하나만으로 로그인 상태를 확정하기 어렵다는 것입니다.

예를 들어 로그아웃 버튼이 존재한다고 해서 서버 세션이 반드시 유효하다고 보장할 수는 없습니다.

가능하다면 해당 서비스에서 제공하는 공식 인증 API나 세션 확인 기능을 사용하는 것이 가장 안정적입니다.

11. DOM 요소로 로그인 상태 확인하기

직접 관리하는 웹사이트처럼 HTML 구조를 알고 있는 경우에는 DOM 요소를 기준으로 로그인 상태를 확인할 수 있습니다.

예를 들어 로그인한 사용자에게만 다음 요소가 표시된다고 가정하겠습니다.

<button class="logout-button">로그아웃</button>

Android에서는 다음과 같이 확인할 수 있습니다.

val script = """
    (function() {
        return document.querySelector('.logout-button') !== null;
    })();
""".trimIndent()

webView.evaluateJavascript(script) { result ->
    val hasLogoutButton = result == "true"

    Log.d(
        "WebView",
        "Logout button exists: $hasLogoutButton"
    )
}

이 방식은 구현이 간단하지만 사이트의 HTML 구조가 변경되면 동작하지 않을 수 있습니다.

또한 버튼이 존재한다는 사실은 UI 상태에 대한 관찰 결과일 뿐, 서버 인증 상태에 대한 확정적인 검증은 아닙니다.

12. 쿠키를 이용한 로그인 상태 확인

WebView의 쿠키는 CookieManager를 통해 확인할 수 있습니다.

val cookieManager = CookieManager.getInstance()

val cookies = cookieManager.getCookie(
    "https://example.com"
)

Log.d("WebView", "Cookies exist: ${cookies != null}")

쿠키 존재 여부를 확인하는 것은 로그인 상태를 추정하는 데 도움이 될 수 있습니다.

하지만 주의할 점이 있습니다.

로그인하지 않은 사용자에게도 쿠키가 존재할 수 있습니다.

또한 로그인 세션이 만료되었더라도 쿠키가 남아 있을 수 있습니다.

따라서 쿠키 존재 여부만으로 로그인 성공을 판단하는 것은 권장하지 않습니다.

특히 세션 쿠키나 인증 토큰의 실제 값을 Logcat에 출력하지 않아야 합니다.

인증 관련 정보는 민감한 데이터이므로 저장, 로깅, 전송 과정에서 별도의 보안 관리가 필요합니다.

13. 여러 WebView에서 쿠키 공유하기

하나의 앱에서 여러 WebView를 사용하는 경우가 있습니다.

예를 들어 다음과 같은 구조입니다.

MainActivity
    │
    └── ViewPager2
           │
           ├── WebView A
           ├── WebView B
           └── WebView C

일반적인 Android WebView는 같은 앱의 동일한 WebView 데이터 디렉터리를 사용하는 경우 쿠키 저장소를 공유합니다.

따라서 WebView A에서 특정 사이트에 로그인하면 WebView B에서도 해당 사이트의 쿠키를 사용할 수 있습니다.

하지만 서로 다른 사이트의 로그인 세션이 자동으로 공유되는 것은 아닙니다.

또한 사이트별 쿠키 정책과 인증 방식에 따라 실제 동작이 달라질 수 있습니다.

쿠키 허용 설정

val cookieManager = CookieManager.getInstance()

cookieManager.setAcceptCookie(true)

서드파티 쿠키는 WebView별로 설정할 수 있습니다.

cookieManager.setAcceptThirdPartyCookies(
    webView,
    false
)

서드파티 쿠키를 허용해야 하는 인증 흐름도 있을 수 있지만, 보안과 개인정보 보호를 고려해 필요한 경우에만 허용하는 것이 좋습니다.

14. onPageFinished 이후 JavaScript 실행 시 주의사항

다음과 같은 코드는 흔히 사용됩니다.

override fun onPageFinished(
    view: WebView,
    url: String?
) {
    super.onPageFinished(view, url)

    view.evaluateJavascript(
        "document.querySelector('.user-name')?.textContent"
    ) { result ->
        Log.d("WebView", result)
    }
}

하지만 동적 웹사이트에서는 해당 요소가 아직 생성되지 않았을 수 있습니다.

이 문제를 해결하기 위해 무조건 Thread.sleep()을 사용하는 것은 좋지 않습니다.

대신 JavaScript에서 특정 요소가 나타나는지 확인하는 방법을 사용할 수 있습니다.

15. MutationObserver로 DOM 변경 감지하기

MutationObserver는 DOM 변경을 감지할 수 있는 JavaScript API입니다.

예를 들어 로그인 후 사용자 메뉴가 생성되는 상황을 감지할 수 있습니다.

(function() {
    const selector = ".user-menu";

    const checkElement = () => {
        const element = document.querySelector(selector);

        if (element) {
            console.log("User menu detected");
            return true;
        }

        return false;
    };

    if (checkElement()) return;

    const observer = new MutationObserver(() => {
        if (checkElement()) {
            observer.disconnect();
        }
    });

    observer.observe(document.documentElement, {
        childList: true,
        subtree: true
    });
})();

이 스크립트는 .user-menu 요소가 생성되는지 감지합니다.

단, 위 예시는 DOM 변경을 감지하는 기본 구조이며 Android로 결과를 전달하지는 않습니다.

Android와 통신하려면 evaluateJavascript()의 결과 콜백과는 별도로 안전한 메시지 전달 구조가 필요합니다.

또한 MutationObserver를 등록했다면 감지가 완료되거나 더 이상 필요하지 않을 때 해제해야 합니다.

장시간 유지되는 페이지에서는 불필요한 DOM 감시가 성능에 영향을 줄 수 있습니다.

16. WebChromeClient로 JavaScript 콘솔 확인하기

개발 중 JavaScript 오류를 확인하려면 WebChromeClient의 onConsoleMessage()를 사용할 수 있습니다.

webView.webChromeClient = object : WebChromeClient() {

    override fun onConsoleMessage(
        consoleMessage: ConsoleMessage
    ): Boolean {

        Log.d(
            "WebConsole",
            "${consoleMessage.message()} " +
                "(${consoleMessage.sourceId()}:" +
                "${consoleMessage.lineNumber()})"
        )

        return true
    }
}

이렇게 하면 JavaScript의 console.log()나 오류 메시지를 Logcat에서 확인할 수 있습니다.

다만 운영 앱에서 모든 콘솔 메시지를 그대로 기록하면 민감한 정보가 로그에 포함될 수 있습니다.

따라서 디버그 빌드에서만 상세 로그를 활성화하는 것이 좋습니다.

17. WebView 디버깅 활성화하기

Android WebView는 Chrome 개발자 도구를 이용해 디버깅할 수 있습니다.

if (BuildConfig.DEBUG) {
    WebView.setWebContentsDebuggingEnabled(true)
}

디버깅이 활성화된 앱을 실행한 뒤 개발 PC의 Chrome에서 다음 주소로 접근할 수 있습니다.

chrome://inspect

연결된 Android 기기의 WebView가 표시되면 개발자 도구를 통해 다음 항목을 확인할 수 있습니다.

  • DOM 구조

  • JavaScript 콘솔

  • 네트워크 요청

  • 쿠키 및 저장소

  • JavaScript 오류

  • 페이지 성능

단, WebView 디버깅은 개발 환경에서만 활성화하는 것이 좋습니다.

릴리스 빌드에서는 비활성화 상태를 유지하도록 관리해야 합니다.

18. WebView에서 JavaScript 실행 시 스레드 주의사항

WebView는 기본적으로 UI 스레드에서 접근해야 합니다.

따라서 다음과 같은 코드는 피해야 합니다.

CoroutineScope(Dispatchers.IO).launch {
    webView.evaluateJavascript(
        "document.title",
        null
    )
}

백그라운드 스레드에서 WebView 메서드를 직접 호출하면 스레드 관련 문제가 발생할 수 있습니다.

대신 메인 스레드에서 호출해야 합니다.

lifecycleScope.launch(Dispatchers.Main) {
    webView.evaluateJavascript(
        "document.title"
    ) { result ->
        Log.d("WebView", result)
    }
}

JavaScript 결과를 받은 뒤 무거운 JSON 파싱이나 데이터베이스 저장이 필요하다면 해당 작업만 백그라운드로 분리합니다.

webView.evaluateJavascript(script) { result ->

    lifecycleScope.launch {

        val parsed = withContext(Dispatchers.Default) {
            parseResult(result)
        }

        withContext(Dispatchers.IO) {
            saveResult(parsed)
        }
    }
}

이 구조에서는 WebView 접근과 UI 처리는 메인 스레드에서 수행하고, 무거운 작업만 적절한 Dispatcher로 이동합니다.

19. 여러 WebView를 사용할 때 성능 관리

여러 WebView를 동시에 생성하면 메모리 사용량과 렌더링 비용이 증가할 수 있습니다.

특히 ViewPager2 내부에서 WebView를 사용하는 경우에는 인접 페이지가 미리 생성될 수 있습니다.

예를 들어 다음과 같은 구조입니다.

ViewPager2
   │
   ├── Fragment A
   │     └── WebView A
   │
   ├── Fragment B
   │     └── WebView B
   │
   └── Fragment C
         └── WebView C

모든 Fragment에서 onViewCreated()가 호출되자마자 loadUrl()을 실행하면 사용자가 아직 보지 않은 페이지도 로딩될 수 있습니다.

이러한 구조는 메모리와 네트워크 사용량을 증가시킬 수 있습니다.

개선 방향

  • 현재 표시되는 페이지의 WebView부터 로딩

  • 인접 페이지의 불필요한 선로딩 제한

  • WebView 생성 시점 지연

  • 사용하지 않는 WebView 정리

  • Fragment 생명주기에 맞춘 로딩 상태 관리

다만 ViewPager2의 Fragment 생성 및 생명주기 정책을 고려해야 합니다.

onViewCreated()가 호출되었다는 이유만으로 해당 Fragment가 사용자에게 실제로 표시되고 있다고 판단하면 안 됩니다.

현재 선택된 페이지를 기준으로 로딩을 제어하는 구조가 더 명확할 수 있습니다.

20. WebView 생명주기 관리

WebView는 일반 View보다 많은 리소스를 사용합니다.

따라서 Activity나 Fragment가 종료될 때 적절한 정리 작업이 필요할 수 있습니다.

Fragment에서 WebView를 사용하는 경우에는 View 생명주기를 기준으로 관리해야 합니다.

override fun onDestroyView() {
    webView.apply {
        stopLoading()
        loadUrl("about:blank")
        removeAllViews()
        destroy()
    }

    super.onDestroyView()
}

이 코드는 WebView를 완전히 폐기하는 경우의 예시입니다.

실제 구현에서는 WebView가 부모 ViewGroup에 연결되어 있다면 부모에서 제거하는 작업도 고려해야 합니다.

또한 WebView를 재사용하도록 설계한 경우에는 무조건 destroy()를 호출하면 안 됩니다.

WebView의 생성, 재사용, 폐기 정책을 명확하게 정하는 것이 중요합니다.

21. WebView 보안 체크리스트

WebView는 외부 웹 콘텐츠를 실행할 수 있기 때문에 일반적인 Android UI보다 보안에 주의해야 합니다.

특히 다음 사항을 확인하는 것이 좋습니다.

  • JavaScript는 필요한 WebView에서만 활성화

  • 신뢰할 수 없는 페이지에 addJavascriptInterface 노출 금지

  • 민감한 인증 정보와 쿠키를 로그에 출력하지 않음

  • HTTP 평문 통신 허용 최소화

  • 외부 URL 이동 정책 검토

  • 파일 접근 관련 WebView 설정 확인

  • 서드파티 쿠키 허용 여부 검토

  • WebView 디버깅은 디버그 빌드에서만 활성화

  • JavaScript 결과를 신뢰할 수 없는 입력으로 처리

  • WebView 생명주기와 메모리 관리

  • 외부 사이트의 인증 정책 및 이용약관 확인

22. 실무에서 추천하는 WebView 구조

여러 WebView를 운영하거나 로그인 상태를 관리해야 하는 앱이라면 다음과 같이 역할을 분리하는 것이 좋습니다.

Activity / Fragment
        │
        ▼
    WebViewManager
        │
        ├── WebView 설정
        ├── 페이지 이동 관리
        ├── JavaScript 실행
        └── 생명주기 관리
        │
        ▼
    WebPageAnalyzer
        │
        ├── DOM 요소 분석
        ├── 로그인 UI 상태 확인
        └── JSON 결과 파싱
        │
        ▼
    Repository
        │
        ├── SQLite 저장
        └── 상태 데이터 관리

이 구조에서는 Activity나 Fragment에 모든 WebView 코드를 작성하지 않아도 됩니다.

또한 사이트마다 DOM 구조가 다르다면 사이트별 분석 로직을 별도 클래스로 분리할 수 있습니다.

예를 들어 다음과 같은 인터페이스를 정의할 수 있습니다.

interface LoginStateDetector {
    fun getDetectionScript(): String
}

사이트별 구현체를 만들어 필요한 JavaScript를 반환하도록 구성할 수 있습니다.

다만 로그인 상태 감지는 DOM 구조나 쿠키만으로 확정하기 어렵기 때문에 가능한 경우 공식 인증 API를 우선적으로 활용하는 것이 좋습니다.

23. 마무리

Android WebView는 단순한 웹페이지 표시 기능을 넘어 JavaScript와 Android 코드를 연결하는 다양한 기능을 제공합니다.

하지만 WebView를 이용해 웹페이지 데이터를 분석하거나 로그인 상태를 확인하는 앱에서는 비동기 실행, 생명주기, 보안 문제를 함께 고려해야 합니다.

실무에서는 다음 원칙을 추천합니다.

  1. Android에서 JavaScript를 실행할 때는 evaluateJavascript()를 사용합니다.

  2. JavaScript에서 Android로 데이터를 전달해야 할 때는 신뢰 경계를 검토한 뒤 통신 방식을 선택합니다.

  3. onPageFinished()만으로 동적 콘텐츠 준비를 판단하지 않습니다.

  4. 로그인 상태는 DOM이나 쿠키 하나만으로 확정하지 않습니다.

  5. 여러 WebView를 사용하는 경우 로딩 시점과 생명주기를 분리해 관리합니다.

  6. JavaScript 결과 처리와 SQLite 저장은 메인 스레드를 차단하지 않도록 구성합니다.

특히 외부 웹사이트와 연동하는 앱에서는 사이트의 HTML 구조가 언제든 변경될 수 있으므로 유지보수 가능한 구조를 만드는 것이 중요합니다.


참고 자료

태그: Android, Kotlin, WebView, JavaScript, evaluateJavascript, addJavascriptInterface, WebViewClient, WebChromeClient, CookieManager, 로그인 상태 감지, ViewPager2, WebView 보안, MVVM