Android webview详解

xiaoxiao2021-02-27  356

Android Hybrid解析

    市面上的App大致分为3类::Native App、Web App和Hybrid App,Hybrid App兼具"Native App"良好的用户交互体验和"Web App"跨平台开发的优势

   Webview解析

    解决webview加载劣势——资源预加载:

         资源加载缓慢: H5页面是从服务器上下发的,客户端的页面在内存里面,加载网页的时间更长,而且受限于网络情况,但是这种问题在某种程度上市可以弥补的,比如我们可以做一些资源预加载方案,下面列举资源预加载:

         一、使用webview自身的缓存机制: 如果我们在App里面访问一个页面,短时间内再次访问这个页面,就会感觉第二次打开的时候顺畅很多,加载速度比第一次的时间要短,这个就是因为webview自身内部会做一些缓存,只要打开过得资源,他都会试着缓存到本地,第二次需要访问的时候它直接从本地读取,但这个读取其实是不太稳定的东西,关掉之后,或者说这种缓存失效之后,系统会自动把他清楚。我们在应用启动的时候开一个像素的webview,事先访问以下我们常用的资源,后续打开页面的时候如果再用到这些资源他就可以从本地读取,页面加载的时间会短一些。

         二、自己去构建,自己管理缓存: 把这些需要预加载的资源放在App里面,他可能是预先放进去的,也可能是后续下载的,问题在于前端这些页面怎么去缓存,两个方案,第一种是前端可以在H5打包的时候把里面的资源URL进行替换,这样可以直接访问本地地址;第二种是客户端可以拦截这些网页发出的所有请求做替换:美团就是使用的的第二种预加载方案: 详情看美团大众点评Hybrid化建设,实现原理:每当webview发起资源请求的时候,我们会拦截这些资源的请求,去本地检查一下我们这些静态资源本地离线包有没有。针对本地的缓存文件我们有些策略能够及时的去更新它,为了安全考虑,也需要同时做一些预加载和安全包的加密工作。预加载有以下几点优势:

           1、我们拦截了webview里面发出的所有的请求,但是并没有替换里面的前端应用的任何代码,前端这套页面代码可以在App内,或者其他的App里面都可以直接访问,他不需要为我们App做定制化的东西

            2、这些URL请求,他会直接带上先前用户操作留下的Cookie,因为我们没有更改资源原始URL地址;

            3、整个前端在用离线包和缓存文件的时候是完全无感知的,前端只用管写一个自己的页面,客户端会帮他处理好这样一些静态资源预加载的问题,有这个离线包的话加载速度会变快很多,特别是弱网情况下,没有这些离线包加载速度会慢一些。而且如果本地离线包的版本不能跟H5匹配的话,H5页面也不会发生什么问题。

    WebView的常见设置:

            WebSetting webSettings=webView.getSettings();

             //设置这个属性为true允许webview和js代码进行交互,这个本身会有漏洞

            webSettings.setJavaScriptEnabled(true);

            //设置WebView是否可以打开WebView新窗口

            webSettings.setJavaScriptCanOpenWindowAutomatically(true);

            //webview是否支持多窗口,如果设置未true,需要重写

            //WebChromeClient#onCreateWindow(WebView,boolean,boolean,Message)函数,默认为false

            webSettings.setSuppportMutipleWindows(true); 

           //这个属性用来设置webview是否能够加载图片资源,包括哪些使用data uri协议嵌入的图片。使用setBlockNetworkImage(boolean)方法来控制仅仅加载使用网络URI协议的图片,需要提到的一点是如果这个设置从false变为true之后,所有被内容引用的正在显示的webview图片资源都会被自动加载,该标识默认值为true。

           webSettings.setLoadsImagesAutomatically(false);

           //标识是否加载网络上的图片(使用http或者https域名的资源),需要注意的是如果getLoadsImageAutomatically不返回true,这个标识将没有作用

            webSettings.setBlockNetworkImage(boolean)

            //显示webView提供的缩放控件

            webSettings.setDisplayZoomControls(true);

            webSettings.setBuiltInZoomControls(true)

             //设置是否启动WebView API,默认值为false

            webSettings.setDatabaseEnabled(true);

            //打开webview的storage功能,这样JS的localStorage,sessionStorage对象才可以使用(比如一张网页的拼图js页面)

            webSettings.setDomStorageEnabled(true);

            //打开WebView的LBS功能,这样JS的geolocation对象才可以使用

            webSettings.setGeolocationEnabled(true);

            webSettings.setGeolocationDatabasePath("");

            //设置是否打开webview表单数据的保存功能

             webSettings.setSaveFormData(true);

            //设置webview的默认的userAgent字符串

            webSettings.setUserAgentString("");

            //设置是否WebView支持"viewport"的HTML meta tag,这个标识用来屏幕自适应的,当这个标识设置为false时,页面布局的宽度被一直设置为css中控制的webview的宽度;如果设置为true并且页面含有viewport meta tag,那么被这个tag声明的宽度将会被使用。

            //webSettings.setUseWideViewPort(false);

            //设置webview的字体,可以通过这个函数,改变webview的字体,默认字体为"sans-serif"

             webSettings.setStandardFontFamily("");

              //设置webview字体的大小,默认大小为16

             webSettings.setDefaultFontSize(20);

             //设置webview支持的最小字体大小,默认为8

              webSettings.setMinimumFontSize(12);

              //设置页面是否支持缩放

              webSettings.setSupportZoom(true);

               //设置文本的缩放倍数,默认为100

              webSettings.setTextZoom(2);

             

然后还有最常用的 WebViewClient 和 WebChromeClient,WebViewClient主要辅助WebView执行处理各种响应请求事件的,比如:

onLoadResource

onPageStart

onPageFinish

onReceiveError

onReceivedHttpAuthRequest

shouldOverrideUrlLoading

WebChromeClient 主要辅助 WebView 处理J avaScript 的对话框、网站 Logo、网站 title、load 进度等处理:

onCloseWindow(关闭WebView)

onCreateWindow

onJsAlert

onJsPrompt

onJsConfirm

onProgressChanged

onReceivedIcon

onReceivedTitle

onShowCustomView    

   WebView的缓存模式:         LOAD_CACHE_ONLY: 不使用网络,只读取本地缓存数据         LOAD_DEFAULT: 根据cache-control决定是否从网络上取数据         LOAD_NO_CACHE: 不使用缓存,只从网络获取数据         LOAD_CACHE_ELSE_NETWORK: 只要本地有,无论是否过期,或者网页使用的缓存模式是"no-cache",都使用缓存中的数据,在没有缓存时才从网络上获取     Webview清空缓存和清空历史记录:           CacheManager处理webview缓存相关,mWebview.clearCache(true):清空缓存,mWebview.clearHistory()      Webview 与 native的交互            js调用native三种方式:            第一种方式:通过addJavascriptInterface方法进行添加对象映射: 第一步,设置属性:mWebView.getSettings().setJavaScriptEnabled(true);第二步,Native需要定义一个类并编写带有JavascriptInterface注解的调用方法              public class JSObject { private Context mContext; public JSObject(Context context) { mContext = context; } @JavascriptInterface public String showToast(String text) { Toast.show(mContext, text, Toast.LENGTH_SHORT).show(); return "success"; } } ... //特定版本下会存在漏洞 mWebView.addJavascriptInterface(new JSObject(this), "myObj");         js调用代码: function showToast(){ var result = myObj.showToast("我是来自web的Toast"); }        第二种方式: 利用WebViewClient接口回调方法拦截url: 这种方式使用频次也很高,上面介绍的WebViewClient,其中有个回调接口shouldOverrideUrlLoading(WebView view,String url),我们利用这个拦截url,然后解析这个url的协议,如果发现是我们预先约定好的协议就开始解析参数,执行相应的逻辑, 注意:这个方法在API24版本已经废弃了,需要使用shouleOverrideUrlLoading(WebView view,WebResourceRequest request) 拦截示例代码如下: public boolean shouldOverrideUrlLoading(WebView view, String url) { //假定传入进来的 url = "js://openActivity?arg1=111&arg2=222",代表需要打开本地页面,并且带入相应的参数 Uri uri = Uri.parse(url); String scheme = uri.getScheme(); //如果 scheme 为 js,代表为预先约定的 js 协议 if (scheme.equals("js")) { //如果 authority 为 openActivity,代表 web 需要打开一个本地的页面 if (uri.getAuthority().equals("openActivity")) { //解析 web 页面带过来的相关参数 HashMap<String, String> params = new HashMap<>(); Set<String> collection = uri.getQueryParameterNames(); for (String name : collection) { params.put(name, uri.getQueryParameter(name)); } Intent intent = new Intent(getContext(), MainActivity.class); intent.putExtra("params", params); getContext().startActivity(intent); } //代表应用内部处理完成 return true; } return super.shouldOverrideUrlLoading(view, url); }     js中的代码指定location地址: function openActivity(){ document.location = "js://openActivity?arg1=111&arg2=222"; }      webview通过shouldOverrideUrlLoading拦截js相关数据并做处理,处理完成后如果web端想要得到方法的返回值,只能通过webview的loadUrl方法去执行js方法把返回值传递回去,相关代码如下:        //java mWebView.loadUrl("javascript:returnResult(" + result + ")");//javascript function returnResult(result){ alert("result is" + result); }        备注:这种方式打开Native页面还是很合适的,制定好相应的协议,就能够让web端具有打开所有本地页面的能力了。         第三种方式: 利用WebChromeClient回调接口的三个方法拦截消息          @Override public boolean onJsAlert(WebView view, String url, String message, JsResult result) { return super.onJsAlert(view, url, message, result); } @Override public boolean onJsConfirm(WebView view, String url, String message, JsResult result) { return super.onJsConfirm(view, url, message, result); } @Override public boolean onJsPrompt(WebView view, String url, String message, String defaultValue, JsPromptResult result) { //假定传入进来的 message = "js://openActivity?arg1=111&arg2=222",代表需要打开本地页面,并且带入相应的参数 Uri uri = Uri.parse(message); String scheme = uri.getScheme(); if (scheme.equals("js")) { if (uri.getAuthority().equals("openActivity")) { HashMap<String, String> params = new HashMap<>(); Set<String> collection = uri.getQueryParameterNames(); for (String name : collection) { params.put(name, uri.getQueryParameter(name)); } Intent intent = new Intent(getContext(), MainActivity.class); intent.putExtra("params", params); getContext().startActivity(intent); //代表应用内部处理完成 result.confirm("success"); } return true; } return super.onJsPrompt(view, url, message, defaultValue, result); }       通过WebChromeClient接口,拦截JS中的几个提示方法,也就是几种样式的对话框,在JS中有三个常用的对话框方法:         1、onJsAlert弹出警告框,一般情况下在Android中为Toast,在文本里面加入\n就可以换行;         2、onJsConfirm 弹出确认框,会返回布尔值,通过这个值可以判断点击是确认还是取消,true表示点击了确认,false表示点击了取消         3、onJsPrompt弹出输入框,点击确认返回输入框中的值,点击取消返回null         prompt方法调用如下所示:          function clickprompt(){ var result=prompt("js://openActivity?arg1=111&arg2=222"); alert("open activity " + result); }         这里需要注意的是 prompt 里面的内容是通过 message 传递过来的,并不是第二个参数的 url,返回值是通过 JsPromptResult 对象传递。为什么要拦截 onJsPrompt 方法,而不是拦截其他的两个方法,这个从某种意义上来说都是可行的,但是如果需要返回值给 web 端的话就不行了,因为 onJsAlert 是不能返回值的,而 onJsConfirm 只能够返回确定或者取消两个值,只有 onJsPrompt 方法是可以返回字符串类型的值,操作最全面方便。       以上三种方案的总结对比       第一种,调用onJavaScriptInterface方法: 是现在目前最普遍的用法,方便简洁,但唯一的不足是在4.2系统以下存在漏洞问题;       第二种,通过shouldOverrideUrlLoading,拦截url并解析,如果是已经约定好的协议则进行相应规定好的操作,缺点是协议的约束需要记录一个规范的文档,而且从Native层往web层传递值比较繁琐,优点就是不会存在漏洞,ios7之下的版本就是使用的这种方式       第三种,利用js拦截方法,和第二种方式的思想其实是类似的,只是拦截的方法变了,这里拦截了JS中的三种对话框方法,二这三种对话框方法的区别在于返回值问题,alert对话框没有返回值,confirm对话框只有两种状态的返回值,prompt对话框可以返回任类型的返回值,缺点就是协议的制定比较麻烦,需要记录详细的文档,但是不会存在第二种方法的漏洞问题,这里有个疑问?如果l拦截onJsPromt是不是网页需要有个输入弹出框然后执行我们的拦截,如果实际需求不需要弹出输入框这种情况是不是就不能用了?        native调用js        第一种方式,webview的loadUrl方法:         //java mWebView.loadUrl("javascript:show(" + result + ")");//javascript <script type="text/javascript"> function show(result){ alert("result"=result); return "success"; } </script>       注意,调用的名字一定要对应上,要不然调用不成功,而且js的调用一定要在onPageFinished函数回调之后才能调用,要不然会失败。         第二种方式        Google在Android 4.4为我们新增了一个方法,这个方法比loadUrl方法更方便简洁,而且比loadUrl效率更高,因为loadUrl的执行会造成页面刷新一次,这个方法不会,因为这个方法是在4.4版本财引入的,所以我们使用的时候需要添加版本判断       final int version = Build.VERSION.SDK_INT; if (version < 18) { mWebView.loadUrl(jsStr); } else { /*jsStr是javascript执行脚本,ValueCallBack是执行完脚本的返回值,也可能是null,在没有返回的情况*/ mWebView.evaluateJavascript(jsStr, new ValueCallback<String>() { @Override public void onReceiveValue(String value) { //此处为 js 返回的结果 } }); }                                                                                        参考自微信公众号——App架构师,Android WebView详解
转载请注明原文地址: https://www.6miu.com/read-1154.html

最新回复(0)