zoukankan      html  css  js  c++  java
  • 【Android】怎样写一个JsBridge

    JsBridge

    简单介绍

    Android JsBridge 就是用来在 Android app的原生 java 代码与 javascript 代码中架设通信(调用)桥梁的辅助工具。

    原文地址点这里

    github点这里

    使用方式戳这里

    有问题请联系 xesam

    原理概述

    Javascript 运行在 WebView 中,而 WebView 仅仅是 Javascript 运行引擎与页面渲染引擎的一个包装而已。

    因为这样的天然的隔离效应,我们能够将这样的情况与 IPC 进行类比,将 Java 与 Javascript 的每次互调都看做一次 IPC 调用。
    如此一来,我们能够模仿各种已有的 IPC 方式来进行设计。比方 RPC。本文模仿 Android 的 Binder 机制来实现一个 JsBridge。

    首先回想一一下基于 Binder 的经典 RPC 调用:

    Javascript-bridge-rpc

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

    Javascript-bridge-circle

    剩下的问题就是怎么来实现这个机制了。大致有这么几个须要解决的问题:

    1. java 怎样调用 Javascript
    2. Javascript 怎样调用 java
    3. 方法參数以及回调怎样处理
    4. 通信的数据格式是怎样的

    以下逐个讨论这些问题:

    1. java 怎样调用 Javascript

    要实现 Java 与 Javascript 的相互调用。有两条途径能够考虑:

    1. 集成一个定制化的 Javascript 与 Html 渲染引擎,java 通过引擎底层与 Javascript 交互。这样能够获得全然的控制权。
    2. 使用 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();

    这里有两个方面须要统一:

    1. Javascript 的运行方法比較怪异,所以,我们须要将概念统一化。

    2. 假设须要运行的方法比較多。那么,代理对象上也须要定义非常多的方法。我们须要将各种方法定义统一起来管理。

    所以。我们先将 Javascript 的运行包装成相似 java 一样的代理对象,然后通过在各自的 stub 上注冊回调来添加功能支持。


    比方。假设 java 想添加 getPackageName 方法,那么。直接在 JavaProxy 上注冊就可以:

    
        javaProxy.register("getPackageName", new JavaHandler(){
            @Override
            public void handle(Object value){
                //xxxxx
            }
        })

    如图:

    Javascript-bridge-register

    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

  • 相关阅读:
    JavaScript 数据类型判断
    使用渐进增强的方式美化复选框样式
    使用 Bootstrap 和 HTML5 Boilerplate 开始一个项目
    CSS基础知识之文本属性二三事
    精简CSS代码
    CSS选择器特殊性与重要性
    面试官:能解释一下javascript中的this吗
    VueRouter爬坑第四篇-命名路由、编程式导航
    Vuex实践(下)-mapState和mapGetters
    Vuex实践(中)-多module中的state、mutations、actions和getters
  • 原文地址:https://www.cnblogs.com/yutingliuyl/p/7250574.html
Copyright © 2011-2022 走看看