인터넷을 통해 가져오는 대신 앱에 정적으로 컴파일하는 콘텐츠를 앱에 사용할 수 있도록 HTML, JavaScript, CSS와 같은 웹 기반 콘텐츠를 제공할 수 있습니다.
인앱 콘텐츠는 인터넷 액세스가 필요하지 않으며 사용자의 대역폭을 소비하지 않습니다. 콘텐츠가 WebView
전용으로 설계된 경우(즉, 네이티브 앱과의 통신에 종속됨) 사용자가 실수로 웹브라우저에 로드할 수 없습니다.
하지만 인앱 콘텐츠에는 몇 가지 단점이 있습니다. 웹 기반 콘텐츠를 업데이트하려면 새 앱 업데이트를 제공해야 하며, 사용자가 오래된 앱 버전을 사용하는 경우 웹사이트에 있는 내용과 기기의 앱에 있는 내용 간에 콘텐츠가 일치하지 않을 수 있습니다.
WebViewAssetLoader
WebViewAssetLoader
는 WebView
객체에 인앱 콘텐츠를 로드하는 유연하고 성능이 뛰어난 방법입니다. 이 클래스는 다음을 지원합니다.
- 동일 출처 정책과의 호환성을 위해 HTTP(S) URL로 콘텐츠를 로드합니다.
- 자바스크립트, CSS, 이미지, iframe과 같은 하위 리소스 로드
기본 활동 파일에 WebViewAssetLoader
를 포함합니다. 다음은 애셋 폴더에서 간단한 웹 콘텐츠를 로드하는 예입니다.
Kotlin
private class LocalContentWebViewClient(private val assetLoader: WebViewAssetLoader) : WebViewClientCompat() { @RequiresApi(21) override fun shouldInterceptRequest( view: WebView, request: WebResourceRequest ): WebResourceResponse? { return assetLoader.shouldInterceptRequest(request.url) } // To support API < 21. override fun shouldInterceptRequest( view: WebView, url: String ): WebResourceResponse? { return assetLoader.shouldInterceptRequest(Uri.parse(url)) } }
Java
private static class LocalContentWebViewClient extends WebViewClientCompat { private final WebViewAssetLoader mAssetLoader; LocalContentWebViewClient(WebViewAssetLoader assetLoader) { mAssetLoader = assetLoader; } @Override @RequiresApi(21) public WebResourceResponse shouldInterceptRequest(WebView view, WebResourceRequest request) { return mAssetLoader.shouldInterceptRequest(request.getUrl()); } @Override @SuppressWarnings("deprecation") // To support API < 21. public WebResourceResponse shouldInterceptRequest(WebView view, String url) { return mAssetLoader.shouldInterceptRequest(Uri.parse(url)); } }
앱은 필요에 맞게 WebViewAssetLoader
인스턴스를 구성해야 합니다. 다음 섹션에 예시가 있습니다.
인앱 애셋 및 리소스 만들기
WebViewAssetLoader
는 PathHandler
인스턴스를 사용하여 지정된 리소스 경로에 해당하는 리소스를 로드합니다. 이 인터페이스를 구현하여 앱의 필요에 따라 리소스를 가져올 수 있지만 Webkit 라이브러리는 각각 Android 애셋과 리소스를 로드하기 위한 AssetsPathHandler
와 ResourcesPathHandler
을 번들로 제공합니다.
시작하려면 앱의 애셋과 리소스를 만듭니다. 일반적으로 다음 사항이 적용됩니다.
- HTML, 자바스크립트, CSS 등의 텍스트 파일은 애셋에 포함됩니다.
- 이미지와 기타 바이너리 파일은 리소스에 속합니다.
프로젝트에 텍스트 기반 웹 파일을 추가하려면 다음 단계를 따르세요.
- Android 스튜디오에서 app > src > main 폴더를 마우스 오른쪽 버튼으로 클릭한 다음 New > Directory를 선택합니다.
- 폴더 이름을 'assets'로 지정합니다.
- assets 폴더를 마우스 오른쪽 버튼으로 클릭한 다음 새로 만들기 > 파일을 클릭합니다.
index.html
를 입력하고 Return 또는 Enter 키를 누릅니다. - 이전 단계를 반복하여
stylesheet.css
의 빈 파일을 만듭니다. - 다음 두 코드 샘플의 콘텐츠로 만든 빈 파일을 입력합니다.
```html
<!-- index.html content -->
<html>
<head>
<!-- Tip: Use relative URLs when referring to other in-app content to give
your app code the flexibility to change the scheme or domain as
necessary. -->
<link rel="stylesheet" href="/assets/stylesheet.css">
</head>
<body>
<p>This file is loaded from in-app content.</p>
<p><img src="/res/drawable/android_robot.png" alt="Android robot" width="100"></p>
</body>
</html>
```
```css
<!-- stylesheet.css content -->
body {
background-color: lightblue;
}
```
프로젝트에 이미지 기반 웹 파일을 추가하려면 다음 단계를 따르세요.
Android_symbol_green_RGB.png
파일을 로컬 머신에 다운로드합니다.파일 이름을
android_robot.png
로 바꿉니다.수동으로 파일을 하드 드라이브에서 프로젝트의
main/res/drawable
디렉터리로 이동합니다.
그림 4에서는 추가한 이미지와 앱에서 렌더링된 위 코드 샘플의 텍스트를 보여줍니다.
앱을 완료하려면 다음 단계를 따르세요.
핸들러를 등록하고
onCreate()
메서드에 다음 코드를 추가하여AssetLoader
를 구성합니다.Kotlin
val assetLoader = WebViewAssetLoader.Builder() .addPathHandler("/assets/", AssetsPathHandler(this)) .addPathHandler("/res/", ResourcesPathHandler(this)) .build() webView.webViewClient = LocalContentWebViewClient(assetLoader)
Java
final WebViewAssetLoader assetLoader = new WebViewAssetLoader.Builder() .addPathHandler("/assets/", new WebViewAssetLoader.AssetsPathHandler(this)) .addPathHandler("/res/", new WebViewAssetLoader.ResourcesPathHandler(this)) .build(); mWebView.setWebViewClient(new LocalContentWebViewClient(assetLoader));
다음 코드를
onCreate()
메서드에 추가하여 콘텐츠를 로드합니다.Kotlin
webView.loadUrl("https://appassets.androidplatform.net/assets/index.html")
Java
mWebView.loadUrl("https://appassets.androidplatform.net/assets/index.html");
인앱 콘텐츠와 웹사이트의 리소스 혼합
앱은 인앱 콘텐츠와 인터넷의 콘텐츠(예: 웹사이트의 CSS로 스타일이 지정된 인앱 HTML 페이지)를 혼합하여 로드해야 할 수 있습니다.
WebViewAssetLoader
은 이 사용 사례를 지원합니다. 등록된 PathHandler
인스턴스 중 지정된 경로의 리소스를 찾을 수 없으면 WebView
는 인터넷에서 콘텐츠를 로드하는 방식으로 대체됩니다. 인앱 콘텐츠와 웹사이트의 리소스를 혼합하는 경우 인앱 리소스의 디렉터리 경로(예: /assets/
또는 /resources/
)를 예약합니다. 이러한 위치에 웹사이트의 리소스를 저장하지 마세요.
Kotlin
val assetLoader = WebViewAssetLoader.Builder() .setDomain("example.com") // Replace this with your website's domain. .addPathHandler("/assets/", AssetsPathHandler(this)) .build() webView.webViewClient = LocalContentWebViewClient(assetLoader) val inAppHtmlUrl = "https://example.com/assets/index.html" webView.loadUrl(inAppHtmlUrl) val websiteUrl = "https://example.com/website/data.json" // JavaScript code to fetch() content from the same origin. val jsCode = "fetch('$websiteUrl')" + ".then(resp => resp.json())" + ".then(data => console.log(data));" webView.evaluateJavascript(jsCode, null)
Java
final WebViewAssetLoader assetLoader = new WebViewAssetLoader.Builder() .setDomain("example.com") // Replace this with your website's domain. .addPathHandler("/assets/", new AssetsPathHandler(this)) .build(); mWebView.setWebViewClient(new LocalContentWebViewClient(assetLoader)); String inAppHtmlUrl = "https://example.com/assets/index.html"; mWebView.loadUrl(inAppHtmlUrl); String websiteUrl = "https://example.com/website/data.json"; // JavaScript code to fetch() content from the same origin. String jsCode = "fetch('" + websiteUrl + "')" + ".then(resp => resp.json())" + ".then(data => console.log(data));"; mWebView.evaluateJavascript(jsCode, null);
웹 호스팅 JSON 데이터를 가져오는 인앱 HTML 페이지의 예시는 GitHub의 WebView
데모를 참고하세요.
loadDataWithBaseURL
앱이 HTML 페이지만 로드해야 하고 하위 리소스를 가로채지 않아도 되는 경우 앱 애셋이 필요하지 않은 loadDataWithBaseURL()
를 사용하는 것이 좋습니다. 다음 코드 샘플에서와 같이 이를 사용할 수 있습니다.
Kotlin
val html = "<html><body><p>Hello world</p></body></html>" val baseUrl = "https://example.com/" webView.loadDataWithBaseURL(baseUrl, html, "text/html", null, baseUrl)
Java
String html = "<html><body><p>Hello world</p></body></html>"; String baseUrl = "https://example.com/"; mWebView.loadDataWithBaseURL(baseUrl, html, "text/html", null, baseUrl);
인수 값은 신중하게 선택하세요. 다음을 고려하세요.
baseUrl
: HTML 콘텐츠가 로드되는 URL입니다. HTTP(S) URL이어야 합니다.data
: 문자열로 표시하려는 HTML 콘텐츠입니다.mimeType
: 일반적으로text/html
로 설정해야 합니다.encoding
:baseUrl
가 HTTP(S) URL인 경우 사용되지 않으므로null
로 설정할 수 있습니다.historyUrl
:baseUrl
와 동일한 값으로 설정됩니다.
앱이 동일 출처 정책을 준수하도록 하려면 HTTP(S) URL을 baseUrl
로 사용하는 것이 좋습니다.
콘텐츠에 적합한 baseUrl
를 찾을 수 없고 loadData()
를 사용하려는 경우 백분율 인코딩 또는 Base64 인코딩으로 콘텐츠를 인코딩해야 합니다.
다음 코드 샘플과 같이 Base64 인코딩을 선택하고 Android API를 사용하여 프로그래매틱 방식으로 인코딩하는 것이 좋습니다.
Kotlin
val encodedHtml: String = Base64.encodeToString(html.toByteArray(), Base64.NO_PADDING) webView.loadData(encodedHtml, mimeType, "base64")
Java
String encodedHtml = Base64.encodeToString(html.getBytes(), Base64.NO_PADDING); mWebView.loadData(encodedHtml, mimeType, "base64");
피해야 할 사항
인앱 콘텐츠를 로드하는 데는 여러 가지 다른 방법이 있지만 다음과 같은 방법을 사용하지 않는 것이 좋습니다.
file://
URL과data:
URL은 불투명 출처로 간주되므로fetch()
또는XMLHttpRequest
와 같은 강력한 웹 API를 활용할 수 없습니다.loadData()
는 내부적으로data:
URL을 사용하므로 대신WebViewAssetLoader
또는loadDataWithBaseURL()
를 사용하는 것이 좋습니다.WebSettings.setAllowFileAccessFromFileURLs()
및WebSettings.setAllowUniversalAccessFromFileURLs()
로file://
URL 문제를 해결할 수 있지만true
로 설정하면 앱이 파일 기반 악용에 취약해질 수 있으므로 설정하지 않는 것이 좋습니다. 보안을 강화하기 위해 모든 API 수준에서 이를false
로 명시적으로 설정하는 것이 좋습니다.- 같은 이유로
file://android_assets/
및file://android_res/
URL을 사용하지 않는 것이 좋습니다.AssetsHandler
및ResourcesHandler
클래스는 드롭인 교체를 위한 것입니다. MIXED_CONTENT_ALWAYS_ALLOW
를 사용하지 않습니다. 이 설정은 일반적으로 필요하지 않으며 앱의 보안을 약화시킵니다. 웹사이트의 리소스와 동일한 스키마(HTTP 또는 HTTPS)를 통해 인앱 콘텐츠를 로드하고MIXED_CONTENT_COMPATIBILITY_MODE
또는MIXED_CONTENT_NEVER_ALLOW
를 적절하게 사용하는 것이 좋습니다.