点击上方“Android技术杂货铺”,选择“标星”
干货文章,第一时间送达!
封面/pixbay本文来自 张志强
链接:http://zzqhost.com/2018/03/10/Android中JSBridge的原理与实现/ 首先我们来了解一下什么是JSBridge和为什么要使用JSBridge?
在开发中,为了追求开发的效率以及移植的便利性,一些展示性强的页面我们会偏向于使用h5来完成,功能性强的页面我们会偏向于使用native来完成,而一旦使用了h5,为了在h5中尽可能的得到native的体验,我们native层需要暴露一些方法给js调用,比如,弹Toast提醒,弹Dialog,分享等等,有时候甚至把h5的网络请求放到native去完成。
JSBridge做得好的一个典型就是微信,微信给开发者提供了JSSDK,该SDK中暴露了很多微信native层的方法,比如支付,定位等。
本文将对js和Native的通信原理和实现方法的一些探讨。
实现JSBridge关键点的原理剖析Android中的JSBridge是H5与Native通信的桥梁,其作用是实现H5与Native间的双向通信。要实现H5与Native的双向通信,解决如下四个问题即可:
1、Java如何调用JavaScript
2、JavaScript如何调用Java
3、方法参数以及回调如何处理
4、通信协议的制定
下面从以上问题依次开始讨论
Java如何调用JavaScript在WebView中,如果java要调用js的方法,是非常容易做到的,使用- WebView.loadUrl(“javascript:function()”)
复制代码 即可,这样,就做到了的native层调用h5层的单向通信- WebView.loadUrl("javascript:function()");
复制代码 JavaScript如何调用Javajs调用Android的方法有以下四种:
1、WebView 的2、- WebViewClient.shouldOverrideUrlLoading()
复制代码 3、- WebChromeClient.onConsoleMessage()
复制代码 4、- WebChromeClient.onJsPrompt()
复制代码 、、我们先对此四种方案进行一个详细的描述,最后选择一个方案即可。本文章中采用了第四种方案。
JavascriptInterfaceJavascriptInterface是Android官方提供的js和Native通信方案。其实现如下:
1、实现一个java类,供js调用- public class MyJavascriptInterface {
- @JavascriptInterface
- public void showToast(String toast) {
- Toast.makeText(MainActivity.this, toast, Toast.LENGTH_SHORT).show();
- }
- }
复制代码 2、在webView中注册这个类- webView.addJavascriptInterface(new MyJavascriptInterface(), "javascriptInterface");
复制代码 3、在js中直接调用这个接口:- function showToast(text){
- window.javascriptInterface.showToast(text);
- }
复制代码 4、总结
大多数人都知道WebView存在一个漏洞,见WebView中接口隐患与手机挂马利用,虽然该漏洞已经在Android 4.2上修复了(即使用代替,但是由于兼容性和安全性问题,基本上我们不会再利用Android系统为我们提供的方法或者注解来实现,所以我们只能另辟蹊径,去寻找既安全,又能实现兼容Android各个版本的方案。
WebViewClient.shouldOverrideUrlLoading()这个方法是拦截所有webView的跳转,页面可以构造一个特殊格式的Url跳转,拦截Url后判断其格式,然后Native就能执行自身的逻辑了。- public class CustomWebViewClient extends WebViewClient {
- @Override
- public boolean shouldOverrideUrlLoading(WebView view, String url) {
- if (isJsBridgeUrl(url)) {
- // JSbridge的处理逻辑
- return true;
- }
- return super.shouldOverrideUrlLoading(view, url);
- }
- }
复制代码 WebChromeClient.onConsoleMessage()在js中执行, 会进入Android的- WebChromeClient.consoleMessage()
复制代码 回调。- public class CustomWebChromeClient extends WebChromeClient {
- @Override
- public boolean onConsoleMessage(ConsoleMessage consoleMessage) {
- super.onConsoleMessage(consoleMessage);
- String msg = consoleMessage.message();//Javascript输入的Log内容
- }
- }
复制代码 WebChromeClient.onJsPrompt()1、在WebView有一个方法,叫,可以设置对象,而这个对象中有三个方法,分别是,,,当js调用window对象的对应的方法,即,,,对象中的三个方法对应的就会被触发,那这三个方法到底要使用哪个呢?
2、这三个方法的区别,可以详见消息框 。
3、一般来说,我们是不会使用的,为什么呢?因为js中alert使用的频率还是非常高的,一旦我们占用了这个通道,alert的正常使用就会受到影响,而和的使用频率相对来说,则更低一点。
4、那么到底是选择还是呢,其实的使用频率也是不低的,比如你点一个链接下载一个文件,这时候如果需要弹出一个提示进行确认,点击确认就会下载,点取消便不会下载,类似这种场景还是很多的,因此不能占用。
5、而prompt则不一样,在Android中,几乎不会使用到这个方法,就是用,也会进行自定义,所以我们完全可以使用这个方法。该方法就是弹出一个输入框,然后让你输入,输入完成后返回输入框中的内容。因此,占用是再完美不过了。- public class CustomWebChromeClient extends WebChromeClient {
- @Override
- public boolean onJsPrompt() {
- super.onJsPrompt();
- ...
- }
- }
- myWebView.setWebChromClient(new CustomWebChromeClient());
复制代码 方法参数以及回调处理1、任何IPC通信都涉及到参数序列化的问题,同理,Java与JavaScript之间只能传递基础类型(包括基本类型和字符串),不包括其他对象或者函数。所以可以采用json格式来传递数据。
2、为了实现异步返回结果,所以JavaScript与Java相互调用不能直接获取返回值,只能通过回调的方式来获取返回结果。
通信协议的制定要进行正常的通信,通信协议的制定是必不可少的。我们回想一下熟悉的http请求url的组成部分。形如- http://host:port/path?param=value
复制代码 , 我们参考http,制定的组成部分- jsbridge://className:callbackAddress/methodName?jsonObj
- // className: 表示java的类名
- // callbackAddress: js回调的标识
- // methodName: java中的方法名
- // jsonObj: 接口数据
复制代码 具体实践调用流程:
1、在js中,可以采用如下方法调用java方法- var JSBridge = {
- call: function(className, method, params, callback) {
- var uri = 'jsbridge://' + className + ':' + callback + '/' + method + '?' + params;
- window.prompt(uri, "");
- }
- }
- // 下面会调用java中的 bridge.showToast方法
- JSBridge.call('bridge', 'showToast', {'msg':'Hello JSBridge'}, function(res) {
- alert(JSON.stringify(res))
- });
复制代码 2、在java中, 可以如下实现:- // 进入prompt回调
- public class JSBridgeWebChromeClient extends WebChromeClient {
- @Override
- public boolean onJsPrompt(WebView view, String url, String message, String defaultValue, JsPromptResult result) {
- result.confirm(JSBridge.callJava(view, message));
- return true;
- }
- }
- // 调用java逻辑
- public class JSBridge {
- ...
- public static String callJava(WebView webView, String uriString) {
- String methodName = "";
- String className = "";
- String param = "{}";
- String port = "";
- if (!TextUtils.isEmpty(uriString) && uriString.startsWith("JSBridge")) {
- Uri uri = Uri.parse(uriString);
- className = uri.getHost();
- param = uri.getQuery();
- port = uri.getPort() + "";
- String path = uri.getPath();
- if (!TextUtils.isEmpty(path)) {
- methodName = path.replace("/", "");
- }
- }
- // 基于上面的className、methodName和port path调用对应类的方法
- if (exposedMethods.containsKey(className)) {
- HashMap methodHashMap = exposedMethods.get(className);
- if (methodHashMap != null && methodHashMap.size() != 0 && methodHashMap.containsKey(methodName)) {
- Method method = methodHashMap.get(methodName);
- if (method != null) {
- try {
- method.invoke(null, webView, new JSONObject(param), new Callback(webView, port));
- } catch (Exception e) {
- e.printStackTrace();
- }
- }
- }
- }
- return null;
- }
- }
- // 直接进入showToast函数的实现
- public static void showToast(WebView webView, JSONObject param, final Callback callback) {
- String message = param.optString("msg");
- Toast.makeText(webView.getContext(), message, Toast.LENGTH_SHORT).show();
- if (null != callback) {
- try {
- JSONObject object = new JSONObject();
- object.put("key", "value");
- object.put("key1", "value1");
- callback.apply(getJSONObject(0, "ok", object));
- } catch (Exception e) {
- e.printStackTrace();
- }
- }
- }
- // 上述程序的callback.apply方法实现如下: 即通过webView.loadUrl实现java调用js的方法
- public class Callback {
- private static Handler mHandler = new Handler(Looper.getMainLooper());
- private static final String CALLBACK_JS_FORMAT = "javascript:JSBridge.onFinish('%s', %s);";
- private String mPort;
- private WeakReference mWebViewRef;
- public Callback(WebView view, String port) {
- mWebViewRef = new WeakReference(view);
- mPort = port;
- }
- public void apply(JSONObject jsonObject) {
- final String execJs = String.format(CALLBACK_JS_FORMAT, mPort, String.valueOf(jsonObject));
- if (mWebViewRef != null && mWebViewRef.get() != null) {
- mHandler.post(new Runnable() {
- @Override
- public void run() {
- mWebViewRef.get().loadUrl(execJs);
- }
- });
- }
- }
- }
复制代码 安全性及其它JSBridge类管理暴露给前端方法,前端调用的方法应该在此类中注册才可使用。的实现是从中查找是否存在,不存在则反射取得对应class中的所有方法,具体方法是在中定义的,方法包括三个参数分别为、、。如果满足条件,则将所有满足条件的方法到中。
[code]private static Map exposedMethods = new HashMap();
public static void register(String exposedName, Class |
|