Safe Java-JS WebView Bridge 分析

Safe Java-JS WebView Bridge 是github上的一个开源库, 主要实现了对js和java调用的一些封装.

https://github.com/pedant/safe-java-js-webview-bridge

从Sample入手, 我们先看看js调用的基本例子

<button onclick="HostApp.toast('我是气泡');">测试</button>

可以看到js调用了一个简单的方法.

我们开始分析native code是如何做的, 先看看启动Activity都做了什么.

**WebActivity.java**
    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        ...
        wv.setWebChromeClient(
            new CustomChromeClient("HostApp", HostJsScope.class)
        );
        ...
    }

其实主要的实现就是设置了一个自定义的CustomChromeClient, 这个CustomChromeClient是继承了InjectedChromeClient, 例子中CustomChromeClient什么都没有做, 主要工作都是由InjectedChromeClient完成的.

**InjectedChromeClient.java**
    public InjectedChromeClient (String injectedName, Class injectedCls) {
        mJsCallJava = new JsCallJava(injectedName, injectedCls);
    }

这里可以看到InjectedChromeClient初始化只是创建了一个JsCallJava对象,那么JsCallJava又是个什么鬼? 我们继续看看代码

public JsCallJava (String injectedName, Class injectedCls)

通过方法定义可以看到传的参数一个是注入名, 一个是Class, 猜想就是把Class的方法定义绑定到注入命.然后写入页面提供js调用. 来看看是不是这样

**JsCallJava.java**
    public JsCallJava (String injectedName, Class injectedCls) {
        try {
            if (TextUtils.isEmpty(injectedName)) {
                throw new Exception("injected name can not be null");
            }
            mInjectedName = injectedName;
            mMethodsMap = new HashMap<String, Method>();
            Method[] methods = injectedCls.getDeclaredMethods();
            ...
            for (Method method : methods) {
                String sign;
                if (method.getModifiers() != (Modifier.PUBLIC | Modifier.STATIC) || (sign = genJavaMethodSign(method)) == null) {
                    continue;
                }
                ...
            }
            ...
            mPreloadInterfaceJS = sb.toString();
        } catch(Exception e){
            Log.e(TAG, "init js error:" + e.getMessage());
        }
    }

去掉了一些构建js的代码. 正如我们猜想就是通过遍历Class,支持public static方法. 然后把生成的js保存到mPreloadInterfaceJS变量中, 继续看下是哪里用到mPreloadInterfaceJS

**InjectedChromeClient.java**
    @Override
    public void onProgressChanged (WebView view, int newProgress) {
        //为什么要在这里注入JS
        //1 OnPageStarted中注入有可能全局注入不成功,导致页面脚本上所有接口任何时候都不可用
        //2 OnPageFinished中注入,虽然最后都会全局注入成功,但是完成时间有可能太晚,当页面在初始化调用接口函数时会等待时间过长
        //3 在进度变化时注入,刚好可以在上面两个问题中得到一个折中处理
        //为什么是进度大于25%才进行注入,因为从测试看来只有进度大于这个数字页面才真正得到框架刷新加载,保证100%注入成功
        if (newProgress <= 25) {
            mIsInjectedJS = false;
        } else if (!mIsInjectedJS) {
            view.loadUrl(mJsCallJava.getPreloadInterfaceJS());
            mIsInjectedJS = true;
            Log.d(TAG, " inject js interface completely on progress " + newProgress);

        }
        super.onProgressChanged(view, newProgress);
    }

注释写的很明白了, 就不过多解释这部分了. 下面我们看看声明的java方法和生成的js代码

public static void testjs(String ssss) {}

我们声明了testjs的静态方法, 下面是生成的JS代码示例

javascript: (function(b) {
    var a = {
        queue: [],
        callback: function() {
            var d = Array.prototype.slice.call(arguments, 0);
            var c = d.shift();
            var e = d.shift();
            this.queue[c].apply(this, d);
            if (!e) {
                delete this.queue[c]
            }
        }
    };

    a.testjs = function() {
        var f = Array.prototype.slice.call(arguments, 0);
        if (f.length < 1) {
            throw "JSBridgeTest call error, message:miss method name"
        }
        var e = [];
        for (var h = 1; h < f.length; h++) {
            var c = f[h];
            var j = typeof c;
            e[e.length] = j;
            if (j == "function") {
                var d = a.queue.length;
                a.queue[d] = c;
                f[h] = d
            }
        }
        var g = JSON.parse(prompt(JSON.stringify({
            method: f.shift(),
            types: e,
            args: f
        })));
        if (g.code != 200) {
            throw "JSBridgeTest call error, code:" + g.code + ", message:" + g.result
        }
        return g.result
    };

    Object.getOwnPropertyNames(a).forEach(function(d) {
        var c = a[d];
        if (typeof c === "function" && d !== "callback") {
            a[d] = function() {
                return c.apply(a, [d].concat(Array.prototype.slice.call(arguments, 0)))
            }
        }
    });
    b.JSBridgeTest = a;
})(window);

可以看到testjs 定义为一个function, 通过分析参数和参数类型生成e, f 两个数组, 通过调用prompt函数回调给natvie code,我们看下natvie code里的处理.

**InjectedChromeClient.java**
    public boolean onJsPrompt(WebView view, String url, String message, String defaultValue, JsPromptResult result) {
        result.confirm(mJsCallJava.call(view, message));
        return true;
    }

可以看到这里就是通过前面构造函数里构造的mJsCallJava 的call 方法实际调用了java对应的方法.

未完待续--

转载于:https://my.oschina.net/uJbsYjHWOB/blog/743155

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值