webView.evaluateJavascript("(function() { return " + command + “; })();”, new ValueCallback() {
@Override
public void onReceiveValue(String result) {
// 此处的result便是 ABC
}
});
Javascript 向 Native 发送数据
Javascript向Native发送数据有4种方式,第一种方式是借助webChromClient
中的onJsAlert()
, onJsPromot()
的方法来获取Javascript相关数据。第二种方式是采用覆盖shouldOverrideUrlLoading
方法,拦截url协议。第三种是最方便的,也就是@JavascriptInterface
方案, 现在大多数App都会用到这种方式, 后面会详细介绍。最后一种是利用在webView
中嵌入iframe
的方式,通过更新iframe
的url。比较出名的混合框架JsBridge
之前就是采用这种方式,现已改成采用@JavascriptInterface
这种方式了。以下简单介绍一下各种方式的使用。
onJsPrompt
webChromeClient
中提供了onJsAlert
, onJsPrompt
方法,方便开发者重写Javascript中的alert
, prompt
方法对应的行为。我们可以在这两个方法中任选一个做为native和js进行交互的桥梁。通常我们借助于onJsPrompt
方法来实现, 就是因为在js中,这个方法通常我们用得比较少。而对于onJsAlert()
, 当调用js中的alert()
时会触发,我们可以通过重写这个方法来实现自定义的提示View
但是这种方式对传入的数据量有限制,和手机的WebView版本有关,以我的测试机为例,在oppo reno
手机 android 10上面, 其传递数据最多只能是10k。 而用@JavascriptInterface
方案, 传递的数据最多可达20 - 30M
我们来看前端网页的写法, 直接调用prompt
函数
var data = prompt(“native://getUserInfo?id=1”);
console.log(‘data:’ + data);
在为WebView设置WebChromeClient
的时候重写onJsPrompt
方法,如下:
@Override
public boolean onJsPrompt(WebView view, String url, String message, String defaultValue, JsPromptResult result) {
Uri uri = Uri.parse(message);
//如果是调nativeAPI.
if (url.startsWith(“native://”)) {
result.confirm(“call natvie api success”);
return true;
}
return super.onJsPrompt(view, url, message, defaultValue, result);
}
shouldOverrideUrlLoading
前端页面的Js代码:
document.location=“native://getUserInfo?id=1”;
native层面在为WebView设置WebViewClient
对象时,我们需要重写shouldOverrideUrlLoading
方法。需要注意的是,WebViewClient
中有两个shouldOverrideUrlLoading
方法的定义:
public boolean shouldOverrideUrlLoading(WebView view, String url)
public boolean shouldOverrideUrlLoading(WebView view, WebResourceRequest request)
其中上面一个在sdk中已被标记Deprecated
, 下面一个是在android 7.0中才引入的,所以为了避免兼容性问题。在使用时,建议这两个方法都重写。
@Override
public boolean shouldOverrideUrlLoading(WebView view, String url) {
//如果是调nativeAPI.
if (url.startsWith(“native://”)) {
Log.i(“CommonWebViewClient”, “shouldOverrideUrlLoading execute------>”)
return true;
}
return super.shouldOverrideUrlLoading(view, url);
}
@JavascriptInterface
在 Android 4.2以下有安全漏洞, 但目前我们的app大部份最小支持版本都已经升到5.0了,这个可以忽略,当然感兴趣可以自己搜索。
在native层面,我们需为要WebView注入一个对象,用来处理两边的数据交互。注入方式如下:
- 首先定义一个类来处理两边的交互:
public class HybridAPI {
public static final String TAG = “HybridAPI”;
@JavascriptInterface
public void sendToNative(final String message) {
Log.i(TAG, “get data from js------------>” + message);
}
}
- 在
WebView
中注入这个类的实例
HybridAPI hybridAPI = new HybridAPI();
webview.addJavascriptInterface(hybridAPI, “HybridAPI”)
在网页中直接用如下代码便可以将数据发送到native端
HybridAPI.sendToNative(‘Hello’);
iframe
我们还可以利用iframe
进行请求伪造向native端发送数据的。思路是向网页中添加一个iframe
控件,通过修改其src
属性,触发native端的shouldOverrideUrlLoading
方法的执行, 同样,native端通过重写该方法,去拿到js端传过来的数据。具体操作方式如下:
var iframe = document.createElement(‘iframe’);
iframe.style.display = ‘none’;
document.documentElement.appendChild(iframe);
iframe.src=“native://getUserInfo?id=1”;
在操作完成后,我们再从当前的dom结构中移除这个组件。
setTimeout(function() {
iframe && iframe.parentNode && iframe.parentNode.removeChild(iframe);
}, 100);
具体实践
在前面总结了WebView和Native交互的几种方案。但距离实际项目使用还有一段距离,在实际项目开发中还有很多问题需要考虑。如:
- 交互的规则如何定义
- 数据如何传递
- 调用之后,如何拿到回调的结果
- 对于Javascript的请求,native端应该如何设计?
- …
native端向JavaScript发送消息只有loadUrl
, evaluateJavascript
这两种方式。Javascript向native端发送信息可以利用onJsPrompt
, @JavascriptInterface
, shouldOverrideUrlLoading
等几种方案,以下 我们通过采用@JavascriptInterface
这种方式(也就是大家通常说的注解方案)为例来看看如何解决实际项目开发中碰到的问题。
交互的规则
首先我们来定义两端的交互规则。
Javascript向native发数据:
我们约定在H5中采用HybridAPI.sendToNative
方法向native端发送数据,于是我们需要在native端做如下支持:
- 定义一个
HybridAPI
类,并向WebView中注册
HybridAPI hybridAPI = new HybridAPI(this);
webview.addJavascriptInterface(hybridAPI, “HybridAPI”);
- 在
HybridAPI
类中定义一个方法sendToNative
, 该方法暴露给Javascript用来给native发送数据
@JavascriptInterface
public void sendToNative(final String message) {
Log.i(TAG, “get data from js------------>” + message);
}
native层向Javascript发数据:
public final String TO_JAVASCRIPT_PREFIX = “javascript:HybridAPI.onReceiveData(‘%s’)”;
public void sendToJavaScript(Map<String, Object> message) {
String str = new Gson().toJson(message);
final String jsCommand = String.format(TO_JAVASCRIPT_PREFIX, escapeString(str));
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
evaluateJavascript(jsCommand, null);
} else {
loadUrl(jsCommand);
}
}
在H5中,我们这样写, 当native向Javascript发送数据时,便会触发Javascript中的Hybrid.onReceiveData
方法, 该方法就能接收到native层传过来的数据
HybridAPI.onReceiveData = function(message) {
console.log(‘[response from native]’ + message);
}
数据结构的定义
在上面我们已经基于@JavascriptInterface
方案完成了native与WebView间通信机制的实现,双方可以交换数据,但开发的时候需要考虑更多问题。比如,如果是Javascript向native发送数据,需要将数据转换成一个字符串,然后再将字符串发给native, native再去解析这个字符串,找到对应的处理方法,提取出相关的业务参数,再进行相应的处理。所以我们需要定义这个字符串的数据结构。
在上面我们已经约定了,H5端可以采用HybridAPI.sendToNative
向native发送数据,该方法只有一个字符串参数, 以获取用户信息
这个业务功能为例,我们的字符串参数是native://getUserInfo?id=1
,这个字符串中的getUserInfo
表示当前通信的目的或行为(为了拿用户信息), ?
后面的id=1
表示的是参数(用户id为1), 如果参数多了,这个字符串会更长,再如果上面涉及到中文的转码,其可读性会大大降低,所以这种交互方式不够直观和友好,我们期望用户采用下面这个方法去与native通信:
HybridAPI.invoke(methodName, params, callbackFun)
methodName
: 当前通信的行为params
: 传递的参数callbackFun
: 接收native端的返回数据
于是,我们在js层面进行一层的封装
var callbackId = 0;
var callbackFunList = {}
HybridAPI.invoke = function(method, params, callbackFun) {
var message = {
method,
params
}
if (callbackFun) {
callbackId = callbackId + 1;
message.id = ‘Hybrid_CB_’ + callbackId;
callbackFunList[callbackId] = callbackFun
}
HybridAPI.sendToNative(JSON.stringify(message));
}
最终还是调用的是sendToNative
与native层进行通信,但是采用HybridAPI.invoke
方法对开发者更加友好。
由于需要在执行成功后调用回调函数。为此在发送消息的时候先把callbackFun
保存起来,在执行成功后再响应。 当Javascript请求发送到native层时,会触发sendToNative
方法,在该方法中, 我们来解析前端的数据:
@JavascriptInterface
public void sendToNative(final String message) {
JSONObject object = DataUtil.str2JSONObject(message);
if (object == null) {
return;
}
final String callbackId = DataUtil.getStrInJSONObject(object, “id”);
final String method = DataUtil.getStrInJSONObject(object, “method”);
final String params = DataUtil.getStrInJSONObject(object, “params”);
handleAPI(method, params, callbackId);
}
private void handleAPI(String method, String params, String callbackId) {
if (“getDeviceInfo”.equals(method)) {
getDeviceInfo();
} else if (“getUserInfo”.equals(method)) {
getUserInfo();
} else if (‘login’.equals(method)) {
login();
}
…
}
native端在处理完成后,再调用evaluateJavascript
或loadUrl
方法,反馈给前端。操作流程示例:
//指定了js端的接收入口
public final String TO_JAVASCRIPT_PREFIX = “javascript:HybridAPI.onReceiveData(‘%s’)”;
public void callJs() {
Map<String, Object> responseData = new HashMap<>();
responseData.put(“error”, error);
responseData.put(“data”, result);
//回调函数的id标识,返回给js,这样才能找到对应的回调函数
responseData.put(“id”, callbackId);
sendToJavaScript(responseData);
}
public void sendToJavaScript(Map<String, Object> message) {
String str = new Gson().toJson(message);
final String jsCommand = String.format(TO_JAVASCRIPT_PREFIX, escapeString(str));
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
evaluateJavascript(jsCommand, null);
} else {
loadUrl(jsCommand);
}
}
// 转义
private String escapeString(String javascript) {
String result;
result = javascript.replace(“\”, “\\”);
result = result.replace(“”“, “\””);
result = result.replace(“'”, “\'”);
result = result.replace(“\n”, “\n”);
result = result.replace(“\r”, “\r”);
result = result.replace(“\f”, “\f”);
return result;
}
在上面的callJs
方法中组织好相关的数据,然后利用Gson
进行序列化,再转进行字符串的转义,最终调用evaluateJavascript
或者loadUrl
来传递给js。于是js端便可以利用HybridAPI.onReceiveData
来接收到。
还记得这段代码中定义的callbackFunList
吗?在上面native给js返回数据的时候,会带上一个id
, 我们可以根据这个id找到本次通信的回调函数,然后将数据回调过去。
var callbackId = 0;
var callbackFunList = {} //看这里
HybridAPI.invoke = function(method, params, callbackFun) {
var message = {
method,
params
}
if (callbackFun) {
callbackId = callbackId + 1;
message.id = ‘Hybrid_CB_’ + callbackId;
callbackFunList[callbackId] = callbackFun
}
自我介绍一下,小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。
深知大多数前端工程师,想要提升技能,往往是自己摸索成长或者是报班学习,但对于培训机构动则几千的学费,着实压力不小。自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!
因此收集整理了一份《2024年Web前端开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。
既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上前端开发知识点,真正体系化!
由于文件比较大,这里只是将部分目录截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且会持续更新!
如果你觉得这些内容对你有帮助,可以扫码获取!!(备注:前端)
最后
文章到这里就结束了,如果觉得对你有帮助可以点个赞哦,如果有需要前端校招面试题PDF完整版的朋友可以点击这里即可免费获取,包括答案解析。
由于文件比较大,这里只是将部分目录截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且会持续更新!
如果你觉得这些内容对你有帮助,可以扫码获取!!(备注:前端)
[外链图片转存中…(img-FDk8HZpf-1713454899495)]
最后
文章到这里就结束了,如果觉得对你有帮助可以点个赞哦,如果有需要前端校招面试题PDF完整版的朋友可以点击这里即可免费获取,包括答案解析。
[外链图片转存中…(img-dWyYLIZn-1713454899495)]