Android Hybrid的一些事

Hybrid

Hybrid,字面意思,“混合的”,在Android中,指的是混合开发,也就是Native和H5混合开发。

主要是实现思想是用H5做页面,以JS为桥梁,调用Native方法,Native用WebView装载H5页面,控制H5页面,这样实现了H5与Native的交互。这样做的好处是各个端只需要一套页面,维护成本低,而且H5可以随便修改,App不用升级也可以更新,曲线实现了“热更新”。但是在性能上,肯定没有纯Native好,而且在体验上也没有Native那么好。

传统交互

Android上设置可以使用JS
WebView webView = (WebView) findViewById(R.id.web_view);
WebSettings settings = webView.getSettings();
settings.setJavaScriptEnabled(true);
Android调用JS方法

JS方法:

// 有返回值
function sum(num1, num2) {
    return num1+num2;
}
// 无返回值
function say(message) {
     document.getElementById("xx").value = message;
}

Android调用JS有返回值的方法:

webView.evaluateJavascript("sum(5, 4)", new ValueCallback<String>() {
    @Override
    public void onReceiveValue(String value) {
        Toast.makeText(MainActivity.this, "value is " + value, Toast.LENGTH_SHORT).show();
    }
});

不过这个方法只有在Android 19以上才能使用!!

Android调用JS无返回值的方法:

webView.loadUrl("javascript:say('hello')");

比较简单!!

接下来我们看看JS怎么调用Android的方法。

JS调用Android方法

Android准备一个被JS调用的类

public class JsInterface {

   @JavascriptInterface
   public void showToast(String message) {
       Toast.makeText(MainActivity.this, message, Toast.LENGTH_SHORT).show();
   }

}

将准备的类加入到WebView中:

webView.addJavascriptInterface(new JsInterface(), "control");

这样Android就准备好了!!看看JS怎么调用

<html>

    <title>主页面</title>

    <script type="text/javascript">
        function showToast(message) {
            window.control.showToast(message);
        }
    </script>

    <body>
        <input type="button" value="toast" onclick="showToast('hello')">
    </body>

</html>

其中,control就是我们在WebView添加给JS执行类的别名(我这里看做别名),JS调用control时,相当于使用了JsInterface类。

缺点

我们在使用这种传统方式实现交互时,在Android 4.2以下,听说会有漏洞,会被XSS攻击,我也不是太懂安全方面的知识,见谅。在Android 4.2以上,已经有解决办法了,就是在JS要调用的方法上,加入 @JavascriptInterface 注解:

@JavascriptInterface
public void showToast(String message) {
    Toast.makeText(MainActivity.this, message, Toast.LENGTH_SHORT).show();
}

这种的话,兼容性就没那么强了,我们来看看其他解决Native和H5交互的方案!

JSBridge

JSBridge的主要流程:

  1. 在js脚本中把对应的方法名,参数等写成一个符合协议的uri,并且通过window.prompt方法发送给java层。
  2. 在java层的onJsPrompt方法中接受到对应的message之后,通过JsCallJava类进行具体的解析。
  3. 在JsCallJava类中,我们解析得到对应的方法名,参数等信息,并且在map中查找出对应的类的方法。
  4. 在得到对应的方法之后,就去调用它,最后调用loadUrl方法调用js的回调方法。

如果想详细了解,请参考 Android中JSBridge的原理与实现

当我查阅代码的时候,发现JSBridge原理不太一样了,不知道是不是改了想法

JSBridge的Github地址:https://github.com/lzyzsd/JsBridge

先看了一下 BridgeWebViewClient 类,因为这个类继承 WebViewClient,根据JSBridge原理,应该是重写onJsPrompt才对的,可发现代码重写的是shouldOverrideUrlLoading,这个方法主要是拦截了前端的url :

public class BridgeWebViewClient extends WebViewClient {

    private BridgeWebView webView;

    public BridgeWebViewClient(BridgeWebView webView) {
        this.webView = webView;
    }

    @Override
    public boolean shouldOverrideUrlLoading(WebView view, String url) {
        try {
            url = URLDecoder.decode(url, "UTF-8");
        } catch (UnsupportedEncodingException e) {
            e.printStackTrace();
        }
        if (url.startsWith(BridgeUtil.YY_RETURN_DATA)) { // 如果是返回数据
            webView.handlerReturnData(url);
            return true;
        } else if (url.startsWith(BridgeUtil.YY_OVERRIDE_SCHEMA)) { //
            webView.flushMessageQueue();
            return true;
        } else {
            return super.shouldOverrideUrlLoading(view, url);
        }
    }

    ...

}

既然这样,我们看看js怎么调用,主要是callHandler方法:

function callHandler(handlerName, data, responseCallback) {
        _doSend({
            handlerName: handlerName,
            data: data
        }, responseCallback);
    }

调用了_doSend方法:

function _doSend(message, responseCallback) {
    if (responseCallback) {
        var callbackId = 'cb_' + (uniqueId++) + '_' + new Date().getTime();
        responseCallbacks[callbackId] = responseCallback;
        message.callbackId = callbackId;
    }
    sendMessageQueue.push(message);
    messagingIframe.src = CUSTOM_PROTOCOL_SCHEME + '://' + QUEUE_HAS_MESSAGE;
}

看到这里,我们应该猜出来,“这个版本”的JSBridge利用“iframe”标签,设置src属性(意思是加载目标地址),java层BridgeWebViewClient重写shouldOverrideUrlLoading拦截目标地址,并不是在重写的onJsPrompt中直接查找方法。

看了一下源码,被绕得有点晕。js调用时,不是直接说要调用java哪个方法,而是先告诉java,js队列中有消息了,让java去获取。java获取到了js的消息队列,然后遍历这个队列,找到每个message对应的是哪个handler,然后再调用,最后callback返回结果。可能对源码还不熟悉,目前还不不知道为什么不直接告诉java调用哪个方法或者handler,非要java去js获取?以后有时间再梳理!!

使用JSBridge

Android 调用 JS

// JS代码中注册
WebViewJavascriptBridge.registerHandler("JavaCallJs", function(data, responseCallback) {
    document.getElementById("show").innerHTML = ("从Java端接收的数据: = " + data);
    responseCallback("Js端处理完了");
});

// Java代码中调用
mWebView.callHandler("JavaCallJs", "向Js端传入的数据", new CallBackFunction() {
    @Override public void onCallBack(String data) {
        // TODO Auto-generated method stub
        Log.i(TAG, "Js端处理完后返回数据: " + data);
    }
});

JS 调用 Android

// Java代码中
mWebView.registerHandler("JsCallJava", new BridgeHandler() {
    @Override public void handler(String data, CallBackFunction function) {
        Log.i(TAG, "Js端返回数据: " + data);
        function.onCallBack("Java端处理完了");
    }
});

//JS代码调用
WebViewJavascriptBridge.callHandler(
    'JsCallJava',  { 'param': '中文测试'},
    function(responseData) {
        document.getElementById("show").innerHTML = "send get responseData from java, data = " + responseData
    }
);

使用起来很简单!!

在网上查阅资料,发现这样的JSBridge也有一些问题:

所遇到的问题都是由iframe.src引起的:
1. iframe.src的url长度有大小限制,过大则直接会丢失
2. iframe.src的reload,即src重新复制,如果频率太快,则也会直接丢失
3. 解决完这两个问题后,在Java代码中,responseCallback这个HashMap里面的数据不用remove了,在handleReturnData里,因为上游延迟了发送。

主要的原因是iframe标签弊端太多导致的。

关于Hybrid开发暂时探索这么多!!!

感谢

好好和h5沟通!几种常见的hybrid通信方式
Android中JSBridge的原理与实现
Android JsBridge源码分析
JSBridge源码

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值