美文网首页
WebView学习笔记

WebView学习笔记

作者: Small_Cake | 来源:发表于2022-05-23 16:12 被阅读0次

    参考:
    WebView全面解析
    安卓中原生与H5(webview)之间交互时cookie的同步
    详细的Webview使用攻略
    WebView与JavaScript的交互总结
    webview与HTTPS
    速度提升框架:VasSonic

    Android展示网页内容有两种方式,一种是通过手机系统浏览器,二是在布局中内嵌WebView
    WebView是android中一个非常重要的控件,它的作用是用来展示一个web页面。它使用的内核是webkit引擎,4.4版本之后,直接使用Chrome作为内置网页浏览器

    0.加载网页和内容

        //方式一:加载一个网页
        webView.loadUrl("http://www.baidu.com");
    
        //方式二:加载应用资源文件内的网页
        webView.loadUrl("file:///android_asset/test.html");
    
        //方式三:加载一段代码
        webView.loadData(String data,String mimeType, String encoding);
       //方式四:加载网页或内容(推荐)
        webView.loadDataWithBaseURL("http://www.baidu.com", body, "text/html", "utf-8",null);
    

    1.WebSetting

    方法名 方法描述
    setJavaScriptEnabled(true) 是否支持JS加载,默认为false
    setJavaScriptCanOpenWindowsAutomatically(true) 支持通过JS打开新窗口
    setPluginsEnabled(true) 是否支持插件
    setUseWideViewPort(true) 将图片调整到适合webview的大小
    setLoadWithOverviewMode 缩放至屏幕的大小
    setSupportZoom(true) 支持缩放,默认为true。是setBuiltInZoomControls(true)的前提
    setBuiltInZoomControls(true) 设置内置的缩放控件。若为false,则该WebView不可缩放
    setDisplayZoomControls(false) 隐藏原生的缩放控件
    setCacheMode(WebSettings.LOAD_CACHE_ELSE_NETWORK) LOAD_CACHE_ONLY: 不使用网络,只读取本地缓存数据LOAD_DEFAULT: (默认)根据cache-control决定是否从网络上取数据。LOAD_NO_CACHE: 不使用缓存,只从网络获取数据.LOAD_CACHE_ELSE_NETWORK,只要本地有,无论是否过期,或者no-cache,都使用缓存中的数据。
    setAllowFileAccess(true) 设置可以访问文件
    setLoadsImagesAutomatically(true) 支持自动加载图片
    setLayoutAlgorithm(WebSettings.LayoutAlgorithm.SINGLE_COLUMN) 支持内容重新布局
    setDefaultTextEncodingName("utf-8") 设置编码格式
    setDomStorageEnabled(true) 开启 DOM storage API 功能,有的网页如淘宝样式加载不出来就需要配置此项DOM storage
    setDatabaseEnabled(true) 开启 database storage API 功能
    setAppCacheEnabled(true) 开启 Application Caches 功能
    setAppCachePath(cacheDirPath) 设置 Application Caches 缓存目录
    setAppCacheMaxSize(1024*8) 缓存的最大空间
    setMixedContentMode(WebSettings.MIXED_CONTENT_ALWAYS_ALLOW) 5.1以上默认禁止了https和http混用,所以大于5.1的需要开启
    setRenderPriority(WebSettings.RenderPriority.HIGH) 提高渲染的优先级
    setStandardFontFamily("") 设置 WebView 的字体,默认字体为 "sans-serif"
    webSettings.setDefaultFontSize(20) 设置WebView字体的大小,默认大小为 16
    webSettings.setMinimumFontSize(12) 设置 WebView支持的最小字体大小,默认为 8
    setGeolocationEnabled(true) 允许网页执行定位操作

    2.WebViewClient

    方法名 方法描述
    onPageStarted(WebView view, String url, Bitmap favicon) 页面开始加载
    onPageFinished(WebView view, String url) 页面加载结束
    boolean shouldOverrideUrlLoading(WebView view, String url)或(新5.0以上)boolean shouldOverrideUrlLoading(WebView view, WebResourceRequest request) 拦截URL请求,重定向
    onReceivedError() 加载页面的服务器出现错误(比如404)时回调。
    onReceivedSslError(WebView view, SslErrorHandler handler, SslError error) https请求异常
    shouldInterceptRequest(WebView view, String url) 每次发生资源加载
    onLoadResource(WebView view, String url) 页面加载资源时调用
    shouldOverrideKeyEvent() 重写此方法才能处理浏览器中的按键事件
    doUpdateVisitedHistory() 更新历史记录
    onFormResubmission() 应用程序重新请求网页数据
    onReceivedHttpAuthRequest() 获取返回信息授权请求
    onScaleChanged() WebView发生缩放改变时调用。
    onUnhandledKeyEvent() Key事件未被加载时调用
    • shouldOverrideUrlLoading():拦截URL请求,重定向(有2个方法,一个是兼容5.0以下,一个是兼容5.0以上,保险起见两个都重写)。
      • 无论返回true还是false,只要为WebView设置了WebViewClient,系统就不会再将url交给第三方的浏览器去处理了。
      • 如果返回false,代表将url交给当前WebView加载,也就是正常的加载状态;shouldOverrideUrlLoading()返回true,代表开发者已经对url进行了处理,WebView就不会再对这个url进行加载了。
      • 另外,使用post的方式加载页面,此方法不会被调用。
    webView.setWebViewClient(new WebViewClient(){
    
          //重定向URL请求,返回true表示拦截此url,返回false表示不拦截此url。
          @Override
          public boolean shouldOverrideUrlLoading(WebView view, String url) {
              //作用1:重定向url
              if(url.startsWith("weixin://")){
                  url = url.replace("weixin://","http://");
                  webView.loadUrl(url);
              }
    
              //作用2:在本页面的WebView打开,防止外部浏览器打开此链接
              view.loadUrl(url);
              return true;
          }
      });
    
    • 情况一:loadUrl()无重定向时
    onPageStarted->onPageFinished
    
    • 情况二:loadUrl()网页A重定向到B时
    onPageStarted->onPageFinished->shouldOverrideUrlLoading->onPageStarted->onPageFinished
    

    也有可能重定向多次

    shouldOverrideUrlLoading->shouldOverrideUrlLoading->onPageStarted->onPageFinished
    
    • 情况三:在已加载的页面中点击链接,加载页面A(无重定向)
    shouldOverrideUrlLoading->onPageStarted->onPageFinished
    
    • 情况四:在已加载的页面中点击链接,加载页面A(页面A重定向至页面B)
    shouldOverrideUrlLoading->shouldOverrideUrlLoading->onPageStarted->onPageFinished
    
    • 情况五:执行goBack/goForward/reload方法
    onPageStarted->onPageFinished
    
    • 情况六:发生资源加载
    shouldInterceptRequest->onLoadResource
    
    • onReceivedSslError():webView默认是不处理https请求的,页面显示空白,需要进行如下设置:
    webView.setWebViewClient(new WebViewClient() {    
            @Override    
            public void onReceivedSslError(WebView view, SslErrorHandler handler, SslError error) {    
                handler.proceed();    //表示等待证书响应
            // handler.cancel();      //表示挂起连接,为默认方式
            // handler.handleMessage(null);    //可做其他处理
            }    
        });
    
    异常:未知URL空间异常
                public boolean shouldOverrideUrlLoading(WebView view, String url) {
                    if (!url.startsWith("http")) return true;
                    return super.shouldOverrideUrlLoading(view, url);
                }
    

    3.WebChromeClient

    辅助 WebView 处理 Javascript 的对话框,网站图标,网站标题等等

    方法名 方法描述
    onProgressChanged() 获得网页的加载进度并显示。
    onReceivedTitle() 获得网页的标题时回调。
    onReceivedIcon() 获得网页的图标时回调。
    onCreateWindow() 打开新窗口时回调。
    onCloseWindow() 关闭窗口时回调。
    onJsAlert() 网页弹出提示框时触发此方法
    onJsConfirm() 支持javascript的确认框
    onJsPrompt() 支持javascript输入框,点击确认返回输入框中的值,点击取消返回 null。
    @Override
      public boolean onJsAlert(WebView view, String url, String message, JsResult result) {
          Toast.makeText(MainActivity.this,"Im alert",Toast.LENGTH_SHORT).show();
    
          //部分机型只会弹出一次提示框,调用此方法即可解决此问题。
          result.cancel();
          //返回true表示不弹出系统的提示框,返回false表示弹出
          return true;
      }
    
    webview.setWebChromeClient(new WebChromeClient() {
            
                @Override
    public boolean onJsConfirm(WebView view, String url, String message, final JsResult result) {
        new AlertDialog.Builder(MainActivity.this)
                .setTitle("JsConfirm")
                .setMessage(message)
                .setPositiveButton("OK", new DialogInterface.OnClickListener() {
                    @Override
                    public void onClick(DialogInterface dialog, int which) {
                        result.confirm();
                    }
                })
                .setNegativeButton("Cancel", new DialogInterface.OnClickListener() {
                    @Override
                    public void onClick(DialogInterface dialog, int which) {
                        result.cancel();
                    }
                })
                .setCancelable(false)
                .show();
    // 返回布尔值:判断点击时确认还是取消
    // true表示点击了确认;false表示点击了取消;
        return true;
    }
    
    webview.setWebChromeClient(new WebChromeClient() {
                @Override
                public boolean onJsPrompt(WebView view, String url, String message, String defaultValue, final JsPromptResult result) {
        final EditText et = new EditText(MainActivity.this);
        et.setText(defaultValue);
        new AlertDialog.Builder(MainActivity.this)
                .setTitle(message)
                .setView(et)
                .setPositiveButton("OK", new DialogInterface.OnClickListener() {
                    @Override
                    public void onClick(DialogInterface dialog, int which) {
                        result.confirm(et.getText().toString());
                    }
                })
                .setNegativeButton("Cancel", new DialogInterface.OnClickListener() {
                    @Override
                    public void onClick(DialogInterface dialog, int which) {
                        result.cancel();
                    }
                })
                .setCancelable(false)
                .show();
    
        return true;
    }
    

    4.Cookie

    安卓中原生与H5(webview)之间交互时cookie的同步
    在执行webview的loadurl之前,先执行cookie同步

    • 设置cookie
     public static void synchronousWebCookies(Context context,String url,String cookies){
        if ( !TextUtils.isEmpty(url) )
            if (!TextUtils.isEmpty(cookies) ) {
                if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP){
                    CookieSyncManager.createInstance( context);
                }
                CookieManager cookieManager = CookieManager.getInstance();
                cookieManager.setAcceptCookie( true );
                cookieManager.removeSessionCookie();// 移除
                cookieManager.removeAllCookie();
                StringBuilder sbCookie = new StringBuilder();//创建一个拼接cookie的容器,为什么这么拼接,大家查阅一下http头Cookie的结构  
                sbCookie.append(cookies);//拼接sessionId  
    //          sbCookie.append(String.format(";domain=%s", ""));  
    //          sbCookie.append(String.format(";path=%s", ""));  
                String cookieValue = sbCookie.toString();  
                cookieManager.setCookie(url, cookieValue);//为url设置cookie  
                CookieSyncManager.getInstance().sync();//同步cookie
                            String newCookie = cookieManager.getCookie(url);
                LogManager.i("同步后cookie", newCookie);
            }
        }
    
    • 获取cookie
    public static String syncCookie(String url) {
        CookieManager cookieManager = CookieManager.getInstance();
        return cookieManager.getCookie(url);
    }
    
    • 清除cookie
    CookieManager.getInstance().removeSessionCookies();// 移除所有过期 cookie
    CookieManager.getInstance().removeAllCookies(); // 移除所有的 cookie
    //设置清除cookie后的回调方法
    private void removeCookie(Context context) {
        CookieManager.getInstance().removeAllCookies(new ValueCallback<Boolean>() {
            @Override
            public void onReceiveValue(Boolean value) {
                // 清除结果
            }
        });
    }
    

    5. JS 交互

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

    Android调用JS 方法描述
    loadUrl() loadUrl("javascript:callJS()");只会执行一次
    evaluateJavascript() 通过WebViewevaluateJavascript(),可多次调用,需要Android 4.4以上

    首先开启js

     //允许WebView使用JS
        settings.setJavaScriptEnabled(true);
        //支持通过JS打开新窗口(允许JS弹窗)
        settings.setJavaScriptCanOpenWindowsAutomatically(true);
    
    • a.Android通过loadUrl调用js
     btnCallJs.setOnClickListener(view1 -> webView.loadUrl("javascript:callJS()"));
    

    只有第一次会调用,且调用需要在onPageFinished方法之后

    • b Android通过evaluateJavascript()调用js
            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
                mBinding.btnCallJs.setOnClickListener(view1 ->
                        mBinding.webView.evaluateJavascript("javascript:callJS()", s -> {
                            //s是JS方法的返回值
                            L.e(TAG,s);
                        }));
            }
    

    这种方式比第一种方式效率高(执行时不会刷新页面),同时可以获取返回值。缺点是只兼容到Android4.4(19)版本以后,如果重写了WebChromeClientonJsAlert,返回值为true的情况下,JsResult需要调用cancel,如下:

    @Override
      public boolean onJsAlert(WebView view, String url, String message, JsResult result) {
          Toast.makeText(MainActivity.this,"Im alert",Toast.LENGTH_SHORT).show();
          //部分机型只会弹出一次提示框,调用此方法即可解决此问题。
          result.cancel();
          //返回true表示不弹出系统的提示框,返回false表示弹出
          return true;
      }
    
    JS调用Android 方法描述
    addJavascriptInterface() 对象映射webView.addJavascriptInterface(new AndroidJs(),"android");
    shouldOverrideUrlLoading () WebViewClientshouldOverrideUrlLoading ()方法回调拦截url
    onJsAlert() 通过 WebChromeClientonJsAlert()拦截JS对话框alert()
    onJsConfirm() 通过 WebChromeClientonJsConfirm()拦截JS对话框confirm()
    onJsPrompt() 通过 WebChromeClientonJsAlert()拦截JS对话框prompt()
    • a. addJavascriptInterface()的使用

    创建一个测试的网页test.htmlassets文件夹中

    <head>
    </head>
    <body>
    <h1 onClick='callAndroid()'>这是JS调用原生1</h1>
    <Script>
            function callAndroid(){
                android.hello("js调用了");
            }
        </Script>
    </body>
    

    创建一个对象,关联js调用方法

    public class AndroidJs{
        // 定义JS需要调用的方法
        // 被JS调用的方法必须加入@JavascriptInterface注解
        @JavascriptInterface
        public void hello(String msg) {
            ToastUtil.showShort(msg);
        }
    }
    

    Activity使用时:加载网页test.html,让webView把对象,和网页函数关联起来

    webView.loadUrl("file:///android_asset/test.html");
    webView.addJavascriptInterface(new AndroidJs(),"android");
    
    • 注意:正常情况下这里的test.html应该是后台的网页地址,new AndroidJs()就是我们桥接的jsAndroid的对象,后面的的"android"就是我们在网页test.html中使用的android.hello("js调用了");
    • 它的调用顺序应该是,我们点击了网页test.html中的h1标签,触发onClick方法,执行callAndroid()函数,在callAndroid()函数中又会调用android.hello("js调用了");,它就会通过android去查询安卓对应的映射对象AndroidJs,找到后再调用里面AndroidJs里面的hello方法,并把参数传递进去。
    • 这种方法优点是使用简单,缺点是存在严重的漏洞问题,请看文章:你不知道的 Android WebView 使用漏洞
    • b.shouldOverrideUrlLoading ()的使用
    • 创建一个javascript.htmlsrc/main/assets文件夹里
    <!DOCTYPE html>
    <html>
    <head>
        <script>
             function callAndroid(){
                /*约定的url协议为:js://webview?arg1=111&arg2=222*/
                document.location = "js://webview?arg1=111&arg2=222";
             }
          </script>
    </head>
    <!-- 点击按钮则调用callAndroid()方法  -->
    <body>
    <button type="button" id="button1" onclick="callAndroid()">点击调用Android代码</button>
    </body>
    </html>
    
    • 页面调用
            String url = "file:///android_asset/javascript.html";
            webView.loadUrl(url);
            webView.setWebViewClient(new WebViewClient(){
                @Override
                public boolean shouldOverrideUrlLoading(WebView view, String url) {
                    //"js://webview?arg1=111&arg2=222"
                    Uri uri = Uri.parse(url);
                    String scheme = uri.getScheme();//空间:相当于冒号之前的部分
                    String authority = uri.getAuthority();//授权:双斜杠之后,?之前的部分
                    if (scheme.equals("js")){
                        if (authority.equals("webview")){
                            Set<String> keys = uri.getQueryParameterNames();//参数key:arg1,arg2
                            for (String key : keys) {
                                L.e("key=="+key);
                                L.e("value=="+uri.getQueryParameter(key));
                            }
                        }
                        return true;
                    }
                    return super.shouldOverrideUrlLoading(view, url);
                }
    
            });
    
    image.png
    • 与后台定义好对应的空间和授权,拦截到对应的空间后,根据不同的参数来调用不同的Android原生方法,就是这样。
    • c.通过 拦截WebChromeClientonJsAlert(),onJsConfirm(),onJsPrompt()方法,来实现调用Android的原生方法。
    @Override
    public boolean onJsAlert(WebView view, String url, String message, JsResult result) {
        L.e("url=="+url);
        L.e("message=="+message);
        return super.onJsAlert(view, url, message, result);
    }
    
    alert

    test.html

    <!DOCTYPE html>
    <html>
    <body>
    <h1 onClick='cleanData()'>清空数据</h1>
    <script>
        function cleanData(){
        var a=confirm("确定要清空数据吗?");
             if(a==true){
                  document.write("恭喜你清空了数据!");
             }else{
                  document.write("取消清空操作");
             }
        }
    </script>
    </body>
    </html>
    
    webView.setWebChromeClient(new WebChromeClient(){
                @Override
            public boolean onJsConfirm(WebView view, String url, String message, JsResult result) {
                    L.e("url=="+url);
                    L.e("message=="+message);
                    new AlertDialog.Builder(_mActivity).
                            setTitle("警告").setMessage(message).setPositiveButton("确定",
                            new DialogInterface.OnClickListener() {
                                @Override
                                public void onClick(DialogInterface arg0, int arg1) {
                                    result.confirm();
                                }
                            })
                            .setNegativeButton("取消", new DialogInterface.OnClickListener() {
                                @Override
                                public void onClick(DialogInterface dialogInterface, int i) {
                                    result.cancel();
                                }
                            })
                            .create().show();
                    return true;
                }
    });
    
    confirm
    • 我们可以拦截弹出框的提示语:"确定要清空数据吗?"AlertDialog,让后根据AlertDialog操作执行不同的JsResult,如确定result.confirm();取消result.cancel();

    test.html

    <!DOCTYPE html>
    <html>
    <body>
    <h1 onClick='takePrompt()'>体能测试</h1>
    <script>
        function takePrompt(){
            var result = prompt("请输入你的身高?","170");
            alert(result);
        }
    </script>
    </body>
    </html>
    
            mBinding.webView.setWebChromeClient(new WebChromeClient(){
                @Override
                public boolean onJsPrompt(WebView view, String url, String message, String defaultValue, JsPromptResult result) {
                    L.e(TAG,message + " , " + defaultValue);
                    if(message.contains("身高")){
                        final EditText et = new EditText(_mActivity);
                        new AlertDialog.Builder(_mActivity)
                                .setMessage(message)
                                .setView(et)
                                .setPositiveButton("确定", new DialogInterface.OnClickListener() {
                                    @Override
                                    public void onClick(DialogInterface arg0, int arg1) {
                                        String s = et.getText().toString();
                                        result.confirm(s);
                                    }
                                })
                                .create().show();
                    }
                    return true; //返回true表示不弹出系统提示框
                }
            });
    
    prompt,默认值170
    输入后

    6. WebView相关方法

    方法名 方法描述
    canGoBack() 是否可以返回
    goBack() 返回
    canGoForward() 是否可以前进
    goForward() 前进网页
    goBackOrForward(intsteps) 以当前的index为起始点前进或者后退到历史记录中指定的steps,如果steps为负数则为后退,正数则为前进
    onResume() 激活WebView为活跃状态,能正常执行网页的响应
    onPause() 通过onPause动作通知内核暂停所有的动作,比如DOM的解析、plugin的执行、JavaScript执行
    pauseTimers() 当应用程序(存在webview)被切换到后台时,这个方法不仅仅针对当前的webview而是全局的全应用程序的webview,它会暂停所有webviewlayout,parsing,javascripttimer。降低CPU功耗。
    resumeTimers() 恢复pauseTimers状态
    destroy() 销毁Webview
    clearCache(true) 清除网页访问留下的缓存,由于内核缓存是全局的因此这个方法不仅仅针对webview而是针对整个应用程序.
    clearHistory() 清除当前webview访问的历史记录,除了当前访问记录
    clearFormData() 清除自动完成填充的表单数据
    stopLoading () 停止当前加载
    zoomBy(float zoomFactor) 在此Web视图中执行缩放操作,zoomFactor0.01100.0之间
    zoomIn() 在此Web视图中执行放大操作
    zoomOut() 在此Web视图中执行缩小

    Fragment中返回网页的上一页,而不是直接关闭

        @Override
        public boolean onBackPressedSupport() {
            if (webView.canGoBack()) {
                webView.goBack();
                return true;
            }
            return super.onBackPressedSupport();
        }
    

    Activity中返回网页的上一页,而不是直接关闭

       @Override
        public boolean onKeyDown(int keyCode, KeyEvent event) {
            if (keyCode == KeyEvent.KEYCODE_BACK && webView.canGoBack()) {
                webView.goBack();
                return true;
            }
            return super.onKeyDown(keyCode, event);
        }
    

    7. Cache缓存

    缓存机制

    • 当加载 html 页面时,WebView会在/data/data/包名目录下生成 database 与 cache 两个文件夹
    • 请求的 URL记录保存在 WebViewCache.db,而 URL的内容是保存在 WebViewCache 文件夹下
    • 优先使用缓存WebView.getSettings().setCacheMode(WebSettings.LOAD_CACHE_ELSE_NETWORK);
    • 不使用网络,只读取本地缓存数据LOAD_CACHE_ONLY
    • (默认)根据cache-control决定是否从网络上取数据LOAD_DEFAULT
    • 不使用网络,只读取本地缓存数据LOAD_NO_CACHE
    • 只要本地有,无论是否过期,或者no-cache,都使用缓存中的数据LOAD_CACHE_ELSE_NETWORK
    离线加载: 每个 Application 只调用一次 WebSettings.setAppCachePath()WebSettings.setAppCacheMaxSize()
    if (NetStatusUtil.isConnected(getApplicationContext())) {
        webSettings.setCacheMode(WebSettings.LOAD_DEFAULT);//根据cache-control决定是否从网络上取数据。
    } else {
        webSettings.setCacheMode(WebSettings.LOAD_CACHE_ELSE_NETWORK);//没网,则从本地获取,即离线加载
    }
    
    webSettings.setDomStorageEnabled(true); // 开启 DOM storage API 功能
    webSettings.setDatabaseEnabled(true);   //开启 database storage API 功能
    webSettings.setAppCacheEnabled(true);//开启 Application Caches 功能
    
    String cacheDirPath = getFilesDir().getAbsolutePath() + APP_CACAHE_DIRNAME;
    webSettings.setAppCachePath(cacheDirPath); //设置  Application Caches 缓存目录
    

    8. 安全

    你不知道的 Android WebView 使用漏洞
    WebView挂马漏洞
    WebView远程执行代码漏洞浅析

    相关文章

      网友评论

          本文标题:WebView学习笔记

          本文链接:https://www.haomeiwen.com/subject/rzxbfctx.html