Frontend/Article

모바일 앱(App)과 웹뷰(WebView)의 통신에 관하여 (Feat. JavaScriptInterface)

BeNI 2024. 4. 27. 17:32
728x90

 

 

 

 

이번 글에서는 앱과 웹뷰의 통신에 대해 간단하게 정리보려고 한다 !
웹뷰가 무엇인지, 앱과 웹뷰가 통신을 어떻게 할 수 있는지에 대해 알아보자

 

 

 

1. 웹뷰(WebView)란 ? 

웹뷰란 네이티브 앱에 내장되어 있는 웹 브라우저 이다.

https://docs.tosspayments.com/resources/glossary/webview

 

웹뷰(WebView) | 토스페이먼츠 개발자센터

웹뷰(WebView)는 네이티브 앱에 내제되어 있는 웹 브라우저입니다. 웹뷰를 사용하면 웹 콘텐츠를 네이티브 앱 뷰와 같이 사용자에게 보여줄 수 있어요.

docs.tosspayments.com

간단하게 말하면, 웹뷰란 것을 통해 App에서 웹 페이지를 보여줄 수 있다 !

(네이티브 앱과 웹 앱을 합친 앱을 하이브리드 앱이라고 한다)

 

 

그러면 네이티브 앱으로 다 구현하면 될걸, 왜 웹뷰를 사용하는 걸까?

크게 3가지 이유가 있다.

  • 앱으로 구현하는 것보다 웹뷰로 구현하는 게 더 간편한 경우 
  • 앱의 배포 없이 업데이트 빠르게 하기 위해
  • 여러 플랫폼에서 자유롭게 사용하기 위해(Android, ios 등)

하지만 웹뷰는 앱보다는 비교적 느리고, UI가 제한적인 것 등의 단점들이 있다. 

 

 

2. 웹뷰와 앱의 통신 (Android기준)

웹뷰를 개발하다보면 앱의 기능을 웹뷰에서 사용해야 할 때(혹은, 앱과 데이터를 주고받아야 할 경우)가 있다 

 

이런 경우에 앱에서 JavascriptInterface를 통해서 웹과 앱이 상호작용할 수 있다.

 

 

 

# 1. 앱에서 정의한 함수를 웹뷰에서 호출하는 경우

예를 들어, 앱에서 띄우는 Toast나 Alert 등을 웹뷰에서 띄워야 할 경우가 있다.

이 경우에는 앱에서 토스트를 띄우는 함수를 정의하고 웹에서 해당 함수를 호출하는 과정이 필요하다.

 

이미지 출처: https://www.geeksforgeeks.org/what-is-toast-and-how-to-use-it-in-android-with-examples/

 

 

 

Android developer 에 나온 예시로 어떻게 통신을 하는지 살펴보자

https://developer.android.com/develop/ui/views/layout/webapps/webview?hl=ko#UsingJavaScript

 

WebView에서 웹 앱 빌드  |  Android Developers

이 페이지는 Cloud Translation API를 통해 번역되었습니다. WebView에서 웹 앱 빌드 컬렉션을 사용해 정리하기 내 환경설정을 기준으로 콘텐츠를 저장하고 분류하세요. WebView를 사용하여 웹 애플리케이

developer.android.com

(자세한 과정은 위 링크 참고)

 

 

 

1)  Android에서 WebView 사용 설정 후 웹앱 인터페이스 생성

/** Instantiate the interface and set the context.  */
class WebAppInterface(private val mContext: Context) {

    /** Show a toast from the web page.  */
    @JavascriptInterface
    fun showToast(toast: String) {
        Toast.makeText(mContext, toast, Toast.LENGTH_SHORT).show()
    }
}

 

 

2)웹뷰에 JsInterface 사용설정 추가

WebView webView = (WebView) findViewById(R.id.webview);
webView.addJavascriptInterface(new WebAppInterface(this), "Android");

* Android(이름 자유)라는 이름으로 사용할 수 있게 함

 

 

 

3) 웹뷰에서는 "Android"라는 전역 객체에 접근해 showToast 호출

window.Android.showToast("토스트를 띄워줘!"); // Android.showToast("토스트를 띄워줘!");

* Android가 전역 객체로 들어가기에 window라고 굳이 안붙여도 된다.

 

 

 

 

# 2. 웹뷰에서 정의한 함수를 앱에서 호출하는 경우 

예를 들어, 사용자가 입력한 텍스트를 앱에게 전달해야 한다고 가정해보자(혹은 반대)

이 경우에는 웹에서 함수를 선언하고 앱에서 해당 함수를 호출하여 데이터를 받는(or 주는) 구조다.

 

 

Andorid에서는 evaluateJavascript라는 메서드로 웹뷰에서 js 코드를 실행시킬 수 있다.

https://developer.android.com/reference/android/webkit/WebView#evaluateJavascript(java.lang.String,%20android.webkit.ValueCallback%3Cjava.lang.String%3E)

 

WebView  |  Android Developers

 

developer.android.com

WebView.evaluateJavascript(script: String, resultCallback: ValueCallback<String>);

 

script엔 실행시킬 자바스크립트 코드를, resultCallback에는 해당 코드를 실행시킨 후 반환 값을 이용하는 콜백함수를 넣으면 된다.

(반환값을 이용할 일이 없으면 null)

 

 

1) WebView -> App 

예시로, 아래와 같은 메서드가 웹뷰에 선언되어 있다고 할 때,

let message = "전달하고 싶은 메시지";

window.postMessage = () => {
  return message;
}

 

네이티브에서 evaluateJavascript를 이용해 해당 message를 받을 수 있다.

WebView.evaluateJavascript('javascript:window.postMessage()', (message) -> {
  // message를 이용한 코드
});

 

 

 

2) App -> WebView

웹뷰에서 아래와 같이 함수를 선언되어 있을 때,

function getMessageFromApp() {
  return new Promise((resolve) => {
    window.getMessage = (message) => {
      resolve(message);
    }
    
    window.getMessage = undefined;
  }
}


async function someFunc() {
  const message = await getMessageFromApp();
}

 

앱에서 아래 코드를 실행시키면 웹뷰에서 데이터를 받을 수 있다.

WebView.evaluateJavascript("javascript:window.getMessage('보낼 메시지')", null);

* 순서 주의: 웹뷰에서 함수가 미리 선언이 되어있어야 함

 

 

 

 

 

이런 단방향의 통신(WebView -> App, App -> WebView)은 비교적 간단하다. 

 

 

웹뷰와 앱이 서로의 함수를 호출하는 양방향 통신에 대해 좀 더 살펴보자

 

 

 

# 3. 웹뷰와 앱의 양방향 통신(WebView <-> App)

예를 들어, 앱에서 띄우는 Confirm 팝업을 웹뷰에서 띄우고 싶다고 생각해보자.

당근마켓에서 게시글 삭제할 때 나오는 모달

 

그러면 WebView에서는 팝업을 띄우는 함수를 호출하고, App에서는 해당 팝업을 띄운다.

그 다음 사용자의 취소/삭제 선택 결과를 App에서 WebView로 전달을 해줘야 한다.

 

 

이 경우에는 다음과 같은 과정이 필요하다

  • A: 앱에서 팝업을 띄우는 함수(혹은 무언가..)를 선언
  • B: 웹뷰에서 팝업결과를 받을 함수(혹은 무언가..)를 선언
  • 웹뷰에서 팝업을 띄우는 함수(A)를 호출 시 앱에서 웹뷰의 팝업결과를 받는 함수(B)를 호출하도록 선언

 

 

웹뷰 관점에서 팝업선택결과를 받을 방법으로 두가지 방법이 있다.

 

 

1. resultCallback을 이용

사용자의 응답을 받을 수있는 또다른 resultCallback을 웹에서 정의하여 앱이 해당 함수를 호출했을 때 웹뷰가 데이터를 받을 수 있게한다.

 

 

 

 

1) 앱(App)에서 openConfirmPopup을 띄우는 함수를 정의

* ConfirmPopup(title, desc, resultCallback) 클래스가 정의되어있다고 가정

class WebAppInterface(private val mContext: Context) {
    @JavascriptInterface
    fun openConfirmPopup(title: String, desc: String, resultCallbackName: String) {
        ConfirmPopup.open(title, desc, (result) -> {
            // InterfaceName는 웹뷰에서 자유롭게 정의한다.
            val script = "javacript:window.WebView.${resultCallbackName}(result)"
        	webView.evaluateJavascript(script, null);
        });
    }
}

evaluateJavacript라는 함수로 띄운 웹뷰에서 자바스크립트 코드를 실행할 수 있게 한다.

 

 

2) 웹에서 resultCallback을 정의 후 openConfirmPopup 실행

promise를 반환하는 함수를 선언하여, Android.getConfirmResult가 실행됐을 때 결과를 받을 수 있게 한다.

function openConfirmPopup(title, desc) {
  return new Promise((resolve) => {
    // getConfirmResult이라는 함수가 호출됐을 때 결과를 받을 수 있게함
    window.WebView.getConfirmResult = (result) => {
      resolve(result);
    }
    window.WebView.getConfirmResult = undefined;
    
    window.Android.openConfirmPopup(title, desc, "getConfirmResult");
  });
}


async function someFunction () {
  const result = await opneConfirmPopup("정말로 삭제하시겠습니까?", "삭제된 게시글은 복구할 수 없습니다.");
}

 

 

 

 

두 번째 방법으로 CustemEvent를 이용하는 방법이 있다.

 

 

 

2. WebView에서 CustomEvent 정의

해당 방식은 웹뷰에서 CustomEvent를 정의하고 네이티브에서 실행시키는 방식이다.

let result = '' // 미리 result값을 받을 변수를 선언해둔다.

function openConfirmPopupCallback(event) {
  result = event.result; // event에 응답 결과를 받을 수 있게 한다.
};

window.addEventListener("ConfirmPopup", openConfirmPopupCallback);
// window.removeEventListener("ConfirmPopup", openConfirmPopupCallback); 도 해줘야 한다.


function openConfirmPopup(title, desc) {
    result = undefiend; // 이전 값을 초기화
    window.Android.openConfirmPopup(title, desc);
    return result;
}

 

* 커스텀 이벤트를 사용하기위해 윈도우에 이벤트를 걸어주었다.

 

 

그러면 앱쪽에서는 해당 이벤트를 dispatch 해줌으로써  openConfirmPopupCallback함수를 실행시킬 수 있다.

class WebAppInterface(private val mContext: Context) {
    @JavascriptInterface
    fun openConfirmPopup(title: String, desc: String) {
        ConfirmPopup.open(title, desc, (result) -> {
            val script = "javascript:window.dispatchEvent(new CustomEvent('ConfirmPopup', {result: result})"
        	webView.evaluateJavascript(script, null);
        });
    }
}

 

 

순서는 아래와 같다.

1. 결과 값을 받을 변수(react면 state) 을 선언

2. 결과 값을 받을 함수를 콜백으로 받는 event를 dom요소에 걸어둔다.

3. 네이티브에서 해당 이벤트를 dispatch하여 결과값을 넘김

4. 웹뷰에서 result를 받음

 

 

 

 

 


 

 

위와 같은 방식 이외에도 Android의 JSInterface를 이용해 다양한 방법으로 통신할 수 있을 것이다.

대표적인 방식으로 2가지를 소개해봤는데, 용도에 맞게 잘 활용하면 좋을 것 같다.

 

 

 

이번 글은 여기서 마무리!!!!!

 

궁금한 점이나 틀린 내용있으면 댓글로 달아주세요 ~

 

 

 

 

 

 

 

 


 

참고 자료

https://docs.tosspayments.com/resources/glossary/webview

https://developer.android.com/develop/ui/views/layout/webapps/webview?hl=ko

https://itssweetrain.tistory.com/5

https://boostbrothers.github.io/hospital-detail-webview

728x90