js调用java_JsBridge实现Javascript和Java的互相调用-程序员宅基地

技术标签: js调用java  

前端网页Javascript和Native互相调用在手机应用中越来越常见,JsBridge是最常用的解决方案。

在Android开发中,能实现Javascript与Native代码通信的,有4种途径:

1.JavascriptInterface

2.WebViewClient.shouldOverrideUrlLoading()

3.WebChromeClient.onConsoleMessage()

4.WebChromeClient.onJsPrompt()

JavascriptInterface

这是Android提供的Javascript与Native通信的官方解决方案。

首先Java代码要实现这么一个类,它的作用是提供给Javascript调用。

public classJavascriptInterface {

@JavascriptInterfacepublic voidshowToast(String toast) {

Toast.makeText(MainActivity.this, toast, Toast.LENGTH_SHORT).show();

}

}

然后把这个类添加到WebView的JavascriptInterface中。

webView.addJavascriptInterface(new JavascriptInterface(), "javascriptInterface");

在Javascript代码中就能直接通过“javascriptInterface”直接调用了该Native的类的方法。

functionshowToast(toast) {

javascript:javascriptInterface.showToast(toast);

}

但是这个官方提供的解决方案在Android4.2之前存在安全漏洞。在Android4.2之后,加入了@JavascriptInterface才得到解决。所以考虑到兼容低版本的系统,JavascriptInterface并不适合。

WebViewClient.shouldOverrideUrlLoading()

这个方法的作用是拦截所有WebView的Url跳转。页面可以构造一个特殊格式的Url跳转,shouldOverrideUrlLoading拦截Url后判断其格式,然后Native就能执行自身的逻辑了。

public class CustomWebViewClient extendsWebViewClient {

@Overridepublic booleanshouldOverrideUrlLoading(WebView view, String url) {if(isJsBridgeUrl(url)) {//JSbridge的处理逻辑

return true;

}return super.shouldOverrideUrlLoading(view, url);

}

}

WebChromeClient.onConsoleMessage()

这是Android提供给Javascript调试在Native代码里面打印日志信息的API,同时这也成了其中一种Javascript与Native代码通信的方法。

在Javascript代码中调用console.log('xxx')方法。

console.log('log message that is going to native code')

就会在Native代码的WebChromeClient.consoleMessage()中得到回调。

consoleMessage.message()获得的正是Javascript代码console.log('xxx')的内容。

public class CustomWebChromeClient extendsWebChromeClient {

@Overridepublic booleanonConsoleMessage(ConsoleMessage consoleMessage) {super.onConsoleMessage(consoleMessage);

String msg= consoleMessage.message();//Javascript输入的Log内容

}

}

WebChromeClient.onJsPrompt()

其实除了WebChromeClient.onJsPrompt(),还有WebChromeClient.onJsAlert()和WebChromeClient.onJsConfirm()。顾名思义,这三个Javascript给Native代码的回调接口的作用分别是提示展示提示信息,展示警告信息和展示确认信息。鉴于,alert和confirm在Javascript的使用率很高,所以JSBridge的解决方案中都倾向于选用onJsPrompt()。

Javascript中调用

window.prompt(message, value)

WebChromeClient.onJsPrompt()就会受到回调。

onJsPrompt()方法的message参数的值正是Javascript的方法window.prompt()的message的值。

public class CustomWebChromeClient extendsWebChromeClient {

@Overridepublic booleanonJsPrompt(WebView view, String url, String message, String defaultValue, JsPromptResult result) {//处理JS 的调用逻辑

result.confirm();return true;

}

}

JsBridge

前文提到的4种通信方式都是Javascript通信Native的Java,而反过来,Java通信Javascript只有一种方式。

那就是调用WebView.loadUrl()去执行一个预先定义好的Javascript方法。

webView.loadUrl(String.format("javascript:WebViewJavascriptBridge._handleMessageFromNative(%s)", data));

接下来会结合JsBridge这个开源组件来讲解一下JsBridge的原理。

1. WebView加载html页面

webView.registerHandler("submitFromWeb", ...);这是Java层注册了一个叫"submitFromWeb"的接口方法,目的是提供给Javascript来调用。这个"submitFromWeb"的接口方法的回调就是BridgeHandler.handler()。

webView.callHandler("functionInJs", ..., new CallBackFunction());这是Java层主动调用Javascript的"functionInJs"方法。

packagecom.github.lzyzsd.jsbridge.example;public class MainActivity extends Activity implementsOnClickListener {@Overrideprotected voidonCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);

setContentView(R.layout.activity_main);

webView=(BridgeWebView) findViewById(R.id.webView);webView.loadUrl("file:///android_asset/demo.html");

webView.registerHandler("submitFromWeb", newBridgeHandler() {

@Overridepublic voidhandler(String data, CallBackFunction function) {

Log.i(TAG,"handler = submitFromWeb, data from web = " +data);

function.onCallBack("submitFromWeb exe, response data 中文 from Java");

}

});webView.callHandler("functionInJs", new Gson().toJson(user), newCallBackFunction() {

@Overridepublic voidonCallBack(String data) {}

});}}

我们一层层深入callHandler()方法的实现。这其中会调用到doSend()方法,这里想解释下callbackId。

callbackId生成后不仅仅会被传到Javascript,而且会以key-value对的形式和responseCallback配对保存到responseCallbacks这个Map里面。

它的目的,就是为了等Javascript把处理结果回调给Java层后,Java层能根据callbackId找到对应的responseCallback,做后续的回调处理。

private voiddoSend(String handlerName, String data, CallBackFunction responseCallback) {

Message m= newMessage();if (!TextUtils.isEmpty(data)) {

m.setData(data);

}if (responseCallback != null) {

String callbackStr= String.format(BridgeUtil.CALLBACK_ID_FORMAT, ++uniqueId + (BridgeUtil.UNDERLINE_STR +SystemClock.currentThreadTimeMillis()));

responseCallbacks.put(callbackStr, responseCallback);

m.setCallbackId(callbackStr);

}if (!TextUtils.isEmpty(handlerName)) {

m.setHandlerName(handlerName);

}

queueMessage(m);

}

最终可以看到是BridgeWebView.dispatchMessage(Message m)方法调用的是this.loadUrl(),调用了_handleMessageFromNative这个Javascript方法。那这个Javascript的方法是哪里来的呢?

final static String JS_HANDLE_MESSAGE_FROM_JAVA = "javascript:WebViewJavascriptBridge._handleMessageFromNative('%s');";voiddispatchMessage(Message m) {

String messageJson=m.toJson();//escape special characters for json string

messageJson = messageJson.replaceAll("(\\\\)([^utrn])", "\\\\\\\\$1$2");

messageJson= messageJson.replaceAll("(?<=[^\\\\])(\")", "\\\\\"");

String javascriptCommand=String.format(BridgeUtil.JS_HANDLE_MESSAGE_FROM_JAVA, messageJson);if (Thread.currentThread() ==Looper.getMainLooper().getThread()) {this.loadUrl(javascriptCommand);

}

}

2. 页面加载完成后会加在一段Javascript。

在WebViewClient.onPageFinished()里面的BridgeUtil.webViewLoadLocalJs(view, BridgeWebView.toLoadJs)。正是把保存在assert/WebViewJavascriptBridge.js加载到WebView中。

packagecom.github.lzyzsd.jsbridge;

public class BridgeWebViewClient extendsWebViewClient {@Overridepublic voidonPageFinished(WebView view, String url) {super.onPageFinished(view, url);if (BridgeWebView.toLoadJs != null) {

BridgeUtil.webViewLoadLocalJs(view, BridgeWebView.toLoadJs);

}// if (webView.getStartupMessage() != null) {for(Message m : webView.getStartupMessage()) {

webView.dispatchMessage(m);

}

webView.setStartupMessage(null);

}

}}

我们看看WebViewJavascriptBridge.js的代码,就能找到function _handleMessageFromNative()这个Javascript方法了。

3. 接下来分析下WebViewJavascriptBridge.js

_handleMessageFromNative()方法里面会调用_dispatchMessageFromNative()方法。

当处理来自Java层的主动调用时候会走“直接发送”的else分支。

message.callbackId会被取出来,实例化一个responseCallback,而它是用来Javascript处理完成后把结果数据回调给Java层代码的。

接着会根据message.handleName(在这个分析例子中,handleName的值就是"functionInJs")在messageHandlers这个Map去获取handler,最后交给handler去处理。

function_dispatchMessageFromNative(messageJSON) {

setTimeout(function() {var message =JSON.parse(messageJSON);varresponseCallback;//java call finished, now need to call js callback function

if(message.responseId) {

...}else{//直接发送

if(message.callbackId) {var callbackResponseId =message.callbackId;

responseCallback= function(responseData) {

_doSend({

responseId: callbackResponseId,

responseData: responseData

});

};

}var handler =WebViewJavascriptBridge._messageHandler;if(message.handlerName) {

handler=messageHandlers[message.handlerName];

}//查找指定handler

try{

handler(message.data, responseCallback);

}catch(exception) {if (typeof console != 'undefined') {

console.log("WebViewJavascriptBridge: WARNING: javascript handler threw.", message, exception);

}

}

}

});

}

4. 页面注册的"functionInJs"方法,提供给Java调用Javascript的。

延续上面的分析,messageHandler是哪里设置的呢。答案就在当初webView.loadUrl("file:///android_asset/demo.html");加载的这个demo.html中。

bridge.registerHandler("functionInJs", ...)这里注册了"functionInJs"。

connectWebViewJavascriptBridge(function(bridge) {

bridge.init(function(message, responseCallback) {

console.log('JS got a message', message);vardata={'Javascript Responds':'测试中文!'};

console.log('JS responding with', data);

responseCallback(data);

});

bridge.registerHandler("functionInJs",function(data, responseCallback) {

document.getElementById("show").innerHTML=("data from Java: =" +data);varresponseData= "Javascript Says Right back aka!";

responseCallback(responseData);

});

})

5. "functionInJs"执行完毕把结果回传给Java

"funciontInJs"执行完毕后调用的responseCallback正是_dispatchMessageFromNative()实例化的,而它实际会调用_doSend()方法。

_doSend()方法会先把Message推送到sendMessageQueue中。

然后修改messagingIframe.src,这里会出发Java层的WebViewClient.shouldOverrideUrlLoading()的回调。

function_doSend(message, responseCallback) {if(responseCallback) {var callbackId = 'cb_' + (uniqueId++) + '_' + newDate().getTime();

responseCallbacks[callbackId]=responseCallback;

message.callbackId=callbackId;

}

sendMessageQueue.push(message);

messagingIframe.src= CUSTOM_PROTOCOL_SCHEME + '://' +QUEUE_HAS_MESSAGE;

}

在BridgeWebViewClient.shouldOverrideUrlLoading()里面,会先执行webView.flushMessageQueue()的分支。

@Overridepublic booleanshouldOverrideUrlLoading(WebView view, String url) {try{

url= URLDecoder.decode(url, "UTF-8");

}catch(UnsupportedEncodingException e) {

e.printStackTrace();

}if (url.startsWith(BridgeUtil.YY_RETURN_DATA)) { //如果是返回数据

webView.handlerReturnData(url);return true;

}else if (url.startsWith(BridgeUtil.YY_OVERRIDE_SCHEMA)) { //webView.flushMessageQueue();return true;

}else{return super.shouldOverrideUrlLoading(view, url);

}

}

webView.flushMessageQueue()首先去执行Javascript的_flushQueue()方法,并附带着CallBackFunction。

Javascript的_flushQueue()方法会把sendMessageQueue中的所有message都回传给Java层。

CallBackFunction就是把messageQueue解析出来后一个一个Message在for循环中处理,也正是在for循环中,"functionInJs"的Java层回调方法被执行了。

voidflushMessageQueue() {if (Thread.currentThread() ==Looper.getMainLooper().getThread()) {

loadUrl(BridgeUtil.JS_FETCH_QUEUE_FROM_JAVA,newCallBackFunction() {

@Overridepublic voidonCallBack(String data) {//deserializeMessage

List list = null;try{

list=Message.toArrayList(data);

}catch(Exception e) {

e.printStackTrace();return;

}if (list == null || list.size() == 0) {return;

}for (int i = 0; i < list.size(); i++) {

...

}

}

});

}

}

到此,JsBridge的调用流程就分析完毕了。虽然JsBridge使用了MessageQueue后,分析起来有点绕,但原理是不变的。

Javascript调用Java是通过WebViewClient.shouldOverrideUrlLoading()。当然,还有在文章开头介绍另外3种方式。

Java调用Javascript是通过WebView.loadUrl("javascript:xxxx")。

参考:

版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://blog.csdn.net/weixin_31100705/article/details/114396289

智能推荐

Python+Selenium库的入门实例_python+selenium示例-程序员宅基地

文章浏览阅读175次。基本函数的讲解我就不列举了,网上有很多,我在写实例的时候也是参考学习了下面链接的文章,有兴趣的同学可以仔细看看。软件安装的话可以参看我的上一篇文章(不过我用的ma cOS,后面代码也可能会与windows的有一点点区别)。参考链接:https://blog.csdn.net/weixin_36279318/article/details/79475388 不多说了,直接贴上我的入门代码:from selenium import webdriverfrom sel..._python+selenium示例

Java 调用第三方接口方法_java openconnection-程序员宅基地

文章浏览阅读1.4k次。1、通过统一资源定位器(java.net.URL)获取连接器(java.net.URLConnection)。4、以输入流的形式获取返回内容。2、设置请求的参数。_java openconnection

mongodb启动成功连不上_MongoDB无法连接/认证失败-程序员宅基地

文章浏览阅读3.8k次。问题:1. 在开发工具总配置文件中配置URI后,启动程序无法连接mongodb,uri如下:spring.data.mongodb.uri : mongodb://name:password@ip:27017/dbname ,但是如下配置(无用户名和密码)可以正常连接,uri如下:spring.data.mongodb.uri : mongodb://ip:27017/dbname2. Mongo..._windows上mongodb启动成功,但可视化连不上

yum php redis,centos 7安装redis及php-redis扩展-程序员宅基地

文章浏览阅读226次。首先,下载安装redis服务端+客户端下载,解压,编译: $ wget http://download.redis.io/releases/redis-3.2.9.tar.gz $ tar xzf redis-3.2.9.tar.gz $ cd redis-3.2.9$ make #二进制文件是编译完成后在src目录下,通过下面的命令启动Redis服务: $ src/redi..._php74 yum安装redis.so

关于面试的技巧_才震宏-程序员宅基地

文章浏览阅读270次。相对应的网址https://mp.weixin.qq.com/s/UYFrb-kwdJqbEJTlvkP7SQ 程序员跳槽时,会遇到哪些套路?才震宏 GitChat 5月10日 作者简介才震宏,100offer 资深职业顾问,拥有 5 年互联网技术猎头和职业顾问经验,负责跟进候选人求职面试的全过程,擅长从候选人角度分析职位特点,提供职业规划分析,已为上百位候选人进行过面试..._才震宏

XMind8破解版|中文破解版附带密钥(全功能版本)-程序员宅基地

文章浏览阅读4.2k次。2019独角兽企业重金招聘Python工程师标准>>> ..._xmind秘钥

随便推点

dell台式机双SATA硬盘开机提示NO boot device available- Strike F1 to retryboot .F2-程序员宅基地

文章浏览阅读2.1k次。dell台式机optiplex-745,硬盘是200G,因为数据存储量增长,准备再加一块SATA硬盘,正好有一块同样是200G, 打开机箱安装上以后开机提示,NO boot device available- Strike F1 to retryboot .F2........................一开始开机按f2进入BIOS,从BIOS的device相关..._falling drive:sata0

七夕用腾讯最热门五大编程语言写三行情书-程序员宅基地

文章浏览阅读813次。前两天收到了程序员女友发来的一段话:那些周而复始的日子,现在想来最是热泪盈眶,只因有你陪伴。作为一个没怎么收到过情书的男孩纸深深被感动到了,就问她是写给我的么?她说不是,是公司搞了个三行..._编程任意三行以上有意义的文字

欢迎使用CSDN-markdown编辑器-程序员宅基地

文章浏览阅读203次。欢迎使用Markdown编辑器写博客本Markdown编辑器使用[StackEdit][6]修改而来,用它写博客,将会带来全新的体验哦:Markdown和扩展Markdown简洁的语法代码块高亮图片链接和图片上传LaTex数学公式UML序列图和流程图离线写博客导入导出Markdown文件丰富的快捷键快捷键加粗 Ctrl + B 斜体 Ctrl + I 引用

yarn、npm命令简单比较_yarn 是node包带的吗-程序员宅基地

文章浏览阅读136次。1.概要Yarn:是由Facebook、Google、Exponent 和 Tilde 联合推出了一个新的 JS 包管理工具。npm:是node自带的包管理工具。2. yarn、npm命令简单比较npm yarn 说明 npm init yarn init 初始化某个项目 npm install/link yarn install/link..._yarn 是node包带的吗

YDOOK: cannot be loaded because running scripts is disabled on this system. For more information-程序员宅基地

文章浏览阅读601次。YDOOK:cannot be loaded because running scripts is disabled on this system. For more information YDOOK JYLin1. 解决方案:以管理员方式身份运行 win10 poershell:2. 输入:set-ExecutionPolicy RemoteSigned3. 进入 shell 对话框: 输入 Y:4. 回车:5. 重新使用 npm 脚本创建 vue 项目:成功!_cannot be loaded because running scripts is disabled on this system. for mor

使用 javax.tools 创建动态应用程序_使用javax.tool创建应用程序-程序员宅基地

文章浏览阅读1.4k次。使用 javax.tools 创建动态应用程序 理解并使用 javax.tools.JavaCompiler 构建动态应用程序 _使用javax.tool创建应用程序

推荐文章

热门文章

相关标签