[Android] WebView 파헤치기 2탄

yuuuzzzinzzzang·2024년 2월 6일
2
post-thumbnail

웹뷰를 구현하다 보면 단순히 웹 페이지를 띄우는 것 외에 통신하며 데이터를 주고 받는 동작도 필요합니다. 그렇다면 웹뷰와 앱은 어떻게 통신하게 될까요?

🌉 Bridge?

안드로이드의 네이티브 코드와 웹 페이지는 서로 분리된 환경이기 때문에 상호간의 통신을 위한 연결 다리가 필요한데, 브릿지가 그러한 역할을 합니다. ✨ 

즉, Bridge(브릿지)란 안드로이드 웹뷰의 통신을 위해 만들어진 JavaScript용 Interface라고 할 수 있습니다. 안드로이드와 웹뷰는 각자의 환경에 존재하는 메소드를 직접 호출할 수 없기 때문에 브릿지를 통해 호출합니다.

🌉 addJavascriptInterface

안드로이드 ↔ 웹뷰 통신을 위해 호출되는 함수들을 정의할 AndroidBridge라는 클래스를 만들어보겠습니다.

class AndroidBridge(private val context: Context) {
    ...
}

그리고 이 인터페이스를 사용하기 위해 addJavascriptInterface 를 사용해 웹뷰에 인터페이스를 붙여줘야 합니다. 해당 브릿지 클래스의 인스턴스와 호출되는 데에 사용할 이름 문자열을 전달해줍니다.

addJavascriptInterface(AndroidBridge(context), "AndroidBridge")

🌉 Web → Android

🤔 웹뷰에서 안드로이드 토스트를 띄워줘야 하는 상황이 있다고 가정해봅시다.

그렇다면 안드로이드에서 토스트를 띄우는 함수를 정의하고, 이 함수를 웹뷰에서 호출할 수 있도록 해줘야겠죠 ? 아래와 같이 아까 정의한 AndroidBridge 클래스 안에 토스트를 띄우는 함수를 정의해보겠습니다.

class AndroidBridge(private val context: Context) {

    @JavascriptInterface
    fun showToast(toast: String) {
        Toast.makeText(context, toast, Toast.LENGTH_LONG).show()
    }
}

💡 @JavascriptInterface ?
여기서 함수 위에 붙은 @JavascriptInterface은 해당 함수를 자바 스크립트에서 이용할 수 있도록 만들어주는 어노테이션입니다.

<body>
<h1>WebviewSample</h1>
<button onclick="showAndroidToast('Hello Android from Web')">안드로이드 토스트 띄우기</button>
<script>
    function showAndroidToast(toast) {
        AndroidBridge.showToast(toast)
    }
</script>
</body>

웹뷰에서는 위와 같이 버튼을 클릭해 안드로이드 토스트를 띄우는 함수를 실행시킬 수 있습니다. 아까 인터페이스를 웹뷰에 붙여줄 때 지정한 AndroidBridge와 함께 사용할 함수를 호출해주면 됩니다.

🌉 Android → Web

🤔 안드로이드에서 문자열을 전달해 웹뷰에서 알럿을 띄워야하는 상황이 있다고 가정해봅시다. (샘플을 위한 예시 상황인지라 어색한 감이 있긴 하네요.💦)

<script>
    function showWebViewAlert(text) {
        alert(text);
    }
</script>

웹 쪽에는 위와 같이 문자열을 받아 알럿을 띄울 수 있는 함수가 선언되어 있습니다.

class AndroidBridge(
    private val context: Context,
    private val webView: WebView,
) {

    ...

    fun showWebViewAlert(text: String) {
        val script = "window.showWebViewAlert('$text');"
        evaluateWebViewFunction(script) { result ->
            Log.d("tag_test", result)
        }
    }

    private fun evaluateWebViewFunction(
        script: String,
        callback: ((String) -> Unit)? = null,
    ) {
        return webView.evaluateJavascript(script, callback)
    }
}

그리고 안드로이드에서는 웹의 showWebViewAlert 함수를 호출할 수 있는 함수를 브릿지 클래스 안에 정의해줍니다.

evaluateWebViewFunction 는 웹뷰의 자바 스크립트 함수를 실행하기 위해 중복적으로 호출되는 evaluateJavascript 를 래핑한 함수입니다.

위의 코드에서는 evaluateJavascript()에 웹뷰에서 알럿을 띄우기 위해 텍스트를 받아 showWebViewAlert를 실행하는 스크립트와, 콜백에 대한 결과값을 로그로 출력하는 동작을 전달했습니다.

💡 evaluateJavascript ?
웹의 함수를 호출하려면 evaluateJavascript()를 사용해야 합니다.

public void evaluateJavascript (String script, ValueCallback<String> resultCallback)

비동기로 자바스크립트 함수를 실행할 수 있습니다.

  • 첫 번째 인자인 script에는 실행할 JavaScript 코드를 문자열로 전달하고
  • 두 번째 인자인 resultCallBack에는 필요 시 스크립트 실행 후 반환 값을 다루는 로직을 전달할 수 있습니다.

콜백을 코루틴을 이용한 suspend function으로 바꿔 활용할 수도 있습니다.

    suspend fun showWebViewAlertWithCoroutine(text: String) {
        val script = "window.showWebViewAlert('$text');"
        val result = evaluateWebViewFunctionWithCoroutine(script)
        Log.d("tag_test", result)
    }

    private suspend fun evaluateWebViewFunctionWithCoroutine(script: String): String {
        return suspendCoroutine { cont ->
            webView.evaluateJavascript(script) { result ->
                cont.resume(result)
            }
        }
    }

다음 실행 화면은 버튼 클릭 이벤트로 ‘안드로이드’라는 문자열을 전달해 showWebViewAlert()를 실행한 모습입니다.

느낀점

간단하고, 잘 쓰이지 않는 예시 상황이었을 수 있지만 기본적인 동작들을 통해 안드로이드와 웹뷰 간 상호작용이 어떻게 일어나는지 짚고 넘어가 볼 수 있어 좋았습니다 ! 편의성을 위해 기본 함수들을 커스텀하게 래핑해서 사용하다보면 래핑한 윗단의 함수들만 사용하게 되고, 그 속을 이루고 있는 로우 레벨의 기본 함수들이 어떤 것들이 있고 어떤 일을 하는지 잊기 쉬운 것 같습니다. 꽤나 많은 것을 놓치고 써왔음에 반성하는 마음이 들기도 하네요 😓 그래도 이렇게라도 되짚어보니 뿌듯한 마음도 듭니다!

참고

WebView
Build web apps in WebView

profile
yuuuzzzin의 개발 블로그

1개의 댓글

comment-user-thumbnail
2024년 2월 21일

좋은 글 감사합니다!

답글 달기