간단한 Hybrid App을 만들어보자 (JavascriptInterface / ChromeClient)

지프치프·2023년 2월 5일
0

Android

목록 보기
60/85
post-thumbnail

“Android 로봇은 Google에서 제작하여 공유한 저작물을 복제하거나 수정한 것으로 Creative Commons 3.0 저작자 표시 라이선스의 약관에 따라 사용되었습니다.”


개요

WebView를 사용하여 App을 만들 때
Web과 통신을 하거나 Web의 LifeCycle을 제어하고 싶을 때가 있다.
다행히 Android에선 이를 모두 라이브러리로 지원을 하고 있다.

  • Web과 Javascript를 통해 통신을 할 수 있는 JavascriptInterface
  • Web의 LifeCycle을 제공하는 WebViewClient
  • Web의 Javascript 관련 콜백을 제공하는 WebChromeClient

위 3가지를 활용하면 네이티브 App에서도 WebView를 통해
웹페이지를 제어할 수 있다.
이 포스팅에선 JavascriptInterface와 WebChromeClient를 간단한 샘플 앱을 통해 알아보자

JavascriptInterface

JavascriptInterface라는 어노테이션을 Android에서 제공하는데
Web에서 네이티브 기능에 접근하고 싶을 때
해당 기능을 실행하는 메소드에 이 어노테이션을 추가하면
Web에서 해당 메소드를 호출할 수 있다.

토스트 메세지를 띄우는 메소드가 있는 인터페이스를 만들어보자

class JSInterface(private val context: Context) {
    @JavascriptInterface
    fun showToast(text: String) = Toast.makeText(context, text, Toast.LENGTH_SHORT).show()
}

이제 WebView를 세팅하러 가보자

        binding.apply {
            webview.apply {
                webViewClient = MyWebViewClient()
                webChromeClient = MyChromeClient()
                settings.apply {
                    javaScriptEnabled = true
                    loadWithOverviewMode = true
                    useWideViewPort = true
                    cacheMode = WebSettings.LOAD_DEFAULT
                }
                addJavascriptInterface(JSInterface(this@MainActivity), "JeepChief")
                loadUrl("file:///android_asset/index.html")
            }
        }

addJavascriptInterface(InterfaceClass(), "웹에서 사용할 이름")
추가해주면 WebView에 인터페이스가 추가되어 웹과 주고받을 준비는 끝이다.

WebView의 Setting과 관련된 사항은 이전 포스트를 참고하길 바란다.

샘플로 사용할 웹페이지를 만들어주자
위치는 asset폴더에 저장해주면 된다.

<!DOCTYPE html>
<html>
    <head>
        <meta charset="UTF-8" />
        <title>Hybrid Sample</title>
    </head>
    <body onload="showToast('hello')">
        <script type="text/javascript">
            // Web에서 네이티브 호출
            function showToast(text) {
                JeepChief.showToast(text);
            }

            // 네이티브에서 호출할 Web 메소드
            function showAlert(text) {
                alert(text);
            }
        </script>
    </body>
</html>

onload() 속성을 사용해서 웹페이지가 열리면
토스트가 호출되도록 만들었다.

이제 실행해보면 위 캡쳐처럼 웹페이지의 로드가 되면 토스트 메세지를 띄우는 것을 볼 수 있다.

네이티브에서 Web 호출

그렇다면 반대로 생각을 해볼 수도 있다.
네이티브에서 Web을 호출할 순 없을까?
이 또한 매우 간단하게 가능하다.
webView.loadUrl()을 호출할 때 매개변수를
Url 대신 javascript 코드를 넣어주면 Web에서 해당 코드를 실행해준다.

필자는 네이티브에서 버튼을 클릭하면 웹에서 정의된 alert을 출력하는 메소드를 호출해보았다.

먼저 클릭할 버튼의 리스너에서 loadUrl 메소드를 호출했다.

            btnAlert.setOnClickListener {
                webview.loadUrl("javascript:showAlert(\"Hello World!\")")
            }

매개변수로 호출할 Javascript 코드를 자유롭게 작성하고
앞에 javascript:를 붙여주면 웹에서 실행을 해준다.

그리고 이제 앱을 실행해보면..

alert이 네이티브에서도 정상적으로 호출되는 것을 볼 수 있다.

WebChromeClient

하지만 위의 alert을 커스텀하고 싶을 때가 있다.
title을 다르게 보여주고 싶거나 alert의 style을 지정하는 등의
일을 하고 싶을 때 말이다.

이 또한 Android에선 WebChromeClient를 통해 지원한다.
WebChromeClient는 javascript과 관련된 다양한 콜백을 제공한다.
이 포스팅에선 간단히 몇 개만 소개하고 나머지는 위의 링크를 통해 확인해보길 바란다.

class MyChromeClient : WebChromeClient() {
    companion object val TAG = "Hybrid"
    private var webTitle = ""
    private lateinit var webIcon: Bitmap
    override fun onReceivedTitle(view: WebView?, title: String?) {
        Log.e(TAG, "onReceivedTitle()")
        super.onReceivedTitle(view, title)
        title?.let { webTitle = it }
    }

    override fun onReceivedIcon(view: WebView?, icon: Bitmap?) {
        Log.e(TAG, "onReceivedIcon()")
        super.onReceivedIcon(view, icon)
        icon?.let { webIcon = it }
    }

    override fun onJsAlert(
        view: WebView?,
        url: String?,
        message: String?,
        result: JsResult?
    ): Boolean {
        Log.e(TAG, "onJsAlert()")
        view?.context?.let {
            AlertDialog.Builder(it)
                .setTitle(webTitle)
                .setMessage(message)
//                .setIcon(BitmapDrawable(view.context.resources, webIcon))
                .setPositiveButton("확인") { _, _ -> result?.confirm() }
                .setNegativeButton("취소") { _, _ -> result?.cancel() }
                .show()
        }
        return true
    }

    override fun onConsoleMessage(consoleMessage: ConsoleMessage?): Boolean {
        return super.onConsoleMessage(consoleMessage)
    }
}
  • onReceivedTitle()
    웹페이지의 Title을 가져온다.
  • onReceivedIcon()
    웹페이지의 Icon(favicon)을 가져온다.
  • onJsAlert()
    웹페이지에서 alert이 발생했을 때 호출되며 alert의 내용과 리스너들을 가져온다.
  • onConsoleMessage()
    웹페이지에서 콘솔 메시지의 내용들이 출력되면 호출되며 콘솔 메세지를 가져온다. 여기서 콘솔 메세지는 Javascript에서 console.log()를 통해 출력되는 메세지들이다.

필자는 onJsAlert()에서 alert을 커스텀했다.
실행 화면은 아래와 같다.

샘플 앱의 원본 소스는 여기에서 확인할 수 있다.

개인적으로 공부했던 것을 바탕으로 작성하다보니
잘못된 정보가 있을수도 있습니다.
인지하게 되면 추후 수정하겠습니다.
피드백은 언제나 환영합니다.
읽어주셔서 감사합니다.

profile
지프처럼 거침없는 개발을 하고싶은 개발자

0개의 댓글