Hybrid 开发已经成为一种常态了,根据百度百科释义:

Hybrid App(混合模式移动应用)是指介于 web-app、native-app 这两者之间的 app,兼具“Native App 良好用户交互体验的优势”和“Web App 跨平台开发的优势”。

尽管我对「兼具“Native App 良好用户交互体验的优势”」持保守意见,但毋庸置疑,「跨平台开发的优势」让当今市场上绝大多数的应用都使用了 Hybrid 开发技术。

Hybrid 开发方案的流行,也衍生了许多第三方框架,如 QuickHybrid 等,但更多开发者仍然选择自行实现 Hybrid。

在 Android 中,Hybrid 开发就是使用 WebView 控件加载 H5 链接,由于 Android 端使用的语言是 Java 或 Kotlin,而 H5 端使用的是 JavaSrcipt,两者没有办法直接互相调用,所以 Hybrid 开发的核心实际上就是两者之间的控制桥。

JavaScript 调用 Android

因为是在移动平台使用,所以 H5 端调用原生平台的能力更为常用。

首先需要在 Android 端创建一个提供交互方法的类,如:

public class JsInterface {
    @JavascriptInterface
    public void toast(String msg) {
        Toast.makeText(AppUtil.getContext(), msg, Toast.LENGTH_SHORT).show();
    }
}

有三个注意的点:一是方法必须添加 @JavascriptInterface 注解;二是方法的参数仅可为 JavaScript 能够提供的类型,比如上方的的 Toast 需要传入 Context 对象,这个对象是 Android 平台提供的,总不能找 H5 要吧;三是方法的返回值同样也仅能为 JavaScript 可用类型,理由同上。

然后,我们配置 WebView 使其能够调用上面的方法:

public class WebActivity extends AppCompatActivity {
    ...
    WebView webView;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        ...
        webView.getSettings().setJavaScriptEnabled(true);
        webView.addJavascriptInterface(new JsInterface(), "android");
        ...
    }
}

因为启用 JavaScript 可能会在应用程序中引入 XSS 漏洞,从而产生安全问题,所以 WebView 默认情况下是禁止 JavaScript 调用的,可以通过 setJavaScriptEnabled() 开启,然后调用 addJavascriptInterface() 将刚刚的交互类添加进来,该方法接收两个参数,第一个就是刚刚的交互类的对象,第二个参数是是 Android 端和 H5 端约定的一个字符串,通过这个约定的字符串“暗号”,能够让 H5 端通过 JavaScript 调用 Android 端提供的方法。

最后,在 H5 端调用即可:

<html>
    <script type="text/javascript">
        ...
        function toastMessage(message) {
            android.toast(message)
        }
    </script>
    ...
</html>

调用方法就是使用刚刚约定好的字符串“暗号”拼接 . 以及 Android 端提供的方法名称即可。

Android 调用 JavaScript

Android 调用 JavaScript 几乎都是通过字符串操作的,不像 JavaScript 调用 Android 一样通过类似函数的写法,所以写起来比较容易出错。

无返回值调用

首先在 H5 端创建一个能够给 Android 端调用的方法:

<html>
    <script type="text/javascript">
        ...
        function alertMessage(msg) {
            alert(msg)
        }
    </script>
    ...
</html>

然后 Android 端在合适的时机调用即可:

public class WebActivity extends AppCompatActivity {
    WebView webView;
    ...
    public void alertMessage(String msg) {
        webView.loadUrl("javascript:alertMessage('" + msg + "')";);
    }
}

同样是通过 loadUrl() 方法执行,传入 javascript: 拼接方法名称组成的字符串,需要两个点:一是如果有参数,同样要传入 JavaScript 能够处理的类型,理由同上文所述;二是假如传入的参数为字符串,可以使用单引号 ' 代替双引号 ",如果想要使用双引号 ",记得转义。

有返回值调用

由于通过 loadUrl() 方法调用 JavaScript 函数,没有办法直接获取返回值。

Android 4.4 之前并没有提供直接调用 JavaScript 函数并获取值的方法,所以在此之前,常用的思路是 Java 调用 JavaScript 方法,JavaScript 方法执行完毕后 H5 端再次调用 Android 端代码将值返回。

但是现在,有了更好的方法。

首先依然是现在 H5 端创建一个能够给 Android 端调用的方法:

<html>
    <script type="text/javascript">
        ...
        function sum(m, n) {
            return m + n
        }
    </script>
    ...
</html>

然后 Android 端在合适的时机调用即可:

public class WebActivity extends AppCompatActivity {
    WebView webView;
    ...
    public void sum(m, n) {
        webView.evaluateJavascript("sum(" + m + ", " + n + ")", new ValueCallback<String>() {
            @Override
            public void onReceiveValue(String value) {
                System.out.println(value);
            }
        });
    }
}

改为使用 evaluateJavascript() 方法执行 JavaScript 函数,并在回调中获取返回值。

有两个需要注意的点:一是回调中限定了返回值的类型为 String,对于简单的类型会尝试转换成字符串返回,对于复杂的数据类型,建议以字符串形式的 JSON 返回;二是 evaluateJavascript() 需要 UI 线程调用,因此 onReceiveValue() 回调也执行在主线程。