[Android] WebViewJSBridge

原文地址

一 什么是webViewjavascripBridge?

WebViewJavascriptBridge是移动UIView和Html交互通信的桥梁,用作者的话来说就是实现java(ios为oc)和js的互相调用的桥梁。替代了WebView的自带的JavascriptInterface的接口,使得我们的开发更加灵活和安全。

二 为什么要用webViewjavascripBridge?

对于安卓开发有一段时间的人来说,知道安卓4.4以前谷歌的webview存在安全漏洞,网站可以通过js注入就可以随便拿到客户端的重要信息,甚至轻而易举的调用本地代码进行流氓行为,谷歌后来发现有此漏洞后,增加了防御措施,如果要是js调用本地代码,开发者必须在代码申明JavascriptInterface,
列如在4.0之前我们要使得webView加载js只需如下代码:

mWebView.addJavascriptInterface(new JsToJava(), "myjsfunction");    

4.4之后调用需要在调用方法加入加入@JavascriptInterface注解,如果代码无此申明,那么也就无法使得js生效,也就是说这样就可以避免恶意网页利用js对安卓客户端的窃取和攻击。
但是即使这样,我们很多时候需要在js记载本地代码的时候,要做一些判断和限制,或者有可能也会做些过滤和对用户友好提示,因此JavascriptInterface也就无法满足我们的需求了,特此有大神就写出了WebViewJavascriptBridge框架。

三 怎么使用webViewjavascripBridge

1 将jsBridge.jar引入到我们的工程

Android Studio:

repositories {
    // ...
    maven { url "https://jitpack.io" }
}

dependencies {
    compile 'com.github.lzyzsd:jsbridge:1.0.4'
}

2 WebView包需使用以上包的webView

Layout中使用第三方webView

<?xml version="1.0" encoding="utf-8"?>  
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"  
    android:layout_width="match_parent"  
    android:layout_height="match_parent"  
    android:orientation="vertical" >  
  
    <!-- button 演示Java调用web -->  
    <Button   
        android:id="@+id/button"  
        android:layout_width="match_parent"  
        android:text="@string/button_name"  
        android:layout_height="48dp"  
        />  
      
    <!-- webview 演示web调用Java -->  
    <com.github.lzyzsd.jsbridge.BridgeWebView  
        android:id="@+id/webView"  
        android:layout_width="match_parent"  
        android:layout_height="match_parent" >  
     </com.github.lzyzsd.jsbridge.BridgeWebView>  
  
</LinearLayout>  

3 Java代码

1 activity
public class MainActivity extends Activity implements OnClickListener {  
  
    private final String TAG = "MainActivity";  
  
    BridgeWebView webView;  
  
    Button button;  
  
    int RESULT_CODE = 0;  
  
    ValueCallback<Uri> mUploadMessage;  
  
    static class Location {  
        String address;  
    }  
  
    static class User {  
        String name;  
        Location location;  
        String testStr;  
    }  
  
    @Override  
    protected void onCreate(Bundle savedInstanceState) {  
        super.onCreate(savedInstanceState);  
        setContentView(R.layout.activity_main);  
  
        webView = (BridgeWebView) findViewById(R.id.webView);  
  
        button = (Button) findViewById(R.id.button);  
  
        button.setOnClickListener(this);  
  
        webView.setDefaultHandler(new DefaultHandler());  
  
        webView.setWebChromeClient(new WebChromeClient() {  
  
            @SuppressWarnings("unused")  
            public void openFileChooser(ValueCallback<Uri> uploadMsg, String AcceptType, String capture) {  
                this.openFileChooser(uploadMsg);  
            }  
  
            @SuppressWarnings("unused")  
            public void openFileChooser(ValueCallback<Uri> uploadMsg, String AcceptType) {  
                this.openFileChooser(uploadMsg);  
            }  
  
            public void openFileChooser(ValueCallback<Uri> uploadMsg) {  
                mUploadMessage = uploadMsg;  
                pickFile();  
            }  
        });  
        //加载本地网页  
  
        //webView.loadUrl("file:///android_asset/demo.html");  
        //加载服务器网页  
        webView.loadUrl("https://www.baidu.com");  
  
        //必须和js同名函数,注册具体执行函数,类似java实现类。  
        webView.registerHandler("submitFromWeb", new BridgeHandler() {  
  
            @Override  
            public void handler(String data, CallBackFunction function) {  
  
                String str ="这是html返回给java的数据:" + data;  
                // 例如你可以对原始数据进行处理  
                makeText(MainActivity.this, str, LENGTH_SHORT).show();  
  
                Log.i(TAG, "handler = submitFromWeb, data from web = " + data);  
                function.onCallBack( str + ",Java经过处理后截取了一部分:"+ str.substring(0,5));  
            }  
  
        });  
        //模拟用户获取本地位置  
        User user = new User();  
        Location location = new Location();  
        location.address = "上海";  
        user.location = location;  
        user.name = "Bruce";  
  
        webView.callHandler("functionInJs", new Gson().toJson(user), new CallBackFunction() {  
            @Override  
            public void onCallBack(String data) {  
                makeText(MainActivity.this, "网页在获取你的位置", LENGTH_SHORT).show();  
  
            }  
        });  
  
        webView.send("hello");  
  
    }  
  
    public void pickFile() {  
        Intent chooserIntent = new Intent(Intent.ACTION_GET_CONTENT);  
        chooserIntent.setType("image/*");  
        startActivityForResult(chooserIntent, RESULT_CODE);  
    }  
  
    @Override  
    protected void onActivityResult(int requestCode, int resultCode, Intent intent) {  
        if (requestCode == RESULT_CODE) {  
            if (null == mUploadMessage){  
                return;  
            }  
            Uri result = intent == null || resultCode != RESULT_OK ? null : intent.getData();  
            mUploadMessage.onReceiveValue(result);  
            mUploadMessage = null;  
        }  
    }  
  
    @Override  
    public void onClick(View v) {  
        if (button.equals(v)) {  
            webView.callHandler("functionInJs", "data from Java", new CallBackFunction() {  
  
                @Override  
                public void onCallBack(String data) {  
                    // TODO Auto-generated method stub  
                    Log.i(TAG, "reponse data from js " + data);  
                }  
  
            });  
        }  
  
    }  
  
}  
2 自定webVIewClient
class MyWebViewClient extends BridgeWebViewClient{  
        public MyWebViewClient(BridgeWebView webView) {  
            super(webView);  
        }  
    }  

将自定义webViewClient设置到webview上

mWebView.setWebViewClient(new MyWebViewClient(mWebView));  

上面一句话很关键:

通过实例化webView,用法和安卓原生的view没多大区别,设置WebChromClient, 设置加载的html(同样支持网络和本地文件),接着我们需要给web注册和html端约定好的js方法名。代码列举的submitFromweb和js的执行的方法名一致,玩过NDK的JNI调用的朋友也知道必须和c代码之间有个约定,其实js桥和jni有点类似,通过注册handler来实现回调,Java代码中通过js返回的数据,进行处理后在调用function.onCallback返回给js这里不做过多解释。

//必须和js函数同名,注册具体执行回调函数,类似java实现类。  
        webView.registerHandler("submitFromWeb", new BridgeHandler() {  
  
            @Override  
            public void handler(String data, CallBackFunction function) {  
  
                String str ="这是html返回给java的数据:" + data;  
                // 例如你可以对原始数据进行处理  
                makeText(MainActivity.this, str, LENGTH_SHORT).show();  
  
                Log.i(TAG, "handler = submitFromWeb, data from web = " + data);  
                function.onCallBack( str + ",Java经过处理后截取了一部分:"+ str.substring(0,5));  
            }  
  
        });  

如果是webview刚开始就执行一段Java代码 ,可以通过webView.CallHandler()来实现 。当然我们注册的方法也要和js里面的方法名一致。

webView.callHandler("functionInJs", new Gson().toJson(user), new CallBackFunction() {  
           @Override  
           public void onCallBack(String data) {  
            makeText(MainActivity.this, "网页在获取你的位置", LENGTH_SHORT).show();  
  
           }  
       });  
3 Html和js实现

html代码如下,效果图


这里写图片描述
<html>  
    <head>  
        <meta content="text/html; charset=utf-8" http-equiv="content-type">  
        <title>  
            js调用java  
        </title>  
    </head>  
      
    <body>  
        <p>  
            <xmp id="show">  
            </xmp>  
        </p>  
        <p>  
            <xmp id="init">  
            </xmp>  
        </p>  
        <p>  
            <input type="text" id="text1" value="用户名(username)" />  
        </p>  
        <p>  
            <input type="text" id="text2" value="password" />  
        </p>  
        <p>  
            <input type="button" id="enter" value="发消息给Native" onclick="testClick();"  
            />  
        </p>  
        <p>  
            <input type="button" id="enter1" value="调用Native方法" onclick="testClick1();"  
            />  
        </p>  
        <p>  
            <input type="button" id="enter2" value="显示html" onclick="testDiv();" />  
        </p>  
        <p>  
            <input type="file" value="打开文件"  />  
        </p>  
    </body>  
      
</html>  

js代码

<script>  
  
       function testDiv() {  
           document.getElementById("show").innerHTML = document.getElementsByTagName("html")[0].innerHTML;  
       }  
  
       function testClick() {  
           var str1 = document.getElementById("text1").value;  
           var str2 = document.getElementById("text2").value;  
  
           //发送消息给java代码  
           var data = "name=" + str1 + ",pass=" + str2;  
  
           window.WebViewJavascriptBridge.send(  
               data  
               , function(responseData) {  
  
                   document.getElementById("show").innerHTML = "repsonseData from java, data = " +responseData  
               }  
           );  
  
       }  
  
       function testClick1() {  
           var str1 = document.getElementById("text1").value;  
           var str2 = document.getElementById("text2").value;  
  
           //调用本地java方法  
           window.WebViewJavascriptBridge.callHandler(  
               'submitFromWeb'  
               , {'param': str1}  
               , function(responseData) {  
                   document.getElementById("show").innerHTML = "send get responseData from java, data = " + responseData  
               }  
           );  
       }  
  
       function bridgeLog(logContent) {  
           document.getElementById("show").innerHTML = logContent;  
       }//注册事件监听  
       function connectWebViewJavascriptBridge(callback) {  
           if (window.WebViewJavascriptBridge) {  
               callback(WebViewJavascriptBridge)  
           } else {  
               document.addEventListener(  
                   'WebViewJavascriptBridgeReady'  
                   , function() {  
                       callback(WebViewJavascriptBridge)  
                   },  
                   false  
               );  
           }  
       }  
       //注册回调函数,第一次连接时调用 初始化函数  
       connectWebViewJavascriptBridge(function(bridge) {  
           bridge.init(function(message, responseCallback) {  
               console.log('JS got a message', message);  
               var data = {  
                   'Javascript Responds': 'Wee!'  
               };  
               console.log('JS responding with', data);  
               responseCallback(data);  
           });  
  
           bridge.registerHandler("functionInJs", function(data, responseCallback) {  
               document.getElementById("show").innerHTML = ("data from Java: = " + data);  
               var responseData = "Javascript Says Right back aka!";  
               responseCallback(responseData);  
           });  
       })  
   </script>

这段代码不难理解,我们对上面的id为enter的Button注册了一个点击事件,点击后执行以下testClick()方法,依次类推,其他Button注册事件相同,

当点击“发消息给Native”的按钮时,Js通过webWiew的jsBridage.send()发送一条数据给java层(密码和用户名),同时在此用function()来执行应java层回调函数的。此demo中是把java返回的数据插入到"show"的div里面去。

testClick1():此方法中调用callHandler来调用Java代码的submitFromweb同名函数,可以结合上面的Activty的代码理解下,此函数调用我们已在java注册实现好的。

//必须和js同名函数,注册具体执行函数,类似java实现类。  
        webView.registerHandler("submitFromWeb", new BridgeHandler() {  
  
            @Override  
            public void handler(String data, CallBackFunction function) {  
  
                String str ="这是html返回给java的数据:" + data;  
                // 例如你可以对原始数据进行处理  
                makeText(MainActivity.this, str, LENGTH_SHORT).show();  
  
                Log.i(TAG, "handler = submitFromWeb, data from web = " + data);  
                function.onCallBack( str + ",Java经过处理后截取了一部分:"+ str.substring(0,5));  
            }  
  
        });  

java注册Js函数如上面列子 ,那么在js中注册方法来注册呢,
在js中我们同样可以直接注册一个回调函数,通过 bridge.registerHandler()来注册,接着调用 responseCallback(responseData)来返回数据给java代码的同名函数。也可以直接调用init()来指定网页首次加载上面注册java代码。

connectWebViewJavascriptBridge(function(bridge) {  
          bridge.init(function(message, responseCallback) {  
              console.log('JS got a message', message);  
              var data = {  
                  'Javascript Responds': 'Wee!'  
              };  
              console.log('JS responding with', data);  
              responseCallback(data);  
          });  
  
          bridge.registerHandler("functionInJs", function(data, responseCallback) {  
              document.getElementById("show").innerHTML = ("data from Java: = " + data);  
              var responseData = "Javascript Says Right back aka!";  
              responseCallback(responseData);  
          });  
      })  

四 总结

通过以上的API介绍,代码示例,不难发现此框架的优雅和简便,对js和java双方来说,如果Html中的js需要调用java代码,而java代码没做任何实现,那么js中方法也是无效的,反之java代码注册的函数,没在js里去回调实现,那么Java层也是无法获取js中数据的,由此可见,此通信是双方支持的,必须由双方来约定,这样就避免了Android之前存在的js注入漏洞,也很大的提高了安全性,也可以保证我们的网页数据不被第三方的APP获取,具体来讲,列如我们的项目某一个web的h5界面,被系统浏览器或者其他第三方App的恶意加载,那么它的java代码想调用你的js函数,实现需要你的H5的Js先注册,不然跟本无法调用你的h5信息。这样保证了这个html数据的安全性,,第三方的浏览器可以加载预览你的网页,但是第三方java无法和你的的h5中的js交互通信的。同样加载我们自己的APP加载第三方的网页时候,我们可以对第三方网页进行一些行为的过滤,方便保护我们手机的安全,列如第三方可以获取本机地址时我们可以提示用户授权。虽然H5并不属于插件的一种,但是借助h5我可以方便的去更新一些运营活动,和某些需要经常需要更换UI的业务功能的地方,以上只JSBridge的使用姿势,,以后再给大家解剖下JsBridge的内部实现。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 201,924评论 5 474
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 84,781评论 2 378
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 148,813评论 0 335
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,264评论 1 272
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,273评论 5 363
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,383评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,800评论 3 393
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,482评论 0 256
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,673评论 1 295
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,497评论 2 318
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,545评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,240评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,802评论 3 304
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,866评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,101评论 1 258
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,673评论 2 348
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,245评论 2 341

推荐阅读更多精彩内容