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

Author Avatar
发表:10月 29, 2018 更新:11月 07, 2018
字数统计:1.2k 字 阅读时长:5 分

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

需求说明

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

问题分析

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

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

<!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

<!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 资源文件中的内容:

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 资源文件:

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 文件:

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 修改为:

<!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 加载资源文件:

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 是经过混淆的话,这种方案需要在混淆文件中加入如下配置(至于为什么要这样做,暂未可知):

-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 加载资源文件:

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