Android WebView与Native通信总结(1)

本文详细介绍了在实际项目中如何通过@JavascriptInterface实现WebView和Native之间的高效通信,包括交互规则定义、数据结构设计、封装方法以及如何通过反射优化处理大量接口的方法。
摘要由CSDN通过智能技术生成

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端在处理完成后,再调用evaluateJavascriptloadUrl方法,反馈给前端。操作流程示例:

//指定了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
}
HybridAPI.sendToNative(JSON.stringify(message));
}

所以,我们js端接收数据,可能是这样子:

HybridAPI.onReceiveData = function(message) {
var callbackFun = this.callbackFunList[message.id];
if (callbackFun) {
callbackFun(message.error || null, message.data);
}
delete this.callbackFunList[message.id];
}

再回到我们上面的获取用户信息这个业务功能,我们的写法就会是这样子了:

HybridAPI.invoke(‘getUserInfo’, {“id”: “1”}, function(error, data) {
if (error) {
console.log(‘获取用户信息失败’);
} else {
console.log(‘username:’ + data.username + ‘, age:’ + data.age);
}
});

至此,我们就将一具完整的数据通信流程实现了,由js端用HybridAPI.invoke(method, params, callbackFun)来向native端来发送数据,native处理完毕后,js端通过callbackFun来接收数据。

改进

在上面的java代码中,我们可以看到,native层的入口是sendToNative方法,该方法中解析传入的字符串,再交给handleAPI方法来处理

@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();
}

}

我们会发现,随着业务的发展,项目的迭代,js端可能会需要native提供越来越多的能力,所以我们的handleAPI方法中就会有越来越多的if...else if...了。

于是,我们可以按业务来划分,新建一个UserController类来处理getUserInfo, login, logout这种与用户相关的native 接口。新建一个DeviceController来处理类似于getDeviceInfo, getDeviceXXX,… 等与设备信息相关的接口。然后我们再维护一个controller list, 每次调用js api的时候从这个list里面去找对应的 controller中的方法处理。

这样,就可以把具体的业务处理方法抽取出来。然而即便这样,还是避免不了在每个Controller中去写一段这个if...else if ...这种代码。于是,其实我们可以很自然的想到用反射来做点事。

我们和H5开发约定好了,如果需要获取用户的信息,就调用getUserInfo方法,这个方法名始终不变。同时,我们在Java端这样定义UserController:

public class UserController implements IController{

private volatile static UserController instance;
private UserController() {}

public static UserController getInstance() {
if (instance == null) {
synchronized(UserController.class) {
if (instance == null) {
instance = new UserController();
}
}
}
return instance;
}

@APIMethod
public UserInfo getUserInfo(Map<String, Object> params, String callbackId) {
//TODO
}

@APIMethod
public void login(Map<String, Object> params, INativeCallback callback) {
//TODO
}

@APIMethod
public boolean logout(Map<String, Object> params, INativeCallback callback) {
//TODO
}
}

我们将该UserController添加到上面提到的controller list中,然后我们在handleAPI方法中:

private void handleNativeAPI(String methodName, String params, String callback) {
for (IController controller : controllerList) {
Method[] methods = controller.getClass().getDeclaredMethods();
自我介绍一下,小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。

深知大多数前端工程师,想要提升技能,往往是自己摸索成长或者是报班学习,但对于培训机构动则几千的学费,着实压力不小。自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!

因此收集整理了一份《2024年Web前端开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。

img

既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上前端开发知识点,真正体系化!

由于文件比较大,这里只是将部分目录截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且会持续更新!

如果你觉得这些内容对你有帮助,可以扫码获取!!(备注:前端)

最后

整理面试题,不是让大家去只刷面试题,而是熟悉目前实际面试中常见的考察方式和知识点,做到心中有数,也可以用来自查及完善知识体系。

《前端基础面试题》,《前端校招面试题精编解析大全》,《前端面试题宝典》,《前端面试题:常用算法》PDF完整版点击这里免费领取

前端面试题宝典

前端校招面试题详解

67298)]

最后

整理面试题,不是让大家去只刷面试题,而是熟悉目前实际面试中常见的考察方式和知识点,做到心中有数,也可以用来自查及完善知识体系。

《前端基础面试题》,《前端校招面试题精编解析大全》,《前端面试题宝典》,《前端面试题:常用算法》PDF完整版点击这里免费领取

[外链图片转存中…(img-eeclhUv8-1713454867298)]

[外链图片转存中…(img-zeDCTCUc-1713454867299)]

[外链图片转存中…(img-bHg0584j-1713454867299)]

  • 13
    点赞
  • 15
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值