Android中JSBridge的原理与实现

  • 时间:2019-06-11 04:52 作者:Android开发架构 来源:Android开发架构 阅读:543
  • 扫一扫,手机访问
摘要:首先我们来理解一下什么是JSBridge和为什么要使用JSBridge?在开发中,为了追求开发的效率以及移植的便利性,少量展现性强的页面我们会偏向于使用h5来完成,功能性强的页面我们会偏向于使用native来完成,而一旦使用了h5,为了在h5中尽可能的得到native的体验,我们native层需要暴

首先我们来理解一下什么是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()”)就可,这样,就做到了JSBridge的native层调用h5层的单向通信

WebView.loadUrl("javascript:function()");

JavaScript如何调用Java

js调用Android的方法有以下四种:

1、WebView 的 andJavascriptInterface

2、WebViewClient.shouldOverrideUrlLoading()

3、WebChromeClient.onConsoleMessage()

4、WebChromeClient.onJsPrompt()、onJsAlert()、onJsConfirm()

我们先对此四种方案进行一个详细的形容,最后选择一个方案就可。本文章中采用了第四种方案。

JavascriptInterface

JavascriptInterface是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上修复了(即便用@JavascriptInterface代替addJavascriptInterface),但是因为兼容性和安全性问题,基本上我们不会再利用Android系统为我们提供的addJavascriptInterface方法或者者@JavascriptInterface注解来实现,所以我们只能另辟蹊径,去寻觅既安全,又能实现兼容Android各个版本的方案。

WebViewClient.shouldOverrideUrlLoading()

这个方法是阻拦所有webView的跳转,页面可以构造一个特殊格式的Url跳转,shouldOverrideUrlLoading阻拦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中执行console.log(), 会进入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有一个方法,叫setWebChromeClient,可以设置WebChromeClient对象,而这个对象中有三个方法,分别是onJsAlert,onJsConfirm,onJsPrompt,当js调用window对象的对应的方法,即window.alert,window.confirm,window.prompt,WebChromeClient对象中的三个方法对应的就会被触发,那这三个方法究竟要使用哪个呢?

2、这三个方法的区别,可以详见w3c JavaScript 消息框 。

3、一般来说,我们是不会使用onJsAlert的,为什么呢?由于js中alert使用的频率还是非常高的,一旦我们占用了这个通道,alert的正常使用就会受到影响,而confirm和prompt的使用频率相对alert来说,则更低一点。

4、那么究竟是选择confirm还是prompt呢,其实confirm的使用频率也是不低的,比方你点一个链接下载一个文件,这时候假如需要弹出一个提醒进行确认,点击确认就会下载,点取消便不会下载,相似这种场景还是很多的,因而不能占用confirm。

5、而prompt则不一样,在Android中,几乎不会使用到这个方法,就是用,也会进行自己设置,所以我们完全可以使用这个方法。该方法就是弹出一个输入框,而后让你输入,输入完成后返回输入框中的内容。因而,占用prompt是再完美不过了。

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的组成部分

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<String, Method> 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<WebView> 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类管理暴露给前台方法,前台调用的方法应该在此类中注册才可使用。register的实现是从Map中查找key能否存在,不存在则反射获得对应class中的所有方法,具体方法是在BridgeImpl中定义的,方法包括三个参数分别为WebView、JSONObject、CallBack。假如满足条件,则将所有满足条件的方法put到map中。

private static Map<String, HashMap<String, Method>> exposedMethods = new HashMap<>();public static void register(String exposedName, Class<? extends IBridge> clazz) {        if (!exposedMethods.containsKey(exposedName)) {            try {                exposedMethods.put(exposedName, getAllMethod(clazz));            } catch (Exception e) {                e.printStackTrace();            }        }    }    

JSBridge类中的callJava方法就是将js传递过来的URL解析,根据将要调用的类名从刚刚建立的Map中找出,根据方法名调用具体的方法,并将解析出的三个参数传递进去。

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("/", "");            }        }        if (exposedMethods.containsKey(className)) {            HashMap<String, Method> 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;    }

CallBack类是用来回调js中回调方法的Java对应类。Java层解决好的返回结果是通过CallBack类来实现的。在这个回调类中传递的参数是JSONObject(返回结果)、WebView和port,port应与js传递过来的port相对应。

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<WebView> 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);                }            });        }    }

在java层的JSBridge中注册方法,例如

JSBridge.register("bridge", BridgeImpl.class);
更多资料分享欢迎Android工程师朋友们加入安卓开发技术进阶互助:856328774免费提供安卓开发架构的资料(包括Fultter、高级UI、性能优化、架构师课程、 NDK、Kotlin、混合式开发(ReactNative+Weex)和一线互联网公司关于Android面试的题目汇总。
  • 全部评论(0)
最新发布的资讯信息
【系统环境|】ComfyUI差分扩散修复图像(2025-10-19 22:20)
【系统环境|】学习 ComfyUI前,先看下 Stable Diffusion 本地部署的详细教程(2025-10-19 22:19)
【系统环境|】ComfyUi PuLID的妙用三种工作流让脸部更像电商出图摄像图片合成(2025-10-19 22:18)
【系统环境|】一期讲不完用三期让你初步掌握AI(以comfyUI为例)第二节(2025-10-19 22:17)
【系统环境|】手把手教程:用ComfyUI+本地大模型实现英文翻译中文(2025-10-19 22:17)
【系统环境|】Flux.1 本地部署安装教程,最强开源绘画大模型(2025-10-19 22:16)
【系统环境|】ComfyUI 教程-15.Flux.1大模型的介绍以及工作流搭建(2025-10-19 22:15)
【系统环境|】15个最受欢迎的ComfyUI工作流(2025-10-19 22:14)
【系统环境|】12个ComfyUI必备的自定义节点(2025-10-19 22:13)
【系统环境|】秒变 AI 绘画大师,comfyui抓紧学起来(2025-10-19 22:12)
手机二维码手机访问领取大礼包
返回顶部