HTML、JavaScript、CSS などのウェブベースのコンテンツをアプリに提供して、インターネット経由で取得するのではなく、それをアプリに静的にコンパイルできます。
アプリ内コンテンツでは、インターネット アクセスは必要なく、ユーザーの帯域幅を消費しません。コンテンツが WebView
専用に設計されている(つまり、ネイティブ アプリとの通信に依存している)場合は、ユーザーが誤ってウェブブラウザでそのコンテンツを読み込むことはありません。
ただし、アプリ内コンテンツにはいくつかのデメリットがあります。ウェブベースのコンテンツを更新するには、新しいアプリのアップデートを送信する必要があります。ユーザーが古いバージョンのアプリを使用している場合、ウェブサイトの内容とデバイス上のアプリの内容の間でコンテンツが一致しなくなる可能性があります。
WebViewAssetLoader
WebViewAssetLoader
は、アプリ内コンテンツを WebView
オブジェクトに読み込む、柔軟かつ効率的な方法です。このクラスでは、以下がサポートされます。
- 同一生成元ポリシーとの互換性を確保するために、HTTP(S) URL を使用してコンテンツを読み込む。
- JavaScript、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、JavaScript、CSS などのテキスト ファイルはアセットに含まれます。
- イメージなどのバイナリ ファイルはリソースに属します。
プロジェクトにテキストベースのウェブ ファイルを追加する手順は次のとおりです。
- Android Studio で、[app] > [src] > [main] フォルダを右クリックし、[New] > [Directory] を選択します。
- フォルダに「assets」という名前を付けます。
- assets フォルダを右クリックし、[New] > [File] をクリックします。
「
index.html
」と入力して、Return キーまたは Enter キーを押します。 - 前の手順を繰り返して、
stylesheet.css
に空のファイルを作成します。 - 作成した空のファイルに、次の 2 つのコードサンプルの内容を追加します。
```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
と同じ値に設定されます。
アプリにおいて同一オリジン ポリシーに準拠するため、baseUrl
には HTTP(S) URL を使用することを強くおすすめします。
コンテンツに適した 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()
を使用することをおすすめします。file://
URL に関する問題はWebSettings.setAllowFileAccessFromFileURLs()
とWebSettings.setAllowUniversalAccessFromFileURLs()
で回避できますが、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
を使用することをおすすめします。