[AS2.3.3]WebView使用学习记录2

前言

本篇是webview的下篇,主要对webview和网页的js交互处理

webview学习记录上篇

本篇的gif效果先贴下

gif


Android对网页Javascript的调用

想让Android通过webview调用网页上面的Javascript其实很简单

只要网页上面的Javascript方法名字就可以直接调用了

在Android Api 19之前
webview调用js的方法只有直接loadUrl("javascript:js方法名字")

在此之后由于原来低版本调用js的安全问题
谷歌对js进行了修改并且加入了一个新的方法evaluateJavascript(String script, ValueCallback<String> resultCallback)

对比如下

调用方法 优点 缺点 使用
loadUrl 方法简便 效率低,取js回调的值麻烦 api19以下
evaluateJavascript 效率高,可以很好的获取返回结果 无法兼容19以下的版本 api19以上

上面gif中三个按钮的调用和js方法如下

    function callJS(){
        alert("Android调用了JS的callJS方法");
    }
    function callJS2(msg){
        var r = confirm(msg);
        if(r == true){
            alert("是");
        }else{
            alert("否");
        }
    }
    function callJS3(){
        var r = prompt("callJS3方法,随便输入");
        alert(r);
        return r;
    }
    //按钮1
    webView.loadUrl("javascript:callJS()");
    //按钮2
    webView.loadUrl("javascript:callJS2('Android调用了JS的callJS2方法')");
    //按钮3
    webView.evaluateJavascript("javascript:callJS3()", new ValueCallback<String>() {
        @Override
        public void onReceiveValue(String value) {
            //此处为 js 返回的结果
            Log.e("-s-", "=>"+value);
        }
    });

打印的log如下
log

正常使用的时候建议加入判断,低于19的系统就使用原来的loadUrl方法调用,之后都建议都换成evaluateJavascript

以上的方法相对应了,无参数调用、有参数调用以及获取返回结果


网页Javascript对Android的调用

从上面的gif我们能看到 点击网页上面的按钮可以直接调用app的Toast功能,这就是网页对安卓的调用,我们可以通过设置让网页点击之后调用手机的相机等一系列操作等

下面说下正常的使用

  1. 第一步
    我们需要设置webview的WebSettings让webview支持js
    wv.getSettings().setJavaScriptEnabled(true);
    设置之后我们能看到一个XSS警告,这个后面讲

  2. 第二步
    为webview添加js的触发方法和设置调用的方法名字
    wv.addJavascriptInterface(new javascriptInterface(),"cwebview");
    这边我定义了一个class和命名与js上面的方法调用是一样的

js方法

    function callAndroid(){
       cwebview.hello("js调用了android中的hello方法");
    }

javascriptInterface.class

    private class javascriptInterface{

        @JavascriptInterface
        public void hello(String msg){
            Toast.makeText(context, msg, Toast.LENGTH_SHORT).show();
        }
    }

这样我们就能在网页上面调用Android了,记住必须加入标示@JavascriptInterface不然无法被webview识别为映射的js方法。

继续我们上面讲的警告,对于这种情况,如果想要调用js方法又不需要开启js方法也是可以的,这边有两种方法,都需要和服务器进行约定设置,然后就可以实现不开启js就直接使用。

  • 第一种shouldOverrideUrlLoading

我们在重写webview的WebViewClient的时候,里面的shouldOverrideUrlLoading方法是用来让webview可以继续加载,而不会跳转到系统的浏览器。这样我们就可以和服务器约定,设置一个js的调用格式,然后进行js的操作。

这边我们约定的url是 js://webview?msg=调用callAndroid2方法&msg2=test2

js方法

    function callAndroid2(){
        document.location = "js://webview?msg=调用callAndroid2方法&msg2=test2";
    }

然后我们改写一下shouldOverrideUrlLoading方法


        wv.setWebViewClient(new WebViewClient(){
            @Override
            public boolean shouldOverrideUrlLoading(WebView view, String url) {

                //目标url为 js://webview?msg=调用callAndroid2方法&msg2=test2
                //判断是否有js标示
                if (url.contains("js://")) {
                    //将网址转换为Uri
                    Uri uri = Uri.parse(url);
                    //判断Uri的开头 即 :// 前的部分 js
                    if (uri.getScheme().equals("js")) {
                        //判断Uri的权 即 :// 到 ? 之间的部分 webview
                        if (uri.getAuthority().equals("webview")) {
                            //遍历Uri的参数 即 ? 之后的全部参数
                            Map<String,String> parms = new HashMap<>();
                            Set<String> names = uri.getQueryParameterNames();
                            StringBuilder sb = new StringBuilder();
                            for (String name : names) {
                                parms.put(name,uri.getQueryParameter(name));
                                sb.append(name+"="+uri.getQueryParameter(name)+"\n");
                            }
                            Toast.makeText(TestActivity.this, sb.toString(), Toast.LENGTH_SHORT).show();
                        }
                    }
                    return true;
                }

                view.loadUrl(url);
                return true;
            }
        });

这样就实现了不开启js也不需要设置方法就直接可以让js和Android进行交互。

  • 第二种onJsPrompt

我们上篇也讲过对话框,先来看下三种对话框的对比

方法 作用 返回值 备注
alert 弹出一个只有确认的对话框 无返回值 在文本后输入\n可以换行
confirm 弹出一个确认和取消的对话框 两个返回值 返回值为布尔类型
prompt 弹出一个可以输入的对话框可以确认和取消 可以任意设置返回值 点击确认会返回输入框内输入的值,取消则返回null

所以我们在选择用对话框进行js调用Android的时候最好选择使用onJsPrompt来实现

js方法

    function callAndroid3(){
        prompt("js://webview?msg111=调用callAndroid3方法&msg222=test3");
    }

然后我们改写WebChromeClient中的onJsPrompt方法,由于和上面的第一种方法差不多就不详写备注了,只有一个取消对话框的需要写下!

        wv.setWebChromeClient(new WebChromeClient(){
            @Override
            public boolean onJsPrompt(WebView view, String url, String message, String defaultValue, JsPromptResult result) {

                //目标url为 js://webview?msg111=调用callAndroid3方法&msg222=test3
                if (message.contains("js://")){
                    Uri uri = Uri.parse(message);
                    if (uri.getScheme().equals("js")) {
                        if (uri.getAuthority().equals("webview")) {
                            Map<String,String> params = new HashMap<>();
                            Set<String> names = uri.getQueryParameterNames();
                            StringBuilder sb = new StringBuilder();
                            for (String s : names) {
                                params.put(s,uri.getQueryParameter(s));
                                sb.append(s+"=>"+uri.getQueryParameter(s)+"\n");
                            }
                            Toast.makeText(TestActivity.this, sb.toString(), Toast.LENGTH_SHORT).show();
                            //关闭对话框
                            result.cancel();
                        }
                        return true;
                    }
                }

                return super.onJsPrompt(view, url, message, defaultValue, result);
            }
        });

这样就是另一种实现js交互的方案。

三种JS调用Android方案的比对

调用方法 优点 缺点 使用场景
使用addJavascriptInterface 官方方法简洁方便 Android4.2以下有漏洞问题 Android4.2以上版本使用方便容易
使用shouldOverrideUrlLoading 不存在漏洞问题 使用复杂,需要和服务器设置相应的条件标示 只是直接调用一次Android不需要Android的返回值的情况
使用onJsPrompt 不存在漏洞问题 使用复杂,需要和服务器设置相应的条件标示 如果有漏洞问题,又需要返回值等经常调用Android的情况

关于本篇的整个测试html代码

<!DOCTYPE html>
<html>
<head>
    <meta charset="utf-8">
    <title>JS_TEST</title>

    # JS代码
    <script>
        function callJS(){
            alert("Android调用了JS的callJS方法");
        }
        function callJS2(msg){
            var r = confirm(msg);
            if(r == true){
                alert("是");
            }else{
                alert("否");
            }
        }
        function callJS3(){
            var r = prompt("callJS3方法,随便输入");
            alert(r);
            return r;
        }
        function callAndroid(){
            cwebview.hello("js调用了android中的hello方法");
        }
        function callAndroid2(){
            document.location = "js://webview?msg=调用callAndroid2方法&msg2=test2";
        }
        function callAndroid3(){
            prompt("js://webview?msg111=调用callAndroid3方法&msg222=test3");
        }
    </script>
</head>
<body>
    <br />
    //点击按钮则调用callAndroid函数
    <br />
    <br />
    <button type="button" id="button1" onclick="callAndroid()">网页调用Android1</button>
    <br />
    <br />
    <button type="button" id="button2" onclick="callAndroid2()">网页调用Android2</button>
    <br />
    <br />
    <button type="button" id="button3" onclick="callAndroid3()">网页调用Android3</button>
</body>
</html>

WebView优化建议

当WebView的使用频率变得频繁的时候,对于其各方面的优化就变得逐渐重要了起来。可以知道的是,我们每加载一个 H5页面,都会有很多的请求。除了HTML主URL自身的请求外,HTML外部引用的 JS、CSS、字体文件、图片都是一个个独立的HTTP 请求,虽然请求是并发的,但当网页整体数量达到一定程度的时候,再加上浏览器解析、渲染的时间,Web整体的加载时间变得很长。同时请求文件越多,消耗的流量也会越多。那么对于加载的优化就变得非常重要,这方面的经验我也没有什么别的,大概三个方面:

第一个,就是资源本地化的问题

首先可以明确的是,以目前的网络条件,通过网络去服务器获取资源的速度是远远比不上从本地读取的。谈论各种优化策略其实恰恰忽略了“需要加载”才是阻挡速度提升的最大绊脚石。所以我们的思路一,就是将一些较重的资源比如js、css、图片甚至HTML本身进行本地化处理,在每次加载到这些资源的时候,从本地读取进行加载,可以简单记忆为“存·取·更”。

具体实现思路为:

”——将上述重量级资源打包进apk文件,每次加载相应文件时时从本地取即可。也可不打包,在第一次加载时以及接下来的若干间隔时间里动态下载存储,将所有的资源文件都存在Android的asset目录下;
”——重写WebViewClient的WebResourceResponse#shouldInterceptRequest方法,通过一定的判别方法(例如正则表达式)拦截相应的请求,从本地读取相应资源并返回;
”——建立起Cache Control机制,定期或使用API通知的形式控制本地资源的更新,保证本地资源是最新和可用的。

这里附上一篇博客链接,非常棒可供参考:caching-web-resources-in-the-android-device

第二个,就是缓存的问题

倘若你不采用或不完全采用第一条资源本地化的思路,那么你的WebView缓存是必须要开启的(虽然这一思路和第一条有重合的地方)。

WebSettings settings = webView.getSettings();
settings.setAppCacheEnabled(true);
settings.setDatabaseEnabled(true);
settings.setDomStorageEnabled(true);//开启DOM缓存
settings.setCacheMode(WebSettings.LOAD_DEFAULT);

在网络正常时,采用默认缓存策略,在缓存可获取并且没有过期的情况下加载缓存,否则通过网络获取资源以减少页面的网络请求次数。

这里值得提起的是,我们经常在app里用WebView展示页面时,并不想让用户觉得他是在访问一个网页。因为倘若我们的app里网页非常多,而我们给用户的感觉又都像在访问网页的话,我们的app便失去了意义。(我的意思是为什么用户不直接使用浏览器呢?)

所以这时,离线缓存的问题就值得我们注意。我们需要让用户在没有网的时候,依然能够操作我们的app,而不是面对一个和浏览器里的网络错误一样的页面,哪怕他能进行的操作十分有限。

这里我的思路是,在开启缓存的前提下,WebView在加载页面时检测网络变化,倘若在加载页面时用户的网络突然断掉,我们应当更改WebView的缓存策略。

ConnectivityManager connectivityManager = (ConnectivityManager)getSystemService(Context.CONNECTIVITY_SERVICE);
NetworkInfo networkInfo = connectivityManager.getActiveNetworkInfo();
if(networkInfo.isAvailable()) {
    settings.setCacheMode(WebSettings.LOAD_DEFAULT);//网络正常时使用默认缓存策略
} else {
    settings.setCacheMode(WebSettings.LOAD_CACHE_ONLY);//网络不可用时只使用缓存
}

既然有缓存,就要有缓存控制,与一相似的是我们也要建立缓存控制机制,定期或接受服务器通知来进行缓存的清空或更新。

第三个,就是延迟加载和执行js

在WebView中,onPageFinished()的回调意味着页面加载的完成。但该方法会在JavScript脚本执行完成后才会触发,倘若我们要加载的页面使用了JQuery,会在处理完DOM对象,执行完$(document).ready(function() {})后才会渲染并显示页面。这是不可接受的,所以我们需要对Js进行延迟加载,当然这部分是Web前端的工作。

如果说还有什么

那就是JsBridge一律不得滥用,这个对页面加载的完成速度是有很大影响的,倘若一个页面很多操作都通过JSbridge来控制,再怎么优化也无济于事(因为毕竟有那么多操作要实际执行)。同时要注意的是,不管你是否对资源进行缓存,都请将资源在服务器端进行压缩。因为无论是资源的获取和更新,都是要从服务器获取的,所以对于资源文件的压缩其实是最直接也最应该做的事情之一,但是一般服务器端都会做好,所以主要就是上面这三件事。

以上全部来自开车指南


资料

最全面总结 Android WebView与 JS 的交互方式

WebView·开车指南

猜你喜欢

转载自blog.csdn.net/g777520/article/details/79548688
今日推荐