【Android】怎样写一个JsBridge
JsBridge
简单介绍
Android JsBridge 就是用来在 Android app的原生 java 代码与 javascript 代码中架设通信(调用)桥梁的辅助工具。
有问题请联系 xesam
原理概述
Javascript 运行在 WebView 中,而 WebView 仅仅是 Javascript 运行引擎与页面渲染引擎的一个包装而已。
因为这样的天然的隔离效应,我们能够将这样的情况与 IPC 进行类比,将 Java 与 Javascript 的每次互调都看做一次 IPC 调用。
如此一来,我们能够模仿各种已有的 IPC 方式来进行设计。比方 RPC。本文模仿 Android 的 Binder 机制来实现一个 JsBridge。
首先回想一一下基于 Binder 的经典 RPC 调用:

当然,client 与 server 仅仅是用来区分通信两方责任的叫法而已。并非一成不变的。
对于 java 与 javascript 互调的情况,当 java 主动调用 javascript 的时候,java 充当 client 角色,javascript 则扮演 server 的角色,
javascript 中的函数运行完成后回调 java 方法,这个时候。javascript 充当 client 角色,而 javascript 则承担 server 的责任。

剩下的问题就是怎么来实现这个机制了。大致有这么几个须要解决的问题:
- java 怎样调用 Javascript
- Javascript 怎样调用 java
- 方法參数以及回调怎样处理
- 通信的数据格式是怎样的
以下逐个讨论这些问题:
1. java 怎样调用 Javascript
要实现 Java 与 Javascript 的相互调用。有两条途径能够考虑:
- 集成一个定制化的 Javascript 与 Html 渲染引擎,java 通过引擎底层与 Javascript 交互。这样能够获得全然的控制权。
- 使用 Android Sdk 提供的交互方法。
对于第一种途径,代价比較大,并且技术方案比較复杂,一般仅仅有基于 Javascript 的跨平台开发方案才会这么做。
所以。如今着重考查另外一种途径。
Android 的默认 Sdk 中, Java 与 Javascript 的一切交互都是依托于 WebView 的。大致有以下几个可用方法:
第一:
webView.loadUrl("javascript:scriptString"); //当中 scriptString 为 Javascript 代码
第二,在 KITKAT 之后,又新增了一个方法:
webView.evaluateJavascript(scriptString, new ValueCallback<String>() {
@Override
public void onReceiveValue(String value) {
}
});//当中 scriptString 为 Javascript 代码,ValueCallback 的用来获取 Javascript 的运行结果。这是一个异步掉用。
这个调用看起比上面的正常。并且更像是一个方法调用。
须要注意的是,ValueCallback 并非在 UI 线程里面运行的。
2. Javascript 怎样调用 java
要实现 Javascript 调用 java 方法,须要先在 Javascript 环境中注入一个 Java 代理:
class JavaProxy{
@JavascriptInterface //注意这里的注解。出于安全的考虑。4.2 之后强制要求。不然无法从 Javascript 中发起调用
public void javaFn(){
//xxxxxx
};
}
webView.addJavascriptInterface(new JavaProxy();, "java_proxy");
然后在 Javascript 环境中直接调用 obj_proxy 代理上的方法就可以。
java_proxy.javaFn();
这里有两个方面须要统一:
- Javascript 的运行方法比較怪异,所以,我们须要将概念统一化。
- 假设须要运行的方法比較多。那么,代理对象上也须要定义非常多的方法。我们须要将各种方法定义统一起来管理。
所以。我们先将 Javascript 的运行包装成相似 java 一样的代理对象,然后通过在各自的 stub 上注冊回调来添加功能支持。
比方。假设 java 想添加 getPackageName 方法,那么。直接在 JavaProxy 上注冊就可以:
javaProxy.register("getPackageName", new JavaHandler(){
@Override
public void handle(Object value){
//xxxxx
}
})
如图:

3. 方法參数以及回调怎样处理
非常显然。不论什么 IPC 通信都涉及到參数序列化的问题。 同理 java 与 Javascript 之间仅仅能传递基础类型(注意,不单纯是基本类型),包含基本类型与字符串,不包含其它对象或者函数。
因为仅仅涉及到简单的相互调用,这里就能够考虑採用 JSON 格式来传递各种数据,轻量而简洁。
Java 调用 Javascript 没有返回值(这里指 loadUrl 形式的调用)。因此假设 java 端想从 Javascript 中获取返回值,仅仅能使用回调的形式。
可是在运行完成之后怎样找到正确的回调方法信息,这是一个重要的问题。比方有以下的样例:
在 java 环境中,JavaProxy 对象有一个无參数的 getPackageName 方法用来获取当前应用的 PackageName。
获取到 packageName 之后,传递给 Javascript 调用者的相应回调中。
在 Javascript 环境中。获取当前应用的 PackageName 的大致调用例如以下:
bridge.invoke('getPackageName', null, function(packageName){
console.log(packageName);
});
显然
function(packageName){
console.log(packageName);
}
这个 Javascript 函数是无法传递到 java 环境中的,所以,能够採取的一个策略就是,
在 Javascript 环境中将全部回调统一管理起来。而仅仅是将回调的 id 传递到 java 环境去,java 方法运行完成之后。
将回调參数以及相应的回调 id 返回给 Javascript 环境。由 Javascript 来负责运行正确的回调。
这样,我们就能够实现一个简单的回调机制:
在 java 环境中
class JavaProxy{
public void onTransact(String jsonInvoke, String jsonParam){
json = new Json(jsonInvoke);
invokeName = json.getInvokeName(); // getPackageName
callbackId = json.getCallbackId(); // 12345678xx
invokeParam = new Param(jsonParam);// null
...
...
JsProxy.invoke(callbackId, callbackParam); //发起 Javascript 调用,让 Javascript 去运行相应的回调
}
}
在 javascript 环境中
bridge.invoke = function(name, param, callback){
var callbackId = new Date().getTime();
_callbacks[callbackId] = callback;
var invoke = {
"invokeName" : name,
"callbackId" : callbackId
};
JavaProxy.onTransact(JSON.stringify(invoke), JSON.stringify(param));
}
bridge.invoke('getPackageName', null, function(packageName){
console.log(packageName);
});
反之亦然。
4. 通信的数据格式是怎样的
问题都处理了,仅仅须要设计相应的协议就可以。
依照上面的讨论,
在 client 端,我们使用:
Proxy.transact(invoke, callback);
来调用 server 端注冊的方法。
在 server 端,我们使用:
Stub.register(name, handler);
来注冊新功能,使用
Stub.onTransact(invoke, handler);
来处理接收到的 client 端调用。
当中。invoke 包含所要运行的方法以及回调的信息,因此,invoke 的设计例如以下:
{
_invoke_id : 1234,
_invoke_name : "xxx",
_callback_id : 5678,
_callback_name : "xxx"
}
注意 _invoke_id 与 _invoke_name 的差别:
假设当前 invoke 是一个直接方法调用,那么 _invoke_id 应该是无效的。
假设当前 invoke 是一个回调,那么 _invoke_id + _invoke_name 共同决定回调的具体对象
须要注意的问题
1. 回调函数须要及时删除。不然会引起内存泄漏。
因为我们使用一 Hash 来保存各自环境中的回调函数。假设某个回调因为某种原因没有被触发。那么,这个引用的对象就永远不会被回收。
针对这样的问题。处理方案例如以下:
在 Java 环境中:
假设 WebView 被销毁了,应该手动移除全部的回调,然后禁用 javascript 。
另外。一个 WebView 可能载入多个 Html 页面,假设页面的 URL 发生了改变,这个时候也应该清理全部的回调,因为 Html 页面是无状态的。也不会传递相互数据。
这里有一点须要注意的是,假设 javascript 端是一个单页面应用,应该忽略 url 中 fragment (也就是 # 后面的部分) 的变化。因为并没有发生传统意义上的页面跳转,
全部单应用的 Page 之间是可能有交互的。
在 javascript 环境中:
javascript 端情况好非常多,因为 WebView 会自己管理每一个页面的资源回收问题。
使用
必要配置
请在相应的 html 页面中引入
<script src="js-bridge.js"></script>
Java 环境
初始化 JsBridge:
jsBridge = new JsBridge(vWebView);
添加 url 监控:
vWebView.setWebViewClient(new WebViewClient() {
@Override
public void onPageFinished(WebView view, String url) {
super.onPageFinished(view, url);
Log.e("onPageFinished", url);
jsBridge.monitor(url);
}
});
Java 注冊处理方法:
jsBridge.register(new SimpleServerHandler("showPackageName") {
@Override
public void handle(String param, ServerCallback serverCallback) {
new Handler(Looper.getMainLooper()).post(new Runnable() {
@Override
public void run() {
String packageName = getPackageName();
Tip.showTip(getApplicationContext(), "showPackageName:" + packageName);
}
});
}
});
Java 在处理方法中回调 Javascript:
@Override
public void handle(final String param, final ServerCallback serverCallback) {
new Handler(Looper.getMainLooper()).post(new Runnable() {
@Override
public void run() {
User user = getUser();
Map<String, String> map = new Gson().fromJson(param, Map.class);
String prefix = map.get("name_prefix");
Tip.showTip(mContext, "user.getName():" + prefix + "/" + user.getName());
if ("standard_error".equals(prefix)) {
Map<String, String> map1 = new HashMap<>();
map1.put("msg", "get user failed");
String userMarshalling = new Gson().toJson(map1);
serverCallback.invoke("fail", new MarshallableObject(userMarshalling));
} else {
String userMarshalling = new Gson().toJson(user);
serverCallback.invoke("success", new MarshallableObject(userMarshalling));
}
}
});
}
Java 运行 Js 函数:
jsBridge.invoke("jsFn4", new MarshallableString("yellow"), new ClientCallback<String>() {
@Override
public void onReceiveResult(String invokeName, final String invokeParam) {
if ("success".equals(invokeName)) {
new Handler(Looper.getMainLooper()).post(new Runnable() {
@Override
public void run() {
Tip.showTip(getApplicationContext(), invokeParam);
}
});
}
}
@Override
public String getResult(String param) {
return param;
}
});
销毁 JsBridge
@Override
protected void onDestroy() {
super.onDestroy();
jsBridge.destroy();
}
Javascript 环境
Javascript 的灵活性比較高。所以要简单一些:
Javascript 注冊处理函数:
window.JavaBridge.serverRegister('jsFn4', function (transactInfo, color) {
log("jsFn4:" + color);
title.style.background = color;
log("jsFn4:callback");
transactInfo.triggerCallback('success', 'background change to ' + color);
});
Javascript 运行 Java 方法:
var sdk = {
getUser: function (params) {
var _invokeName = 'getUser';
var _invokeParam = params;
var _clientCallback = params;
window.JavaBridge.invoke(_invokeName, _invokeParam, _clientCallback);
}
};
sdk.getUser({
"name_prefix": "standard_error",
"success": function (user) {
log('sdk.getUser,success:' + user.name);
},
"fail": function (error) {
log('sdk.getUser,fail:' + error.msg);
}
})
具体 Demo 请參见 js-bridge-demo project
【Android】怎样写一个JsBridge的更多相关文章
- 【Android】如何写一个JsBridge
JsBridge 简介 Android JsBridge 就是用来在 Android app的原生 java 代码与 javascript 代码中架设通信(调用)桥梁的辅助工具. 原文地址点这里 gi ...
- android 开发 写一个RecyclerView布局的聊天室,并且添加RecyclerView的点击事件
实现思维顺序: 1.首先我们需要准备2张.9的png图片(一张图片为左边聊天泡泡,一个图片为右边的聊天泡泡),可以使用draw9patch.bat工具制作,任何图片导入到drawable中. 2.需要 ...
- Android 自己写一个打开图片的Activity
根据记忆中eoe的Intent相关视频,模仿,写一个打开图片的Activity 1.在主Activity的button时间中,通过设置action.category.data打开一个图片.这时代码已经 ...
- 在Android 下写一个检测软件版本号 以自动升级APP 的插件
直接上图上代码: 1.插件类的编写 工程目录结构图: 代码如下: package org.apache.cordova.versionupdate; import org.apache.cordova ...
- Android学习--写一个发送短信的apk,注意布局文件的处理过程!!!
刚开始写Android程序如图发现使用了findViewById方法之后输出的话居然是null(空指针错误),也就是说这个方法没有成功.网上说这样写是在activity_main .xml去找这个ID ...
- 用android去写一个小程序
前言: 软工的一个小作业:实现"黄金分割小游戏", 需要结对编程,队友:陈乐云 共用时两天. 早期思路设计: 采用键值对的形式,以Map作为存储结构.优点:能够将数据与用户对 ...
- Android下写一个永远不会被KILL掉的进程/服务
Android 系统对于内存管理有自己的一套方法,为了保障系统有序稳定的运信,系统内部会自动分配,控制程序的内存使用.当系统觉得当前的资源非常有限的时候,为了保证一些优先级高的程序能运行,就会杀掉一些 ...
- android开发 写一个自定义形状的按键
步骤: 1.在drawable 文件夹中创建一个xml布局文件. 2.修改布局文件 3.在需要使用背景的按键中导入布局. 创建布局文件: 修改布局文件: <?xml version=" ...
- android怎样写一个循环文字滚动的TextView
效果图: 在layout中这样来声明: <com.kaixin001.view.ScrollText android:id="@+id/news_statustxt" and ...
随机推荐
- JAVA 字节流和字符流度读写的区别
java处理文件的类里面,stream结尾都是采用字节流,reader和writer结尾都是采用字符流.两者的区别就是读写的时候一个是按字节读写,一个是按字符. 字符流的底层就是字节流.而字符流主要是 ...
- Javascript的SEO优化技巧
原文发布时间为:2010-10-22 -- 来源于本人的百度文章 [由搬家工具导入] 1.外部崁入javascript在撰写一些比较复杂的网页特效,如下拉式选单等,会产生大量的javascript码, ...
- 获取所有querystring变量名
原文发布时间为:2009-12-04 -- 来源于本人的百度文章 [由搬家工具导入] protected void Page_Load(object sender, EventArgs e) { ...
- DBus介绍
1. 介绍 DBus是一种桌面环境的进程间通讯(IPC)机制,有低时延.低消耗等优点 基于socket,提供了一对一的对等通讯:使用dbus-daemon作为后台进程时,可实现多对多通讯 由如下三个层 ...
- 《手把手教你学C语言》学习笔记(1)---C语言的特点
学习C语言的原因,主要是需要使用C语言编程,我用故我学,应该是最主要的原因了. C语言的定位:C语言严格意义上只能算是中级语言,是面向过程编程语言的集大成者,虽然这种语言有很多的问题,但总体而言是瑕不 ...
- entity framework6 edmx文件详解
entity framework中的edmx文件作为代码与数据库沟通的桥梁,作用是至关重要的.如果edmx文件出了问题,ef就基本上没得用了.虽然edmx文件是由ef自动生成的,但是一些特定的操作可能 ...
- Akka之BackoffSupervisor
一.背景 最近在开发一个项目,项目的各模块之间是使用akka grpc传输音频帧的,并且各模块中的actor分别都进行了persist.本周在开发过程中遇到了一个bug,就是音频帧在通行一段时间后,整 ...
- xss---攻击
xss表示Cross Site Scripting(跨站脚本攻击),它与SQL注入攻击类似,SQL注入攻击中以SQL语句作为用户输入,从而达到查询/修改/删除数据的目的,而在xss攻击中,通过插入恶意 ...
- php+mysql两次左外联跨表查询
代码如下: $querySel="select * from roomsy rsy left join room ro on rsy.RoomID=ro.ID left join hotel ...
- 一张图,关于 Bayes error rate,贝叶斯错误率等的分析
造轮子是那帮搞研究的“科学家”干的事情(类似E&E里的explore),“工程师”的职责是利用已有的东西解决问题(类似E&E里的exploit). 其次,即使以工程师的角色解决业务问题 ...