※iOSのWKWebViewでローカルのフォントを読み込む方法はこちら。
AndroidのWebViewでフォントをローカルに保持して利用する方法についての記録です。
背景
通常のWebサイトを表示するのと同様にサーバからフォントを取得しても良いのですが、表示を高速化したいときはフォント(や画像)をローカルから読み込むことも有力な手段です。
遥か昔から「WebViewにローカルファイルを読み込ませるにはどうしたら良いか」が検討されており、次のような方法が考案されてきました。
- HTMLにfile:// を記述してローカルファイルを読み込む
- Android内にローカルサーバを用意しコンテンツを送信( webview-local-server )
しかし、現在は WebViewAssetLoader がGoogleに推奨されている方法となっています。
プロジェクトのセットアップ
今回は「TestFont」というプロジェクト名で新規プロジェクトを作成しました。
WebViewAssetLoader を利用するために、/app/build.gradle の dependencies にimplementationを1つ追加します。
implementation 'androidx.webkit:webkit:1.2.0'
ローカルに配置するフォントファイルの準備をします。Google FontsからNoto_Sans_JPをダウンロードしてzipを解凍します。(NotoSansJP-Bold.otf も入っています。)
/app/src/main に assets/font ディレクトリを作成し、ここに NotoSansJP-Bold.otf を配置します。
/app/src/main/assets/font/NotoSansJP-Bold.otf
この時点でプロジェクトを実行して、空のアクティビティが表示されることを確認しましょう。
WebViewで読み込むHTMLの作成
HTMLはサーバから取得し、ローカルのフォントを利用することを目指しているので、それを実現するHTMLを用意します。
例えば、次のような hello.html を作成します。
<html>
<head>
<style>
@font-face {
font-family: "myFont";
src: url('/hogehoge/font/NotoSansJP-Bold.otf') format('opentype');
}
body{
font-family: "myFont";
}
</style>
</head>
<body>
<h1>Hello World!</h1>
<p>I am Jon. I like sushi.</p>
</body>
</html>
このHTMLファイルのポイントは、font-familyの「src: url(“/hogehoge/font/NotoSansJP-Bold.otf”)」です。 後ほど、このhogehogeをフックしてローカルファイルを読み込むようにします。
テスト用のHTMLを用意したら、適当なファイルサーバにアップロードしておきます。私はS3にアップロードしてテストしたので、次のようなURLになりました。
https://s3-ap-northeast-1.amazonaws.com/XXXXXX/test/hello.html
WebViewAssetLoaderを使ったWebViewの実装
ようやくWebViewへの組み込み方です。(WebViewをセットしたActivityを用意してください。)
下記のようにWebViewAssetLoader を利用できますが、ポイントが2点あります。
- setDomainでフックしたいドメインを定義すること
- addPathHandler で置き換えるパスを定義すること
今回は /hogehoge/ を /app/src/main/assets/ に置換する記述になっています。
class WebViewActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_web_view)
val assetLoader: WebViewAssetLoader = WebViewAssetLoader.Builder()
.setDomain("s3-ap-northeast-1.amazonaws.com")
.addPathHandler("/hogehoge/", WebViewAssetLoader.AssetsPathHandler(this))
//.addPathHandler("/res/", WebViewAssetLoader.ResourcesPathHandler(this))
.build()
val webView = findViewById(R.id.webview)
webView.webViewClient = object : WebViewClient() {
override fun shouldInterceptRequest(
view: WebView,
request: WebResourceRequest
): WebResourceResponse? {
return assetLoader.shouldInterceptRequest(request.url)
}
}
webView.loadUrl("https://s3-ap-northeast-1.amazonaws.com/XXXXXX/test/hello2.html")
}
}
これで実行した結果が次のようになります。
Notoフォントが当たってますね!!
いきなりフォントが変わったと言われても分からないと思いますので、ローカルの/assets/font/NotoSansJP-Bold.otf を設置しなかったときの画像も以下に掲載いたします(笑)。
以上、WebViewAssetLoader のデモでした。
追記:文字列のHTMLを読み込んでローカルフォントを利用する場合
loadDataWithBaseURL を使って、HTMLを直接加工する方法もあります。この場合、font-familyのurlのURLとbaseUrlを揃えて指定することがポイントになります。
サンプルコードは次の通り。
/**
* 文字列のHTMLを読み込んでローカルフォントを利用する場合
*/
private fun loadFromLocal(){
val assetLoader: WebViewAssetLoader = WebViewAssetLoader.Builder()
.setDomain("s3-ap-northeast-1.amazonaws.com")
.addPathHandler("/hogehoge/", WebViewAssetLoader.AssetsPathHandler(this))
//.addPathHandler("/res/", WebViewAssetLoader.ResourcesPathHandler(this))
.build()
val webView = findViewById<WebView>(R.id.webview)
webView.webViewClient = object : WebViewClient() {
override fun shouldInterceptRequest(
view: WebView,
request: WebResourceRequest
): WebResourceResponse? {
return assetLoader.shouldInterceptRequest(request.url)
}
}
val content = """
<html>
<head>
<style>
@font-face {
font-family: "myFont";
src: url('https://s3-ap-northeast-1.amazonaws.com/hogehoge/font/NotoSansJP-Bold.otf') format('opentype');
}
body{
font-family: "myFont";
}
</style>
</head>
<body>
<h1>Hello World!</h1>
<p>I am Jon. I like sushi.</p>
</body>
</html>
"""
webView.loadDataWithBaseURL("https://s3-ap-northeast-1.amazonaws.com", content, "text/html", "UTF-8", null);
}
以上、ローカルのHTMLでローカルのフォントを参照するデモでした。