前言
现在业务稍微大一点的公司, 基本上都会引入 android 与 H5 交互的方式开发, 或者是引入 Hybrid 框架, 更有甚者直接全部采用 Js 开发成 web App 形式, 就是看中其开发成本更低 (跨平台), 更新风险更小的优势目前移动端开发市场的遇冷, 除了 android 初级人才过多之外, 还有就是前端技术的崛起, 挤占了 native 开发的空间, 不过仔细想想, 在互联网的意义上, 移动端的 App 其实也属于前端所以顺应技术的浪潮, 拥抱变化才能使自己立于不败之地附上源码地址
界面展示
图中的上半部分是 android 原生界面, 下半部分是 webview 加载 html 的页面, 可以看到, 两边可以相互传递参数, 并且调用对方的代码块了下面我把完成的代码先贴出来, 有基础的同学可以直接 copy 源码然后自己调试看看, 没基础的同学别急, 听我一个一个解析
Android 调用 Js
通过 WebView 有 loadUrl() 和
evaluateJavascript()
两种方法调用 Js 方法
这里采用加载本地 assets 中的 html 文件进行调试
1loadUrl() 方式
- JsMethod.html
- <html>
- <head>
- <meta http-equiv="Content-Type" charset="UTF-8"/>
- <script type="text/javascript">
- var s = '我来自 Js 方法';
- function javatojscallback(param){
- document.getElementById("textshow").innerHTML = (param);
- //window.android.JsToJavaInterface(s)
- }
- </script>
- </head>
- <body>
- <h3>Js Method</h3>
- <h3 id="textshow"> 调用结果 </h3>
- </body>
- </html>
- window.android.JsToJavaInterface(s)
是 Js 调用 android 的方法, 由于 loadUrl() 不能从 Js 返回数据, 可以让 Js 回调 android 的方法回传参数
- MainActivity.java
- ...
- private void initView() {
- javaMethod = new JavaMethod(this);
- webView = new WebView(this);
- WebSettings settings = webView.getSettings();
- settings.setDomStorageEnabled(true);
- settings.setJavaScriptEnabled(true);
- settings.setBlockNetworkImage(false);
- frameLayout.addView(webView);
- webView.loadUrl("file:///android_asset/JsMethod.html");
- }
- ...
调用 Js
- webView.loadUrl("javascript:javatojscallback('我来自 Java')");
- 2evaluateJavascript()
- <html>
- <head>
- <meta http-equiv="Content-Type" charset="UTF-8"/>
- <script type="text/javascript">
- var s = '我来自 Js 方法';
- function javatojswith(param){
- document.getElementById("textshow").innerHTML = (param);
- return s;
- }
- </script>
- </head>
- <body>
- <h3>Js Method</h3>
- <h3 id="textshow"> 调用结果 </h3>
- </body>
- </html>
调用 Js
- webView.evaluateJavascript("javascript:javatojswith('我来自 Java')",
- new ValueCallback<String>() {
- @Override
- public void onReceiveValue(String s) {
- textShow.setText(s);
- }
- });
相信已经大家已经注意到, 被调用的 Js 方法是有返回值的, 如果是采用 loadUrl() 调用, 返回值也会用 loadUrl() 载入, 直接显示在 WebView 上, 这显然是不对的, 我们只想隐形的接收返回值, 而
evaluateJavascript()
就提供了这样的隐形接收方式, 不会调用到 loadUrl()
需要注意的是,
evaluateJavascript()
只能在 android 4.4 之后才能调用
Js 调用 Android
Js 通过 WebView 有三种方式调用 android 方法
- 1addJavascriptInterface
- <html>
- <head>
- <meta http-equiv="Content-Type" charset="UTF-8"/>
- <script type="text/javascript">
- </script>
- </head>
- <body>
- <h3>Js Method</h3>
- <h3 id="textshow"> 调用结果 </h3>
- <input type="button" value="JavascriptInterface" onclick="window.android.JsToJavaInterface('我来自 Js')"/>
- </body>
- </html>
- JavaMethod.java
- public class JavaMethod {
- private MainActivity mainActivity;
- private Handler uiHandler;
- public JavaMethod(MainActivity mainActivity) {
- this.mainActivity = mainActivity;
- uiHandler = new Handler(Looper.getMainLooper());
- }
- @JavascriptInterface
- public void JsToJavaInterface(final String param) {
- uiHandler.post(new Runnable() {
- @Override
- public void run() {
- mainActivity.setTextShow("from JavaInterface:" + param);
- }
- });
- }
- }
这里我把 Js 调用 Java 的方法分离出来到一个 JavaMethod 类中, 然后通过
Looper.getMainLooper()
获取主线程 Handler, 统一采用接口形式更新界面
- MainActivity.java
- ...
- private void initView() {
- ...
- settings.setJavaScriptEnabled(true);
- webView.addJavascriptInterface(javaMethod,"android");
- frameLayout.addView(webView);
- webView.loadUrl("file:///android_asset/JsMethod.html");
- }
- public void setTextShow(String str) {
- textShow.setText(str);
- }
- ...
在 android4.2 之前有个严重漏洞, Js 通过 webview 获取 android 对象后, 可以调用到其他系统方法, 为了避免这个漏洞, 在 4.2 之后, 只能调用到
@JavascriptInterface
注释过的方法
2shouldOverrideUrlLoading
通过 WebViewClient 中的
shouldOverrideUrlLoading
拦截 url, 制定一个对应协议
- <html>
- <head>
- <meta http-equiv="Content-Type" charset="UTF-8"/>
- <script type="text/javascript">
- </script>
- </head>
- <body>
- <h3>Js Method</h3>
- <h3 id="textshow"> 调用结果 </h3>
- <input type="button" value="shouldOverrideUrlLoading" onclick="document.location ='js://jstojava?arg1=1 号参数 & arg2=2 号参数'"/>
- </body>
- </html>
- JavaMethod.java
- ...
- public WebViewClient getWebViewClient() {
- WebViewClient webViewClient = new WebViewClient(){
- @Override
- public boolean shouldOverrideUrlLoading(WebView view, String url) {
- Uri uri = Uri.parse(url);
- // 一般根据 scheme(协议格式) & authority(协议名) 判断
- // url = "js://jstojava?arg1=1&arg2=2"
- if(uri.getScheme().equals("js")) {
- if(uri.getAuthority().equals("jstojava")) {
- final String param1 = uri.getQueryParameter("arg1");
- final String param2 = uri.getQueryParameter("arg2");
- uiHandler.post(new Runnable() {
- @Override
- public void run() {
- mainActivity.setTextShow("arg1="+param1+"arg2="+param2);
- }
- });
- }
- return true;
- }
- return super.shouldOverrideUrlLoading(view, url);
- }
- };
- return webViewClient;
- }
- ...
- MainActivity.java
- ...
- private void initView() {
- javaMethod = new JavaMethod(this);
- webView = new WebView(this);
- WebSettings settings = webView.getSettings();
- settings.setDomStorageEnabled(true);
- settings.setJavaScriptEnabled(true);
- settings.setBlockNetworkImage(false);
- webView.setWebViewClient(javaMethod.getWebViewClient());
- webView.addJavascriptInterface(javaMethod,"android");
- frameLayout.addView(webView);
- webView.loadUrl("file:///android_asset/JsMethod.html");
- }
- ...
这种方式没有版本限制和漏洞, 不过没有返回值, 如果 Js 调用后需要 android 返回就得使用 loadUrl() 或者
evaluateJavascript()
回传对应的接收方法了值得一提的是, 这种方式便于和 IOS 通用一套协议, 简便 Js 端的代码量
3onJsAlert()onJsConfirm()onJsPrompt()
通过 WebChromeClient 中的 onJsAlert()onJsConfirm()onJsPrompt() 拦截 Js 中的 alert()confirm()prompt() 消息而 alertconfirmprompt 代表 Js 中三种常用提示框, 第一种没有返回值, 第二种返回布尔值, 第三种可返回任意值由于考虑到灵活性, 所以我们可以直接实现对 prompt 的拦截即可
- <html>
- <head>
- <meta http-equiv="Content-Type" charset="UTF-8"/>
- <script type="text/javascript">
- function jstojavaprompt(param){
- result = prompt(param);
- document.getElementById("textshow").innerHTML = (result);
- }
- </script>
- </head>
- <body>
- <h3>Js Method</h3>
- <h3 id="textshow"> 调用结果 </h3>
- <input type="button" value="onJsPrompt" onclick="jstojavaprompt('js://jstojava?arg3=3 号参数 & arg4=4 号参数')"/>
- </body>
- </html>
- JavaMethod.java
- ...
- public WebChromeClient getWebChromeClient() {
- WebChromeClient webChromeClient = new WebChromeClient(){
- @Override
- public boolean onJsPrompt(WebView view, String url, String message, String defaultValue, final JsPromptResult result) {
- Uri uri = Uri.parse(message);
- if(uri.getScheme().equals("js")) {
- if(uri.getAuthority().equals("jstojava")) {
- final String param3 = uri.getQueryParameter("arg3");
- final String param4 = uri.getQueryParameter("arg4");
- uiHandler.post(new Runnable() {
- @Override
- public void run() {
- mainActivity.setTextShow("arg3="+param3+"arg4="+param4);
- result.confirm("我来自 onJsPrompt");
- }
- });
- }
- return true;
- }
- return super.onJsPrompt(view, url, message, defaultValue, result);
- }
- };
- return webChromeClient;
- }
- ...
- MainActivity.java
- ...
- private void initView() {
- javaMethod = new JavaMethod(this);
- webView = new WebView(this);
- WebSettings settings = webView.getSettings();
- settings.setDomStorageEnabled(true);
- settings.setJavaScriptEnabled(true);
- settings.setBlockNetworkImage(false);
- webView.setWebChromeClient(javaMethod.getWebChromeClient());
- webView.addJavascriptInterface(javaMethod,"android");
- frameLayout.addView(webView);
- webView.loadUrl("file:///android_asset/JsMethod.html");
- }
- ...
协议的方式与
shouldOverrideUrlLoading
拦截 url 时类似, 在对应线程处理完业务后, 可将结果通过 result.confirm() 返回给 Js
总结
以上的交互方法各有利弊, 主要是由于 android 版本的限制, 没有版本限制的方法稍显麻烦, 但是通用, 一劳永逸, 大家可以从业务覆盖的机型来考虑引入哪种方式来与 Js 交互
源码地址
源码已经集成文中的回调方式, 感兴趣的同学可以看下
最后附上一句鸡汤, 希望大家能时刻让自己保持坚强, 晚安
生活不会为谁放慢节奏, 我们只能提起精神, 抹着泪, 踉跄着追上生活的步伐
来源: https://juejin.im/post/5a806fba6fb9a06348535c9f