[Android] Web을 다루는 방법(WebView) > 자유게시판

본문 바로가기

자유게시판

[Android] Web을 다루는 방법(WebView)

페이지 정보

본문

앱에서 web을 로드하는 방법은 두 가지가 있습니다. 하나(좌측)는 브라우저(Chrome 등 디바이스에 설치되어 있는 브라우저 앱을 이용하여 url을 로드)하는 방식, 다른 하나는(우측) 현재 실행하고 있는 앱에서 url을 로드하는 방식입니다.

 

단순히 브라우저를 로드하는 방식이라면 좌측으로 진행하는 것을 권장하고 있습니다. 하지만 이 방법은 브라우저를 좀 더 세밀하게 제어할 수 없기 때문에 web간의 통신이나 UI 등 세부 작업이 필요하다면 우측 방법을 사용합니다.

 

여기서 잠깐, web을 띄우기 전에 앱에서 필요한 몇 가지 세팅이 있습니다. 가장 첫 번째는 권한을 부여하는 것입니다.

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="...">

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

인터넷 사용에 대한 권한을 사용자에게 요청하는 이유는 아래와 같습니다.

1. 보안상의 이유

2. 데이터 사용이 필요한 작업임을 사용자에게 정보 전달

 

권한 부여 뿐만 아니라 AndroidManifest.xml application단에 아래 코드가 필요합니다. 때로는 networkSecurityConfig 파일이 필요하기도 합니다(저는 여기까지 보통 작업하곤 합니다).

android:usesCleartextTraffic="true"

 

그럼 web을 사용하기 위한 기본적인 세팅이 끝났으니 먼저 좌측 방식으로 web을 띄우는 방법을 알아보겠습니다.

 

.웹 페이지를 로드하는 방법.

방법 1. Intent를 이용한 로드

private val url: String = "https://www.naver.com"

private fun openWebByIntent() {
    val intent = Intent(Intent.ACTION_VIEW, Uri.parse(url))
    startActivity(intent)
}

첫 번째 방법은 Intent를 통한 로드입니다. Intent의 action이 ACTION_VIEW일 경우 뒤의 데이터 url을 보고 적절한 액티비티 또는 앱을 실행하게 되는데요, 이때 url이 "https://'로 시작하기 때문에 웹 url임으로 인지하고 웹 브라우저를 실행하도록 액티비티를 전환하는 것입니다. 말그대로 브라우저를 오픈하는 것이기 때문에, 이후의 동작을 처리하기는 어려울 것 같고 리턴값을 받는다면 onActivityResult()를 사용할 수도 있겠네요.

 

방법 2. WebView를 이용한 로드

안드로이드에서는 웹페이지를 보여주기 위해 WebView 클래스를 제공합니다. xml에서 WebView를 생성하고, 코드상으로 웹을 로드, 로직을 처리합니다.

WebView의 형태는 url이 될 수도 있고, html 형식일 수도 있습니다. 보여줄 형태에 따라 호출해야할 함수가 상이합니다. 물론 헤더에 필요값을 map에 넣어 파라미터로 전달도 할 수 있습니다.

 

- loadUrl()

public void loadUrl(String url)
public void loadUrl(@NonNull String url, @NonNull Map<String, String> additionalHttpHeaders)

url(ex. https://www.naver.com)을 통한 로드 방식은 위 메소드를 이용합니다. 

 

- loadData()

public void loadData(@NonNull String data, @Nullable String mimeType, @Nullable String encoding)

html 형식(ex. <html> ... </html>)을 보여주고 싶을 때는 위 메소드를 이용합니다. html 코드가 담긴 string을 첫 번째 파라미터에 담습니다. 어노테이션에서 알 수 있듯이 이 값은 당연히 필수값입니다. 뒤에 따라오는 두 파라미터들은 추가하지 않아도 됩니다. 참고로 url 형식을 data에 집어넣을 경우 웹 페이지가 로드되지 않고 data가 string형으로 그대로 보여집니다.

 

- loadDataWithBaseUrl()

public void loadDataWithBaseURL(@Nullable String baseUrl, @NonNull String data, @Nullable String mimeType, @Nullable String encoding, @Nullable String historyUrl) {

이 메소드와 loadData()의 차이점은 경로가 상대 경로이냐 절대 경로이냐입니다. 로드할 url이 상대 경로인 경우, 상대 경로들의 기본 url이 baseUrl이 됩니다. historyUrl은 웹 페이지 로드 -> 다른 페이지 이동 -> 백 버튼 등을 통해 이전 화면 복귀할 때 WebView가 다시 이동할 url입니다(이 부분에 관련된 서치를 해보았으나 아직 이해가 되지 않아 조금 더 찾아봐야 할 것 같습니다. 제가 참고한 문서를 아래 첨부합니다).

 

webview.loadDataWithBaseURL("file:///android_res/drawable/", html, "text/html", "UTF-8", null);
String baseUrl    = "http://tutorials.jenkov.com";
String data       = "Relative Link";
String mimeType   = "text/html";
String encoding   = "UTF-8";
String historyUrl = "http://tutorials.jenkov.com/jquery/index.html";
webView.loadDataWithBaseURL(baseUrl, data, mimeType, encoding, historyUrl);

출처 https://stackoverflow.com/questions/29360936/what-is-baseurl-in-android-web-view

 

What is baseUrl in android web view?

In loadDataWithBaseURL method from Android WebView, there are "baseUrl" and "historyUrl". What are they used for? I have read the android documentation but still don't know what they are.

stackoverflow.com

 

제가 loadData()를 사용하다가 html 태그 형식을 로드하는 데 해당 웹 페이지가 보이지 않는 이슈가 있었습니다(동일한 data이나 AOS는 미노출, iOS는 노출). 문제점은 html 태그 내 이미지 포맷 형태에 있었는데요, 이미지 경로가 절대 경로일 때는 문제가 없었으나 상대 경로가 될 경우 보이지 않았습니다. 기존 webView.loadData()를 loadDataWithBaseUrl()로 변경하면 정상 동작하는 것을 확인하였습니다. 하지만 해당 코드일 때 이미지가 보이지 않게 스크롤을 하단으로 내렸다가 다시 이미지에 포커스가 올 경우 다시 재로드하는 듯한 모습을 발견하였습니다. 해당 코드에 delay가 걸려 있었기 때문에 이미지가 사라졌다가 다시 로드되는 추가적인 이슈가 발생하였고 메소드 변경이 아닌 html 코드를 Base64로 인코딩 후 로드하는 방식으로 해결하였습니다. 해당 이슈도 기록차 아래 코드를 남겨두도록 하겠습니다.

// 이슈 발생 코드
webView.loadData(htmlData, "text/html;charset=utf-8", "utf-8)

// 메소드 변경으로 해결한 코드
webView.loadDataWithBaseUrl(null, htmlData, "text/html;charset=utf-8", "utf-8", null)

// 인코딩을 통한 해결 코드
val encodeHtml = Base64.encodeToString(htmlData.toByteArray(), Base64.NO_PADDING)
webView.loadData(encodeHtml, "text/html;charset=utf-8", "base64")

 

.JavaScriptInterface 사용.

웹을 사용하다보면 앱과 통신을 하고, 웹에서 보낸 이벤트를 받아 앱에서 처리해야하는 경우가 필요하기도 합니다(토스트 메시지 노출 등). 이때 우리는 웹에서 이벤트를 받기 위해 JavaScriptInterface(브릿지라고도 함)을 사용합니다.

 

webView.settings.javaScriptEnabled = true

addJavascriptInterface(WebBridgeInterface(root.context), "WebBridge")

class WebBridgeInterface(private val context: Context) {
	
    @JavascriptInterface
    fun eventHandler(type: String, message: String) {
        Toast.makeText(context, "$type $message", Toast.LENGTH_SHORT).show()
    }
}

 

JavaScript 사용을 허용하고, Interface를 등록합니다. 이때 인터페이스는 클래스 파일로 하나 생성이 필요하며, addJavascriptInterface()의 두 번째 argument는 앱에서 임의로 지정하는 것이 아니라 웹과 싱크를 맞춘 네이밍이어야 합니다(인터페이스 클래스 내 메소드명도 동일).

이제 웹에서 앱에게 이벤트를 전달할 때 WebBridge.eventHandler("Success", "message: success")와 같이 전송할 수 있으며, 앱 단에서 이를 캐치하여 로직을 처리할 수 있게 됩니다.

 

브릿지 작업을 하면서 또 만난 이슈가 있었는데요, 특정 조건 만족시 web에서 이벤트를 두 번 내려주는 것이었습니다. success와 error가 함께 떨어지는 이슈였는데요, 핸들러 작업을 ui 스레드에서 동작하게 하여 해당 이슈를 해결하였습니다. 해당 이벤트 처리를 서브 스레드에서 진행한다면 sync의 문제점이 있을 것 같아 한 곳에서만 처리하도록 메인 스레드를 사용하였습니다.

class WebBridgeInterface(private val context: Context) {

    @JavascriptInterface
    fun eventHandler(type: String, message: String) {
        Handler(Looper.getMainLooper()).post {
            Toast.makeText(context, "$type $message", Toast.LENGTH_SHORT).show()
        }
    }
}

 

반대로 앱에서 웹으로 이벤트를 전송할 때는 evaluateJavascript()를 사용합니다. url을 로드하고 웹에서 리턴값을 받아야 하는 경우 해당 메소드를 사용 가능합니다.

evaluateJavascript("javascript:executeFunction()", { callback ->

})

 

.WebViewClient와 WebChromeClient.

우리는 웹과 통신을 하며 페이지 로드의 상태를 캐치하고 조작해야할 때 사용합니다. WebViewClient 클래스를 생성하여 웹 페이지의 로드, 완료, 실패 등을 캐치하여 작업을 처리할 수 있습니다. 하단 사진에서와 같이 많은 오버라이드 메소드를 통해 로직을 처리할 수 있습니다. 여기서 가장 많이 사용되는 몇 가지 메소드만 간단히 설명하겠습니다.

 

WebViewClient

해당 클라이언트를 생성하지 않고 url 로드 시, 웹 페이지가 앱 뷰에서 로드되는 것이 아니라 웹 브라우저 새 창으로 열리니 이 부분 참고가 필요합니다.

 

- onPageStart()

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

페이지가 로드될 때 해당 메소드가 한 번 호출됩니다. 

 

- shouldOverrideUrlLoading()

override fun shouldOverrideUrlLoading(
    view: WebView?,
    request: WebResourceRequest?
): Boolean

보통 리다이렉트 url을 처리할 때 사용합니다. 아래와 같이 리다이렉트 url을 보고 성공, 실패를 판단하여 로직을 처리할 수도 있습니다. 해당 메소드는 Boolean 값을 리턴하는데요, 앱이 해당 url을 캐치하여 제어할 경우 true를, 아닐 경우 false를 리턴합니다. 참고로 디폴트는 false 입니다.

override fun shouldOverrideUrlLoading(
    view: WebView?,
    request: WebResourceRequest?
): Boolean {
    val redirectUrl = request?.url.toString()
    if (redirectUrl.startsWith("redirect/success")) {
    	goToSuccessPage()
        return true
    } else if (redirectUrl.startsWith("redirect/fail")) {
    	goToFailPage()
        return true
    }
    return false
}

- onPageFinished()

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

페이지 로드가 끝났을 때 해당 메소드가 호출됩니다.

 

WebChromeClient

- onCloseWindow()

override fun onCloseWindow(window: WebView?)

웹뷰가 창을 닫을 때 메소드를 호출합니다. 창을 닫으려고 할 때 이전 화면으로 갈 수 있다면 닫는 것이 아니라 이전 화면으로 이동하도록 할 수도 있습니다.

override fun onCloseWindow(window: WebView?) {
    if (window?.canGoBack() == true) {
        window.goBack()
    }
}

 

- onCreateWindow()

override fun onCreateWindow(
    view: WebView?,
    isDialog: Boolean,
    isUserGesture: Boolean,
    resultMsg: Message?
): Boolean

웹뷰가 새 창을 열 때 호출합니다. 예를 들어 결제 또는 인증 팝업을 노출해야 하는 경우에 사용합니다. Boolean 값을 리턴하며 새 창을 열 때는 true, 아닐 때(기본값)는 false입니다.

파라미터를 함께 살펴보겠습니다. view는 해당 요청을 받은 WebView를 뜻합니다. isDialog는 띄울 웹뷰가 전체 화면인지, 다이얼로그 형태로 보여줘야 하는지를 결정합니다. 

 

여기까지 웹뷰를 이해한다면 어느정도 기본 작업이 가능합니다. 이번 WebView 공부를 하며 도움이 되었던 자료를 아래 첨부하며 포스트를 마무리하겠습니다.

 

http://tutorials.jenkov.com/android/android-web-apps-using-android-webview.html

 

Android Web Apps Using Android WebView

This tutorial explains everything you need to know about making Android web apps - apps where part of the GUI is rendered by a WebView using web technologies (HTML, CSS, JavaScript, SVG). The tutorial covers loading content from a remote server, intercepti

tutorials.jenkov.com

 

추천0 비추천0

댓글목록

등록된 댓글이 없습니다.
Total 4,536건 1 페이지
자유게시판 목록
번호 제목 글쓴이 조회 추천 비추천 날짜
4536 Ima 메일보내기 이름으로 검색 2 0 0 07:22
4535 Greta 메일보내기 이름으로 검색 2 0 0 07:02
4534 Greg 메일보내기 이름으로 검색 2 0 0 06:30
4533 Yvonn… 메일보내기 이름으로 검색 2 0 0 06:12
4532 Felix 메일보내기 이름으로 검색 2 0 0 05:46
4531 Ute 메일보내기 이름으로 검색 2 0 0 05:45
4530 Micki 메일보내기 이름으로 검색 4 0 0 04:59
4529 Lamon… 메일보내기 이름으로 검색 4 0 0 04:36
4528 Chris… 메일보내기 이름으로 검색 4 0 0 04:12
4527 Julia… 메일보내기 이름으로 검색 5 0 0 01:37
4526 Dorca… 메일보내기 이름으로 검색 5 0 0 01:10
4525 Marti… 메일보내기 이름으로 검색 19 0 0 01:05
4524 Vicky 메일보내기 이름으로 검색 6 0 0 09-30
4523 Shirl… 메일보내기 이름으로 검색 6 0 0 09-30
4522 Georg… 메일보내기 이름으로 검색 5 0 0 09-30