0%

Webview加载本地资源时的多语言国际化问题

在开发中,有时候 App 的本地资源(如离线 html 文件)等会用 Webview 去加载,如果同时需要考虑多语言国际化问题,应该如何处理呢?

需求说明

比如现在有这样一个需求,一般 App 都有相关的权限申请说明,现在要求用 Webview 去加载本地的 html 资源文件并显示相关权限声明,且支持多语言国际化。

问题分析

我们知道,res 目录可以方便进行多语言国际化的支持。那现在可以把权限声明(perm_state.html)文件放置于 /res/raw/ 的不同语言目录中,以实现国际化:

简体中文的支持:/res/raw-zh-rCN/perm_state.html

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
<!DOCTYPE HTML>
<html>
<head>
<meta charset="UTF-8"/>
<title>权限声明</title>
<style type="text/css">
body {
padding: 0 10px;
background-color: #0;
}

a.title {
font-size: 16px;
font-weight: bold;
}

a.content {
font-size: 14px;
}
</style>
</head>
<body>
<p>
<a class="title">接收短信(RECEIVE_SMS)</a><br/>
<a class="content">解析短信中的验证码需要能够接收短信权限。</a>
</p>
</body>
</html>

默认英文支持:/res/raw/perm_state.html

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
<!DOCTYPE HTML>
<html>
<head>
<meta charset="UTF-8"/>
<title>Permission Statement</title>
<style type="text/css">
body {
padding: 0 10px;
background-color: #0;
}

a.title {
font-size: 16px;
font-weight: bold;
}

a.content {
font-size: 14px;
}
</style>
</head>
<body>
<p>
<a class="title">Receive SMS permission</a><br/>
<a class="content">Receive SMS permission should be granted for parsing SMS.</a>
</p>
</body>
</html>

ResUtils#loadRawRes 用来加载 /res/raw 资源文件中的内容:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
public class ResUtils {
// ...省略

public static String loadRawRes(Context context, @RawRes int rawId) {
InputStream is = null;
String data = "";
try {
is = context.getResources().openRawResource(rawId);
byte[] buffer = new byte[is.available()];
is.read(buffer);
data = new String(buffer);
} catch (IOException e) {
XLog.e("Error occurs when open raw file, id = " + rawId, e);
} finally {
if (is != null) {
try {
is.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
return data;
}
}

接下来就是用 Webview 加载 html 资源文件:

1
2
3
4
public void loadHtmlData() {
String data = ResUtils.loadRawData(context, R.raw.perm_state);
weview.loadData(data, "text/html", "utf-8");
}

以上就是一种解决方案,那么,有没有可以优化的空间呢?

观察到,/res/raw-zh-rCN/perm_state.html/res/raw/perm_state.html 是有相同的 css 样式的,那么能否将 css 样式抽取出来共用呢?当然是可以的 ~

方案优化

css 样式抽取出来生成 perm_state_style.css 文件:

1
2
3
4
5
6
7
8
9
10
11
12
13
body {
padding: 0 10px;
background-color: #0;
}

a.title {
font-size: 16px;
font-weight: bold;
}

a.content {
font-size: 14px;
}

/res/raw-zh-rCN/perm_state.html 修改为:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
<!DOCTYPE HTML>
<html>
<head>
<meta charset="UTF-8"/>
<title>权限声明</title>
<link href="perm_state_style.css" type="text/css" rel="stylesheet"/>
</head>
<body>
<p>
<a class="title">接收短信(RECEIVE_SMS)</a><br/>
<a class="content">解析短信中的验证码需要能够接收短信权限。</a>
</p>
</body>
</html>

其他的 perm_state.html 修改方式跟上述一致就不再赘述。

WebView 有以下两个方法函数:

  • loadData(String data, String mimeType, String encoding): 直接使用 webview 加载 data 包含的数据。
  • loadDataWithBaseURL(String baseUrl, String data, String mimeType, String encoding, String historyUrl): 将 baseUrl 作为基础路径的前提下,使用 webview 加载 data 包含的数据。

我们看到 <link href="perm_state_style.css"/> 是直接引用的 perm_state_style.css,所以可以使用 loadDataWithBaseURL 方法把 css 文件放置于 baseUrl 所代表的目录下即可实现 html 文件中对 css 样式的引用和加载。

那么,perm_state_style.css 到底放哪儿呢? 有两种方案。

css 置于 res/raw 目录

css 置于 /res/raw 目录,即生成 /res/raw/perm_state_style.css

使用 WebView 加载资源文件:

1
2
3
4
pulic void loadHtmlData() {
String data = ResUtils.loadRawData(context, R.raw.perm_state);
weview.loadDataWithBaseURL("file:///android_res/raw/", data, "text/html", "utf-8", null);
}

但是如果生成的 Apk 是经过混淆的话,这种方案需要在混淆文件中加入如下配置(至于为什么要这样做,暂未可知):

1
2
3
4
-keepclassmembers class **.R$* {
public static <fields>;
}
-keep class **.R$*

这种方式 perm_state.htmlperm_state_style.css 基本在同一目录,结构清晰,但是会在最终混淆过后生成的 Apk 中保留 R.java 文件,会稍微增加 Apk 最终大小。

css 置于 assets 目录

css 置于 /assets 目录,即生成 /assets/perm_state_style.css

使用 WebView 加载资源文件:

1
2
3
4
public void loadHtmlData() {
String data = ResUtils.loadRawData(context, R.raw.perm_state);
weview.loadDataWithBaseURL("file:///android_asset/", data, "text/html", "utf-8", null);
}

这种方式 perm_state.htmlperm_state_style.css 不在同一目录,结构不太清晰,但是因为没有混淆限制,不会在最终 Apk 中生成 R.java 文件,稍微减少 Apk 最终大小。

孰优孰劣,暂未可知。

相关源码请参考个人项目 SmsCodeExtractor

参考

File under /res/raw not accessible in Debug buildvariant