WebView

이석규·2024년 2월 6일
0

WebView는 무엇입니까

네이티브 환경에서 웹 페이지를 제공하려고 하는 경우 사용합니다.
Android의 View 클래스를 확장하여 WebView라는 이름으로 쓰입니다.

WebView의 장,단점은 무엇입니까

장점

  • 여러 플랫폼에서 사용할 수 있습니다.
    • AOS iOS 웹페이지 세 곳의 개발을 한 번에 할 수 있습니다.
  • 배포 없이 업데이트를 할 수 있습니다.
    • 앱 배포에 필요한 심사 없이 이를 업데이트 할 수 있습니다.
  • 인터넷 연결이 지속적으로 필요한 데이터를 불러오는데에 유용합니다.
    • 항상 인터넷 연결이 필요한 이메일과 같은 데이터는 웹 뷰로 보여주는 것이 더 쉽습니다.

단점

  • 느립니다.
    • 스토어에서 빌드가 완료되는 네이티브 앱보다 리소스를 다운로드 받고 보여주고 하느라 비교적 느립니다.
  • UI가 제한적입니다.
    • 웹 페이지의 구성 요소는 안드로이드 관련 요소가 아닙니다. Html, CSS, JavaScript 등 웹 개발에 필요한 요소로 구성했기 때문에 UI 구현이 제한적입니다.
  • 스토어 심사가 어려울 수 있습니다.
    • 허가 없는 웹 사이트를 사용하거나, 웹 뷰만으로 구성된 앱의 등록은 방지되고 있습니다.

WebView는 어떻게 사용합니까

1. 인터넷 연결하기

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

2. WebView 띄우기

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

3. url 로드하기

val myWebView: WebView = findViewById(R.id.webview)
myWebView.loadUrl("http://www.example.com") 

아래에서 어떤 웹뷰를 어떻게 띄울거고, 어떻게 상호작용할건지 알아보겠습니다.

WebSetting

웹 뷰를 띄우기 위한 세팅을 합니다.

1. useWideViewPort = Boolean

WebView가 "viewport" HTML 메타 태그에 대한 지원을 활성화해야 하는지 아니면 넓은 뷰포트를 사용해야 하는지를 설정합니다.
false 인 경우 : 레이아웃 너비는 항상 장치 독립적(CSS) 픽셀 단위의 WebView 컨트롤 너비로 설정됩니다.
true 이고 페이지에 뷰포트 메타 태그가 포함 : 태그에 지정된 너비 값이 사용됩니다. 페이지에 태그가 없거나 너비를 제공하지 않으면 넓은 뷰포트가 사용됩니다.

2. javaScriptEnabled = Boolean

JavaScript 실행을 활성화 여부를 WebView에 지시합니다.
true로 설정하지 않을 시, 웹 페이지가 열리지 않거나 디바이스의 크롬을 통해 열립니다.
기본값은 false 입니다.

3. setSupportZoom(Boolean)

WebView가 화면 확대/축소 컨트롤 및 동작을 사용하여 확대/축소를 지원해야 하는지 여부를 설정합니다.
사용해야 하는 특정 확대/축소 메커니즘은 setBuiltInZoomControls(boolean) 으로 설정할 수 있습니다. WebView#zoomIn() 및 WebView#zoomOut() 메서드를 사용하여 수행되는 확대/축소에 영향을 주지 않습니다.
기본값은 true입니다.

4. builtInZoomControls = Boolean

WebView가 내장된 확대/축소 메커니즘을 사용해야 하는지 여부를 설정합니다. 아래의 displayZoomControls와 세트입니다.
내장된 확대/축소 메커니즘은 WebView의 콘텐츠 위에 표시되는 화면 확대/축소 컨트롤과 확대/축소를 제어하기 위한 핀치 제스처의 사용으로 구성됩니다.
이러한 화면 컨트롤의 표시 여부는 setDisplayZoomControls(boolean) 을 사용하여 설정할 수 있습니다.
기본값은 false입니다.

내장된 메커니즘은 현재 지원되는 유일한 확대/축소 메커니즘이므로 이 설정을 항상 활성화하는 것이 좋습니다. 그러나 화면 확대/축소 컨트롤은 Android에서 더 이상 사용되지 않으므로(ZoomButtonsController 참조) setDisplayZoomControls(boolean) 을 비활성화하는 것이 좋습니다.

어차피 기본값이 false이므로, displayZoomControls를 굳이 호출할 필요 없이 builtInZoomControls 자체를 호출하지 않는 것도 좋아보입니다.

5. displayZoomControls = Boolean

내장된 확대/축소 메커니즘을 사용할 때 WebView가 화면 확대/축소 컨트롤을 표시해야 하는지 여부를 설정합니다. setBuiltInZoomControls(boolean) 을 참조하세요.
기본값은 true입니다.

그러나 화면 확대/축소 컨트롤은 Android에서 더 이상 사용되지 않으므로(ZoomButtonsController 참조) 이를 false로 설정하는 것이 좋습니다.

6. defaultTextEncodingName = "UTF-8"

HTML 페이지를 디코딩할 때 사용할 기본 텍스트 인코딩 이름을 설정합니다.
기본값은 "UTF-8"입니다.

7. loadWithOverviewMode = Boolean

WebView가 overview 모드에서 페이지를 로드할지 여부, 즉 너비에 따라 화면에 맞게 콘텐츠를 축소할지 여부를 설정합니다.
이 설정은 콘텐츠 너비가 WebView 컨트롤의 너비보다 클 때(예: getUseWideViewPort()가 활성화된 경우) 고려됩니다.
기본값은 false입니다.

8. databaseEnabled = Boolean

데이터베이스 스토리지 API 활성화 여부를 설정합니다.
기본값은 false입니다.

데이터베이스 저장소 API를 올바르게 설정하는 방법은 setDatabasePath(String)를 참조하세요.
이 설정은 프로세스의 모든 WebView 인스턴스에 전역적으로 적용됩니다.
WebView 구현은 해당 시점 이후에 이 설정에 대한 변경 사항을 무시할 수 있으므로 지정된 프로세스 내에서 WebView 페이지를 로드하기 전에만 이 설정을 수정해야 합니다.

9. domStorageEnabled = Boolean

DOM 저장소 API의 활성화 여부를 설정합니다. DomStorage인 localStorage, sessionStorage 를 사용할수 있도록 허용합니다

localStorage

  • 사용자가 데이터를 지우지 않는 이상, 브라우저나 OS를 종료해도 계속 브라우저에 남아있습니다. (영구성) 단, 동일한 브라우저를 사용할 때만 해당합니다.
  • 지속적으로 필요한 데이터를 저장합니다.(자동 로그인 등)

sessionStorage

  • 윈도우나 브라우저 탭을 닫을 경우 제거합니다.
  • 일시적으로 필요한 데이터 저장합니다. (일회성 로그인 정보, 입력폼 저장 등)

localStorage나 sessionStorage를 쓰지 않고, 세션이나 쿠키만 사용하는 경우가 있을수 있겠지만 그렇지 않은 경우에 true로 설정합니다.
기본값은 false입니다.

10. mixedContentMode = WebSettings.MIXED_CONTENT_ALWAYS_ALLOW

보안 원본이 안전하지 않은 원본에서 리소스를 로드하려고 시도할 때(=HTTPS 프로토콜을 사용하는 사이트에서 HTTP 리소스를 로드하는 것) WebView의 동작을 구성합니다.

Build.VERSION_CODES.KITKAT 이하 앱의 기본값은 MIXED_CONTENT_ALWAYS_ALLOW 입니다.
Build.VERSION_CODES.LOLLIPOP 이상 앱의 기본값은 MIXED_CONTENT_NEVER_ALLOW 입니다. 

11. cacheMode = LOAD_DEFAULT

캐시가 사용되는 방식을 재정의합니다.
정상적인 페이지 로드의 경우 캐시를 확인하고 필요에 따라 콘텐츠의 유효성을 다시 검사합니다. 뒤로 탐색할 때 콘텐츠의 유효성이 다시 검사되지 않고 대신 캐시에서 콘텐츠가 검색됩니다.
기본값은 LOAD_DEFAULT 입니다.

LOAD_CACHE_ONLY - 캐시만 사용하여 페이지를 로드합니다.
LOAD_CACHE_ELSE_NETWORK - 캐시로 먼저 로드하고 나머지를 네트워크로 로드합니다.
LOAD_DEFAULT - 캐시를 사용하지만, 서버에서 갱신된 내용이 있다면 불러옵니다.
LOAD_NO_CACHE - 항상 네트워크만 사용하여 페이지를 로드합니다.

12. javaScriptCanOpenWindiwsAutomatically = Boolean

자바 스크립트에서는 새 창을 띄울 때, window.open()을 사용합니다.
이 때 window.open()을 사용할 수 있도록 해주는 설정이 javaScriptCanOpenWindowsAutomatically입니다.
기본값은 false입니다.

13. setSupportMultipleWindows(Boolean)

WebView가 다중 창을 지원하는지 여부를 설정합니다.
true로 설정된 경우 호스트 애플리케이션에서 WebChromeClient#onCreateWindow를 구현해야 합니다.
기본값은 거짓입니다.

14. allowFileAccess = Boolean

allowFileAccess - 파일 접근 허용 설정

15.allowContentAccess = Boolean

allowContentAccess - 웹뷰를 통해 content url에 접근할지 여부

WebViewClient, WebChromeClient

둘 다 웹 뷰의 속성들을 제어하기 위해 필요한 구현체들입니다. 하지만 둘은 다릅니다.

WebViewClient : 웹 페이지를 로딩할 때 생기는 콜백 함수들로 구성되어 있습니다.
WebChromeClient : 웹 페이지에서 일어나는 액션들에 관한 콜백 함수들로 구성되어 있습니다.

WebViewClient

class BaseWebViewClient(vm: WebViewModel) : WebViewClient() {
    private var viewModel: WebViewModel? = null

    init {
        viewModel = vm
    }

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

    override fun onPageFinished(view: WebView?, url: String?) {
        super.onPageFinished(view, url)
        viewModel!!.hideProgress()
    }

    override fun shouldOverrideUrlLoading(
        view: WebView?,
        request: WebResourceRequest?
    ): Boolean {
        return super.shouldOverrideUrlLoading(view, request)
    }

    // WebView Error Control
    override fun onReceivedError(
        view: WebView?,
        request: WebResourceRequest?,
        error: WebResourceError?
    ) {

        // custom exceptionThrow. Check ERROR_*
        when (error?.errorCode) {
            ERROR_BAD_URL -> { }
            ERROR_CONNECT -> { }
            ERROR_UNKNOWN -> { }
            else -> { }
        }
        super.onReceivedError(view, request, error)
    }
}
  • onPageStarted : WebView Load가 시작될 때 호출됩니다.
  • onPageFinished : WebView Load가 끝날 때 호출됩니다.
  • onReceivedError : WebView에서 error가 발생될 때 호출됩니다.
  • shouldOverrideUrlLoading : WebView 내부에서 다른 웹 url이 호출될 때 호출됩니다.
    true를 리턴하면 현재 웹뷰가 URL 로드를 중단하고 호스트 앱이 URL을 처리합니다.
    false를 리턴하면 현재 웹뷰가 평소처럼 URL을 계속 로드합니다.
    더불어 로직을 짤 때에 다음 포스팅을 읽으면 도움이 될 것 입니다.

true를 리턴하는 상황인데 loadUrl과 같은 메서드가 포함되면, 불필요한 취소가 발생하게 됩니다.

WebChromeClient

class BaseWebChromeClient(vm: WebViewModel) : WebChromeClient() {
    private var viewModel: WebViewModel? = null

    init {
        viewModel = vm
    }

    override fun onCreateWindow(
        view: WebView?,
        isDialog: Boolean,
        isUserGesture: Boolean,
        resultMsg: Message?
    ): Boolean {
        return super.onCreateWindow(view, isDialog, isUserGesture, resultMsg)
    }

    override fun onCloseWindow(window: WebView?) {
        super.onCloseWindow(window)
    }

    // WebView Loading Check newProgress : 0 to 100
    override fun onProgressChanged(view: WebView?, newProgress: Int) {
        super.onProgressChanged(view, newProgress)
        Log.d("newProgressCheck" , "now ? $newProgress")
        viewModel!!.progressPercent(newProgress.toString())
    }

    override fun onJsAlert(
        view: WebView?,
        url: String?,
        message: String?,
        result: JsResult?
    ): Boolean {
        return super.onJsAlert(view, url, message, result)
    }

    override fun onJsConfirm(
        view: WebView?,
        url: String?,
        message: String?,
        result: JsResult?
    ): Boolean {
        return super.onJsConfirm(view, url, message, result)
    }
}
  • onCreateWindow : WebView에서 새 창을 열 때 호출합니다.
  • onCloseWindow : WebView에서 창을 닫을 때 호출합니다.
  • onProgressChanged : WebView가 loading 될 때 호출합니다.
  • onJsAlert : WebView에서 Alert()이 호출되면 호출합니다.
  • onJsConfirm : WebView에서 confirm()이 호출되면 호출합니다.

브릿지(웹 페이지와 네이티브 앱 간의 인터페이스 설정)는 어떻게 합니까

addJavascriptInterface, evaluateJavascript 이런 키워드로 검색해보면 다음과 같은 이미지를 쉽게 찾아볼 수 있습니다.

방향성에 따라 통신의 방식이 조금 다릅니다.

Android > Javascript : evaluateJavascript()

웹 뷰 사용 시, 우리는 상상해 볼 수 있습니다.
다이나믹 링크로 웹 뷰를 띄우고 특정 링크로 이동하는 과정이 있습니다.
우리는 웹 뷰를 띄워줄 수 있지만, 특정 링크로 이동시키는 것은 웹 뷰의 역할 입니다.

즉, Android 환경에서 Javascript 함수를 호출할 수 있도록 하는 것이 evaluateJavascript()입니다.
이 때 Javascript 함수는 웹 뷰 내부의 함수일 수도 있고, 새로운 스크립트 함수가 될 수도 있습니다.

> evaluateJavascript(String script, ValueCallback<String> resultCallback)
: Asynchronously evaluates JavaScript in the context of the currently displayed page.

webView.evaluateJavascript()는 Android WebView에서 Javascript 코드를 실행하기 위한 메서드 입니다.

다음 예시는 웹 뷰에 alert 창을 띄우는 예시입니다.

webViewer.evaluateJavascript("alert('Hello, World!');", null)

실행할 javascript 코드를 문자열로 넘겨줍니다. null이 위치한 두 번째 인자는 실행 결과를 받을 callback입니다.

다음은 특정 페이지로 넘어가기 위한 이벤트 예시입니다.

webView.evaluateJavascript(
   JavascriptEvent<JavascriptEvent.MoveToPage>().dispatch(
       "moveToPage", JavascriptEvent.MoveToPage(path)
    )
 ) {}
  
class JavascriptEvent<T> {
    fun dispatch(name: String, detail: T): String {
          return "window.dispatchEvent(new CustomEvent(\"${name}\", {detail: ${Gson().toJson(detail)}}));"
      }
}

evaluateJavascript() 메서드를 사용하여 JavaScript를 실행하고, 이 때 호출할 JavaScript 함수를 JavaScriptEvent라는 클래스를 사용하여 구현했습니다.
여기서 JavaScriptEvent 안 dispatch() 함수는 자바스크립트 코드를 실행시킬 값을 함수로 구현한 것입니다.

정리하자면,
1. 실행할 코드를 준비하거나, 실행할 스크립트를 준비하거나, 필요한 이벤트를 준비합니다.
2. 이를 evaluateJavascript() 하여 연결해줍니다.
3. 웹 뷰에서 웹 개발자분들이 가져다가 사용하십니다.

Android < Javascript : addJavascriptInterface()

위에서는 웹 뷰에다가 Android가 요청하는 방식이었습니다.

하지만 되려 이런 경우도 있을 수 있습니다. 웹 뷰에서 현재 디바이스의 앨범에 접근해서 이미지를 가져오고 싶을 수 있습니다.

이때는 웹 뷰에서 Android의 코드를 실행해야 합니다. 이를 가능케 해주는 것이 addJavascriptInterface() 입니다.

class AlbumInit(
    private val webViewe: WebView
) {
    @JavascriptInterface
    fun getImage(image: Bitmap) {
    //앨범 접근, 이미지 가져오기 등등
	}
}

webView.addJavascriptInterface(AlbumInit(webView),"approachAlbum")

// 웹 뷰에서 다음과 같이 가져올 수 있습니다.
approachAlbum.getImage(image)

reference :
https://developer.android.com/develop/ui/views/layout/webapps/webview#kotlin
https://heegs.tistory.com/103
https://kimdabang.tistory.com/entry
https://itssweetrain.tistory.com/5
https://gradler.tistory.com/32
https://velog.io/@jjing9/
https://kotlinworld.com/364

profile
안드안드안드

0개의 댓글