JSBridge实现原理探索:以toast为例

@youngwind 2016-06-30 05:10:30发表于 youngwind/blog 移动H5

问题

我们知道,如果想要满足飞快增长的业务需要,必然得在APP中嵌入Web内容。一般来说,是java调用webview类新建一个webview,然后通过loadUrl加载Web页面。如果这个页面是纯展示性的还好说,对于复杂的页面,常常需要调用APP原生的功能,这个时候应该怎么办呢?

我们把问题抽象一下:“如何实现javascript和java的相互调用?”
把问题一分为二,先来看**“如何实现javascript调用java?”**

要实现的效果

我们要实现的效果如下图所示,点击某个按钮,打开一个新页面,然后加载web页面。点击web页面当中的按钮,调用安卓原生的方法toast,弹出提示。
demo

如果你想在手机上查看真实效果的话,可以扫描下面的二维码安装测试包。
1467255657

思路分析

我们来想想如何实现上面的功能。难点有这么几个:

  1. 如何创建一个webview?如何在webview中加载网页?
  2. web如何调用android?

对于第一点,不是本文的重点,你可以参考这个链接来自行解决。

对于第二点,我们继续分析。
web和android,就像是一个中国人和一个美国人。想要不同语言之间的人进行有效沟通的话,必须要满足以下两个基本要素。

  1. 你在听我说话 => 你在监听我的动作 =>javascript的某个行为会被java捕获到
  2. 我们手上有一本中英双译字典 => 我们之间有协议规范 => 对于传输的信息,javascript和java要用规定好的方式来编码和解码

先来看动作监听。javascript的哪些操作会被java捕获到?整个web页面都是运行在java提供的webview实例当中。javascript执行以下四种行为会被webview监听到,箭头后面是对应触发的Java方法。

  1. window.alert => onJSAlert
  2. window.confirm => onJSConfirm
  3. window.prompt => onJsPrompt
  4. window.location => shouldOverrideUrlLoading

好,动作监听我们解决了。接下来是要制定通信协议。说到通信协议,我们首先想到的是http协议,没错,我们可以仿照http协议制定我们自己的协议。
比如http协议是这样子的: http://www.baidu.com?param
我们制定的协议是这样子: ywjs://toast?message
其中ywjs是scheme,代表我们用的是某种自定义的协议。toast是方法名,也就是我希望调用的是系统的toast方法。toast方法所需要的参数,也就是弹出的提示字符串。

代码实现

好,思路我们已经分析好了。万事俱备,只欠敲代码了。(如果完全看不懂,请先补充一下基本的安卓开发知识和webview的使用方法)

// java代码 JSBridgeAlert.java
package com.example.youngwind.helloworld.JSBridgeAlert;

import android.content.pm.ApplicationInfo;
import android.net.Uri;
import android.os.Build;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.webkit.JsResult;
import android.webkit.WebChromeClient;
import android.webkit.WebSettings;
import android.webkit.WebView;
import android.webkit.WebViewClient;
import android.widget.Toast;

import com.example.youngwind.helloworld.R;

public class JSBridgeAlert extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_jsbridge_alert);
        // 添加了chrome:inspect bebug功能
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
            if (0 != (getApplicationInfo().flags &= ApplicationInfo.FLAG_DEBUGGABLE)) {
                WebView.setWebContentsDebuggingEnabled(true);
            }
        }

        final WebView myWebView = (WebView) findViewById(R.id.webView);

        // webview启用javascript
        WebSettings webSettings = myWebView.getSettings();
        webSettings.setJavaScriptEnabled(true);


        myWebView.loadUrl("file:///android_asset/alert.html");

        myWebView.setWebChromeClient(new WebChromeClient() {
            @Override
            public boolean onJsAlert(WebView view, String url, String message, JsResult result) {

                // message === "ywjs://toast?利用jsAlert建立JSBridge"

                if(message.startsWith("ywjs://")){
                    String methodName = "";
                    String param = "";
                    Uri uri = Uri.parse(message);

                    // toast
                    methodName = uri.getHost();

                    // msg参数
                    param = uri.getQuery();

                    if (methodName.equals("toast")) {
                        Toast.makeText(view.getContext(), param, Toast.LENGTH_LONG).show();
                    }
                    // 这一句非常关键,相当于点击了alert的确认
                    // 没有这一句的话,alert只能被触发一次,且其他地方的alert也不能被触发
                    result.confirm();
                }



                return true;
            }
        });
    }
}

// html代码 Alert.html
<!doctype html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>利用jsAlert建立JSBridge</title>
    <style>
        #test {
            width:100%;
            height:50px;
        }
    </style>
</head>
<body>
<button id="test">点我toast</button>
<script src="file:///android_asset/jquery.js" type="text/javascript"></script>
<script type="text/javascript">

    $(document).on('click','#test',function(){
        alert("ywjs://toast?利用jsAlert建立JSBridge");
        })


</script>
</body>
</html>

其他实现方式

刚刚只展示了onJsAlert的实现代码,就像刚刚提到的,还有confirm、prompt和location方法也可以用来实现。
另外,webview本身提供了这样的接口,可以不采用上面的重新思路来进行,那就是addJavascriptInterface。
具体的实现方法可以参考文末的链接,可以查看我编写的demo代码

遗留问题

还有这么一些问题没有有待解决。

  1. java如何调用js方法?
  2. Javascript调用了java方法,java方法执行完成之后如何调用javascript的回调方法?
  3. ios系统下如何做JSBridge?

参考资料