Android控件<第二十三篇>:超详细的Webview攻略(一)

原生webview的引擎是webkit,Android 4.4后直接使用了Chrome,那么是否还有其他引擎呢?有的,比如腾讯的X5内核和crosswalk,crosswalk我不太清楚,有兴趣的可以用用看,反正我没兴趣,目前腾讯的X5内核在国内已经很热门了,这也不排除腾讯的宣传效果,官方公布的X5内核的优势如下:

 1) 速度快:相比系统webview的网页打开速度有30+%的提升;
 2) 省流量:使用云端优化技术使流量节省20+%;
 3) 更安全:安全问题可以在24小时内修复;
 4) 更稳定:经过亿级用户的使用考验,CRASH率低于0.15%;
 5) 兼容好:无系统内核的碎片化问题,更少的兼容性问题;
 6) 体验优:支持夜间模式、适屏排版、字体设置等浏览增强功能;
 7) 功能全:在Html5、ES6上有更完整支持;
 8) 更强大:集成强大的视频播放器,支持视频格式远多于系统webview;
 9) 视频和文件格式的支持x5内核多于系统内核;
10) 防劫持是x5内核的一大亮点。

在这里我可以大胆的猜测,X5内核其实就是基于webkit上进一步优化,腾讯采用填坑的方式将webkit封装成了X5。
由于原生webview的坑比较多,不知道TX要填到什么时候才能将X5完善,一个完善的X5内核还是令人比较期待了,毕竟本人已经被原生webview坑了好久了。

WebView的状态,我把它理解为生命周期:
  //激活WebView为活跃状态,能正常执行网页的响应
  webView.onResume() ;
  //当页面被失去焦点被切换到后台不可见状态,需要执行onPause
  //通过onPause动作通知内核暂停所有的动作,比如DOM的解析、plugin的执行、JavaScript执行。
  webView.onPause();
  //当应用程序(存在webview)被切换到后台时,这个方法不仅仅针对当前的webview而是全局的全应用程序的webview 
  //它会暂停所有webview的layout,parsing,javascripttimer。降低CPU功耗。    
  webView.pauseTimers() 
  //恢复pauseTimers状态 
  webView.resumeTimers();
  //销毁Webview
  //在关闭了Activity时,如果Webview的音乐或视频,还在播放。就必须销毁Webview
  //但是注意:webview调用destory时,webview仍绑定在Activity上
  //这是由于自定义webview构建时传入了该Activity的context对象
  //因此需要先从父容器中移除webview,然后再销毁webview:
  rootLayout.removeView(webView); 
  webView.destroy();

当了解生命周期的前提下,我们不得不考虑webview内存泄漏的问题,网上也有相关的总结:
WebView内存泄漏--解决方法小结
从根源解决WebView内存泄漏、

前进和后退
  //是否可以后退
  Webview.canGoBack() 
  //后退网页
  Webview.goBack()
  //是否可以前进  
  Webview.canGoForward()
  //前进网页
  Webview.goForward()
  //以当前的index为起始点前进或者后退到历史记录中指定的steps
  //如果steps为负数则为后退,正数则为前进
  Webview.goBackOrForward(intsteps) 

Android客户端的返回键分为:硬件返回和软件返回,我们可以监听这两种返回,然后执行Webview.canGoBack()和Webview.goBack()。
实际上,如果第一次加载的网页有重定向链接时,可能会发生无法返回退出当前页面的效果,那么因为当返回到第一个历史网页时,就又被重定向了,我能想到的操作方案如下:

  1. 连续点击两次返回键,强制退出当前页面;(显然这样做不友好)

  2. 在导航栏增加一个关闭当前页面的按钮;(这是很多APP常用的方式)

  3. 捕获历史记录,对有重定向的链接做特定的处理

    //获取历史
    WebBackForwardList mWebBackForwardList = webvie.copyBackForwardList();
    

监听物理返回有两种方式:

    mywebview.setOnKeyListener(new View.OnKeyListener() {
        @Override
        public boolean onKey(View v, int keyCode, KeyEvent event) {
            if (event.getAction() == KeyEvent.ACTION_DOWN) {
                if (keyCode == KeyEvent.KEYCODE_BACK && mywebview.canGoBack()) {  //表示按返回键
                    mywebview.goBack();
                    return true;
                }
            }
            return false;
        }
    });

或者

@Override
public void onBackPressed() {
    if(mywebview !=  null && mywebview.canGoBack()){
        mywebview.goBack();
    }else{
        finish();
    }
}
清除缓存数据
  //清除网页访问留下的缓存
  //由于内核缓存是全局的因此这个方法不仅仅针对webview而是针对整个应用程序
  Webview.clearCache(true);

  //清除当前webview访问的历史记录
  //只会webview访问历史记录里的所有记录除了当前访问记录
  Webview.clearHistory();

  //这个api仅仅清除自动完成填充的表单数据,并不会清除WebView存储到本地的数据
  Webview.clearFormData();

这里需要注意的是,如果你使用了cookie,按照需求决定是否需要清除cookie

下面是完整的onDestroy代码

@Override
protected void onDestroy() {

    //清除Cookie
    CookieSyncManager.createInstance(this);
    CookieManager cookieManager = CookieManager.getInstance();
    cookieManager.removeAllCookie();
    CookieSyncManager.getInstance().sync();

    if( mywebview!=null) {

        mywebview.setWebChromeClient(null);
        mywebview.setWebViewClient(null);
        //清除网页访问留下的缓存,由于内核缓存是全局的因此这个方法不仅仅针对webview而是针对整个应用程序
        mywebview.clearCache(true);
        //只会webview访问历史记录里的所有记录除了当前访问记录
        mywebview.clearHistory();
        //这个api仅仅清除自动完成填充的表单数据,并不会清除WebView存储到本地的数据
        mywebview.clearFormData();

        // 如果先调用destroy()方法,则会命中if (isDestroyed()) return;这一行代码,需要先onDetachedFromWindow(),再
        // destory()
        ViewParent parent = mywebview.getParent();
        if (parent != null) {
            ((ViewGroup) parent).removeView(mywebview);
        }

        mywebview.stopLoading();
        // 退出时调用此方法,移除绑定的服务,否则某些特定系统会报错
        mywebview.getSettings().setJavaScriptEnabled(false);
        mywebview.clearView();
        mywebview.removeAllViews();
        mywebview.destroy();
    }

    super.onDestroy();

}

下面发一下Cookie的同步代码

public class SyncWebCookies {

    private SyncWebCookies(){}

    static class SingleHolder{
        public static SyncWebCookies instance = new SyncWebCookies();
    }

    public static SyncWebCookies getInstance(){
        return SingleHolder.instance;
    }

    /**
     * 获取新cookie
     * @param
     */
    private static String getNewCookie(String cookie) {
        if (cookie != null && cookie.contains(";")) {
            cookie = cookie.substring(cookie.lastIndexOf(";")+1, cookie.length());
        }
        return cookie;
    }

    public static void  synchronousWebCookies(Context mContext, String url) {
        CookieManager cookieManager = CookieManager.getInstance();
        String cookies = cookieManager.getCookie(url);
        cookies = getNewCookie(cookies);//获取最终cookie
        if (!TextUtils.isEmpty(cookies)) {
            if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP) {
                CookieSyncManager.createInstance(mContext);
            }
            cookieManager = CookieManager.getInstance();
            cookieManager.setAcceptCookie(true);
            cookieManager.removeSessionCookie();// 移除
            cookieManager.removeAllCookie();
            StringBuilder sbCookie = new StringBuilder();
            sbCookie.append(cookies);
            String cookieValue = sbCookie.toString();
            //为url设置cookie
            cookieManager.setCookie(url, cookieValue);
            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
                //同步cookie
                CookieManager.getInstance().flush();
            }else{
                //同步cookie
                CookieSyncManager.getInstance().sync();
            }
        }
    }
}

代码的逻辑是这样的,获取原有cookie,然后取cookie最后一个“;”的cookie,再同步。原因有以下几点:
(1)当有新的cookie生成时,新的cookie会添加到原有cookie之后,cookie和cookie之间用分号隔开,也就是说,最后一个cookie才是最新的cookie;
(2)cookieManager.setCookie(url, cookieValue)当设置cookie时,如果cookieValue有分号,只能截取到第一个分号之前的数据,也就是说,如果cookieValue=“1234;5678”,那么调用该方法之后cookie是“1234”,而不是“1234;5678”;

--以上设置cookie的结论是结合以前项目总结的,当前并没有经过系统的测试。--

然而,怎么设置cookie是根据需求而定的。

如果涉及到网络,需要注册网络权限

网页加载

1.loadData(String data, String mimeType, String encoding)

    String data= "###标题###

我是段落

"; mywebview.loadData(data, "text/html", "UTF8");

输出结果如下:


Android控件<第二十三篇>:超详细的Webview攻略(一)_第1张图片
图片.png

感觉奇怪的是,命名encoding指明的是“utf-8”,但是还是乱码了,查询了一些资料才发现,这里encoding其实是不生效的,这是原生的一个bug。
解决方案是改成如下代码:

    String data= "###标题###

我是段落

"; mywebview.loadData(data, "text/html;charset=UTF-8", null);

输出结果是:


Android控件<第二十三篇>:超详细的Webview攻略(一)_第2张图片
图片.png

2.loadDataWithBaseURL(String baseUrl, String data,String mimeType, String encoding, String failUrl)

    String data= "###标题###

我是段落

"; mywebview.loadDataWithBaseURL(null, data, "text/html", "UTF-8", null);

输出效果如下:


Android控件<第二十三篇>:超详细的Webview攻略(一)_第3张图片
图片.png

该方法的encoding是生效的,这个方法是官方比较推荐的方法。官方之所以推荐使用这个方法不仅仅是因为encoding,官方文档中有“同源策略”的概念,这个也许需要结合代码才能理解:

在网上随便找了两个同源图片:

http://img.daimg.com/uploads/allimg/110825/3-110R5133545427.jpg
http://img.daimg.com/uploads/allimg/120302/3-1203021T03E04.jpg

String data = "

"; String baseUrl = "http://img.daimg.com"; mywebview.loadDataWithBaseURL(baseUrl, data, "text/html", "utf-8", null);

data里面的图片路劲是相对路劲,在加载数据时,指定baseUrl之后才可以正确在webview中加载图片,效果如下:

30.gif

3.loadUrl(String url)、loadUrl(String url, Map additionalHttpHeaders)

加载网络图片

//加载网络图片
mywebview.loadUrl("http://img.daimg.com/uploads/allimg/110825/3-110R5133545427.jpg");

加载一个网址

//加载一个网址
mywebview.loadUrl("https://www.baidu.com");


Map map = new HashMap<>();
map.put("key", "value");
mywebview.loadUrl(url, map);

加载本地一个文件(android_asset或android_res)

//该文件的格式是html,webview支持加载html格式的文件,在webview中已网页的形式展示
mywebview.loadUrl("file:///android_asset/htmldemo3.html");
//该文件的格式是ui,这个后缀是我随便命名的,webview不能识别ui格式的文件,当我将这个文件拷贝到assets文件夹中时,必须选择一种AS支持的一种类型
mywebview.loadUrl("file:///android_asset/htmldemo3.ui");
//该文件的格式是txt,webview可以识别txt格式的文件
mywebview.loadUrl("file:///android_asset/htmldemo3.txt");
//该文件的格式是mp3,webview可以识别mp3格式的文件可以查看下方截图:
mywebview.loadUrl("file:///android_asset/htmldemo3.mp3");
Android控件<第二十三篇>:超详细的Webview攻略(一)_第4张图片
图片.png

webview可以尝试加载任意资源文件,我加载了excel文件,发现不能显示内容,总之,文件的格式有很多,webview到底支持哪些格式的文件需要尝试后才知道,最后再扩展一下,文件url的协议不一定是file, 也有可能是content,支持ContentProvider的协议。

//加载某一资源库文件
mywebview.loadUrl("content://authorities/person/xxxx");

4.postUrl(String url, byte[] postData)
一般我们加载网页是这样写mywebview.loadUrl("http://www.xxx.xxx/demo?username='zhangsan'&age=12"),这是一个典型的get请求,但是如果需要post请求呢?这时就会需要postUrl(String url, byte[] postData)的支持。

具体用法如下:

public class User {

     String username;

     int age;
}


    User user = new User();
    user.username = "za";
    user.age = 12;
    //使用POST请求加载指定的网页
    try {
        mywebview.postUrl(url, concatParams(getAllFields(user)).getBytes());
    } catch (UnsupportedEncodingException e) {
        e.printStackTrace();
    } catch (NullPointerException e){
        e.printStackTrace();
    }


/**
 *
 * @param t
 * @param 
 * @return
 */
private  Map getAllFields(T t){
    Field[] field = t.getClass().getDeclaredFields();
    Map fieldsAndValues = new HashMap<>();
    try {
        for (int i = 0; i < field.length; i++) {
            String name = field[i].getName();
            if(field[i].getGenericType().toString().equals("class java.lang.String")){
                String value = (String) field[i].get(t);
                fieldsAndValues.put(name,value);
            }
        }
    } catch (SecurityException e) {
        e.printStackTrace();
    } catch (IllegalAccessException e) {
        e.printStackTrace();
    } catch (IllegalArgumentException e) {
        e.printStackTrace();
    }
    System.out.print(""+fieldsAndValues.size());
    return fieldsAndValues;
}


private String concatParams(Map params) throws UnsupportedEncodingException {
    if(params.size() ==0){
        return null;
    }
    StringBuilder builder = new StringBuilder();
    Set keys = params.keySet();
    Iterator iterator = keys.iterator();
    while (iterator.hasNext()){
        String key = iterator.next();
        String value = URLEncoder.encode(params.get(key), "UTF-8");
        builder.append(String.format("%s=%s&",key, value));
    }
    builder.deleteCharAt(builder.lastIndexOf("&"));
    return builder.toString();
}

5.重新加载当前网页reload()

mywebview.reload();
webview常用配置

webview一般需要支持JS,如果不支持,所有的JS功能将失效

    //是否支持JS,如果访问的页面中要与Javascript交互,则webview必须设置支持Javascript
    mywebview.getSettings().setJavaScriptEnabled(true);

窗口相关的配置一般用的比较少,了解就行

    //让JavaScript自动打开窗口,默认false。适用于JavaScript方法window.open()。
    mywebview.getSettings().setJavaScriptCanOpenWindowsAutomatically(false);
    //设置WebView是否支持多窗口。如果设置为true,主程序要实现onCreateWindow(WebView, boolean, boolean, Message),默认false。
    mywebview.getSettings().setSupportMultipleWindows(false);
    //当WebView切换到后台但仍然与窗口关联时是否raster tiles,打开它可以避免在WebView从后台切换到前台时重新绘制,默认值false。在这种模式下后台的WebView占用更多的内存。请按如下准则显示内存的使用:
    //WebView的尺寸不能比设备的屏幕尺寸更大;
    //限制在少数WebView上使用该模式;
    //在可见的WebView和即将显现的WebView上使用;
    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
        mywebview.getSettings().setOffscreenPreRaster(true);
    }

缩放功能

    mywebview.getSettings().setSupportZoom(true);//是否支持缩放功能(mywebview.getSettings().supportZoom()可以判断当前是否支持缩放)
    mywebview.getSettings().setBuiltInZoomControls(true);//设置内置的缩放控件。若为false,则该WebView不可缩放
    mywebview.getSettings().setDisplayZoomControls(false);//显示或隐藏原生的缩放控件
    //mywebview.getSettings().setDefaultZoom();//已废弃
    //mywebview.getSettings().setEnableSmoothTransition()//已废弃

需要注意的是:
(1)网页端可以将缩放功能屏蔽掉,如果加上以上代码依然不能缩放的话,则说明网页上已经将缩放功能屏蔽。
(2)当原生缩放控件渐变动画未结束前就销毁当前activity,会造成内存泄漏,再次打开activity时还有可能造成以下错误:

    java.lang.IllegalArgumentException: Receiver not registered: android.widget.ZoomButtonsController$1@14ff16a
    at android.app.LoadedApk.forgetReceiverDispatcher(LoadedApk.java:856)
    at android.app.ContextImpl.unregisterReceiver(ContextImpl.java:1352)
    at android.content.ContextWrapper.unregisterReceiver(ContextWrapper.java:576)
    at android.widget.ZoomButtonsController.setVisible(ZoomButtonsController.java:404)
    at android.widget.ZoomButtonsController$2.handleMessage(ZoomButtonsController.java:178)
    at android.os.Handler.dispatchMessage(Handler.java:102)
    at android.os.Looper.loop(Looper.java:150)
    at android.app.ActivityThread.main(ActivityThread.java:5546)
    at java.lang.reflect.Method.invoke(Native Method)
    at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:794)
    at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:684)

设置字体和字体大小

    //API14版本以上已废弃。请取代使用setTextZoom(int)。设置页面文本的尺寸,默认NORMAL。
    //mywebview.getSettings().setTextSize();
    //设置页面上的文本缩放百分比,默认100
    mywebview.getSettings().setTextZoom(100);
    //设置WebView字体库字体,默认“cursive”
    mywebview.getSettings().setCursiveFontFamily("cursive");
    //设置fantasy字体集(font family)的名字默认为“fantasy”
    mywebview.getSettings().setFantasyFontFamily("fantasy");
    //设置固定的字体集的名字,默认为”monospace”。
    mywebview.getSettings().setFixedFontFamily("monospace");
    //设置默认固定的字体大小,默认为16,可取值1到72
    mywebview.getSettings().setDefaultFixedFontSize(16);
    //设置默认的字体大小,默认16,可取值1到72
    mywebview.getSettings().setDefaultFontSize(16);
    //设置最小的字号,默认为8
    mywebview.getSettings().setMinimumFontSize(8);
    //设置最小的本地字号,默认为8。
    mywebview.getSettings().setMinimumLogicalFontSize(8);
    //设置标准字体集的名字,默认值“sans-serif”。
    mywebview.getSettings().setStandardFontFamily("sans-serif");
    //设置衬线字体集(serif font family)的名字,默认“sans-serif”。
    mywebview.getSettings().setSerifFontFamily("sans-serif");
    //设置无衬线字体集(sans-serif font family)的名字。默认值”sans-serif”.
    mywebview.getSettings().setSansSerifFontFamily("sans-serif");

这里设置字体和设置字体大小基本不用,默认就行,这里需要关心的是setTextZoom这个方法,默认值是100,作用有两个:
(1)可以设置webview的字体大小;
(2)当设置手机自带的字体大小时,webview的字体大小会随之变化,setTextZoom可以保证webview字体大小不随手机自带字体大小的变化而变化。

webview编码问题

    //设置默认的字符编码集,默认”UTF-8”
    mywebview.getSettings().setDefaultTextEncodingName("utf-8");//设置编码格式

webview通过JS进行文件访问

    //是否允许在WebView中访问内容URL(Content Url),默认允许。内容Url访问允许WebView从安装在系统中的内容提供者载入内容。
    mywebview.getSettings().setAllowContentAccess(false);
    //设置是否允许 WebView 使用 File 协议,默认设置为true,即允许在 File 域下执行任意 JavaScript 代码
    //使用 file 域加载的 js代码能够使用进行同源策略跨域访问,从而导致隐私信息泄露
    //1.源策略跨域访问:对私有目录文件进行访问
    //2.针对 IM 类产品,泄露的是聊天信息、联系人等等
    //2.针对浏览器类软件,泄露的是cookie 信息泄露
    mywebview.getSettings().setAllowFileAccess(false);
    //设置是否允许通过 file url 加载的 Js代码读取其他的本地文件
    //在Android 4.1前默认允许,在Android 4.1后默认禁止
    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) {
        mywebview.getSettings().setAllowFileAccessFromFileURLs(false);
        // 设置是否允许通过 file url 加载的 Javascript 可以访问其他的源(包括http、https等源)
        //在Android 4.1前默认允许、在Android 4.1后默认禁止
        mywebview.getSettings().setAllowUniversalAccessFromFileURLs(false);
    }

由于安全性的问题,如果以上方法设置成true,攻击者可以通过JS操作手机本地文件,包括app内核app外,这是一个非常严重的安全问题,在以后的配置中,直接把以上设置成false即可。

webview缓存

    //应用缓存API是否可用,默认值false, 结合setAppCachePath(String)使用。
    mywebview.getSettings().setAppCacheEnabled(true);
    String storePath = Environment.getExternalStorageDirectory().getPath() + "/webcache/";
    //storePath路径是外部路径,非APP路劲,所以需要配置文件存储和读取文件的权限
    File file = new File(storePath);
    file.mkdirs();
    //设置应用缓存文件的路径。为了让应用缓存API可用,此方法必须传入一个应用可写的路径。该方法只会执行一次,重复调用会被忽略。
    mywebview.getSettings().setAppCachePath(storePath);
    //mywebview.getSettings().setAppCacheMaxSize(1000);//设置缓存最大值,现在已被弃用,缓存的管理变为自动管理
    //重写使用缓存的方式,默认值LOAD_DEFAULT。缓存的使用方式基于导航类型,正常的页面加载,检测缓存,需要时缓存内容复现。导航返回时,内容不会复现,只有内容会从缓存盘中恢复。该方法允许客户端通过指定LOAD_DEFAULT, LOAD_CACHE_ELSE_NETWORK, LOAD_NO_CACHE or LOAD_CACHE_ONLY的其中一项来重写其行为。
    //LOAD_CACHE_ONLY:  不使用网络,只读取本地缓存数据
    //LOAD_DEFAULT:  根据cache-control决定是否从网络上取数据。
    //LOAD_CACHE_NORMAL: API level 17中已经废弃, 从API level 11开始作用同LOAD_DEFAULT模式
    //LOAD_NO_CACHE: 不使用缓存,只从网络获取数据.
    //LOAD_CACHE_ELSE_NETWORK,只要本地有,无论是否过期,或者no-cache,都使用缓存中的数据
    if(isNetworkConnected(MainActivity.this)){
        mywebview.getSettings().setCacheMode(WebSettings.LOAD_DEFAULT);
    }else{
        mywebview.getSettings().setCacheMode(WebSettings.LOAD_CACHE_ELSE_NETWORK);
    }
    //DOM存储API是否可用,默认false。
    mywebview.getSettings().setDomStorageEnabled(true);
    //数据库存储API是否可用,默认值false。如何正确设置数据存储API参见setDatabasePath(String)。该设置对同一进程中的所有WebView实例均有效。注意,只能在当前进程的任意WebView加载页面之前修改此项,因为此节点之后WebView的实现类可能会忽略该项设置的改变。
    mywebview.getSettings().setDatabaseEnabled(true);
    //mywebview.getSettings().setDatabasePath();//已废弃
    //已废弃,WebView是否保存表单数据,默认值true。
    //mywebview.getSettings().setSaveFormData(true);
    //API18以上版本已废弃。未来版本将不支持保存WebView中的密码。设置WebView是否保存密码,默认true。
    //mywebview.getSettings().setSavePassword(true);

这里着重理解一下文本view的缓存模式,代码中给出的策略是,如果网络可用模式为LOAD_DEFAULT,否则模式为LOAD_CACHE_ELSE_NETWORK。

图片和数据的加载

    //是否禁止从网络(通过http和https URI schemes访问的资源)下载图片资源,默认值为false。注意,除非getLoadsImagesAutomatically()返回true,否则该方法无效。还请注意,即使此项设置为false,使用setBlockNetworkLoads(boolean)禁止所有网络加载也会阻止网络图片的加载。当此项设置的值从true变为false,WebView当前显示的内容所引用的网络图片资源会自动获取。
    mywebview.getSettings().setBlockNetworkImage(false);
    //WebView是否下载图片资源,默认为true。注意,该方法控制所有图片的下载,包括使用URI嵌入的图片(使用setBlockNetworkImage(boolean) 只控制使用网络URI的图片的下载)。如果该设置项的值由false变为true,WebView展示的内容所引用的所有的图片资源将自动下载。
    mywebview.getSettings().setLoadsImagesAutomatically(true);
    //是否禁止从网络下载数据,如果app有INTERNET权限,默认值为false,否则默认为true。使用setBlockNetworkImage(boolean) 只会禁止图片资源的加载。注意此值由true变为false,当前WebView展示的内容所引用的网络资源不会自动加载,直到调用了重载。如果APP没有INTERNET权限,设置此值为false会抛出SecurityException。
    mywebview.getSettings().setBlockNetworkLoads(false);

setBlockNetworkImage:允许或禁止网络图片加载(注意:重点是“网络”)
setLoadsImagesAutomatically:允许或禁止网络和本地图片的加载(注意:重点是“网络”和“本地”)
setBlockNetworkLoads:允许或禁止网络下载数据

webview定位

    //定位是否可用,默认为true。请注意,为了确保定位API在WebView的页面中可用,必须遵守如下约定:
    //(1) app必须有定位的权限,参见ACCESS_COARSE_LOCATION, ACCESS_FINE_LOCATION;
    //(2) app必须提供onGeolocationPermissionsShowPrompt(String, GeolocationPermissions.Callback)回调方法的实现,在页面通过JavaScript定位API请求定位时接收通知。
    //作为可选项,可以在数据库中存储历史位置和Web初始权限,参见setGeolocationDatabasePath(String).
    mywebview.getSettings().setGeolocationEnabled(true);
    //mywebview.getSettings().setGeolocationDatabasePath();//已废弃

设置UserAgent

    //设置WebView的用户代理字符串。如果字符串为null或者empty,将使用系统默认值。注意从KITKAT版本开始,加载网页时改变用户代理会让WebView再次初始化加载。
    mywebview.getSettings().setUserAgentString(mywebview.getSettings().getUserAgentString());

如果是加载第三方网站,请不要随意更改别人的UserAgent,避免加载错误,因为你不清楚人家的加载逻辑中有没有涉及到对UserAgent的判断,UserAgent的一般用法是为了区别是哪一个客户端(比如:Android端可以设置为:android,IOS端可以设置为:ios,PC端可以设置为:pc)

允许使用轻触摸做出选择和光标悬停(已废弃)

    //已废弃。从 JELLY_BEAN 开始,该设置无效。允许使用轻触摸做出选择和光标悬停。
    //mywebview.getSettings().setLightTouchEnabled(false);

禁用菜单项模式行为包括menuItems标签(复制、粘贴、选择)

    //禁用菜单项模式行为包括menuItems标签
    try {
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
            mywebview.getSettings().setDisabledActionModeMenuItems(WebSettings.MENU_ITEM_NONE);
            //mywebview.getSettings().setDisabledActionModeMenuItems(WebSettings.MENU_ITEM_PROCESS_TEXT);
            //mywebview.getSettings().setDisabledActionModeMenuItems(WebSettings.MENU_ITEM_SHARE);
            //mywebview.getSettings().setDisabledActionModeMenuItems(WebSettings.MENU_ITEM_WEB_SEARCH);
        }
    }catch (NoSuchMethodError noSuchMethodError){

    }

这个就不写注释了,因为我在运行的时候崩溃了,报了NoSuchMethodError错误,总之如果以后有一个需求设计到这个再回来看吧。

webview适配

    //设置布局,会引起WebView的重新布局(relayout),默认值NARROW_COLUMNS
    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
        mywebview.getSettings().setLayoutAlgorithm(WebSettings.LayoutAlgorithm.TEXT_AUTOSIZING);
    }else{
        mywebview.getSettings().setLayoutAlgorithm(WebSettings.LayoutAlgorithm.NORMAL);
    }
    //是否允许WebView度超出以概览的方式载入页面,默认false。即缩小内容以适应屏幕宽度。该项设置在内容宽度超出WebView控件的宽度时生效,例如当getUseWideViewPort() 返回true时。
    mywebview.getSettings().setLoadWithOverviewMode(true);
    //WebView是否支持HTML的“viewport”标签或者使用wide viewport。设置值为true时,布局的宽度总是与WebView控件上的设备无关像素(device-dependent pixels)宽度一致。当值为true且页面包含viewport标记,将使用标签指定的宽度。如果页面不包含标签或者标签没有提供宽度,那就使用wide viewport。
    mywebview.getSettings().setUseWideViewPort(true);

setLayoutAlgorithm目前只剩下两种常量了:TEXT_AUTOSIZING和NORMAL,其他的都已经弃用了,目前不太清楚它们的界面效果。
但是将setLoadWithOverviewMode和setUseWideViewPort设置成true完全可以看出效果,没有适配的网页真的适配了。当然,网页本身也可以做到自适应。

播放视频相关

    //WebView是否需要用户的手势进行媒体播放,默认值为true。
    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) {
        mywebview.getSettings().setMediaPlaybackRequiresUserGesture(false);
    }

默认情况下setMediaPlaybackRequiresUserGesture的值是true,需要手动点击视频才能播放,如果改成false,那么在网页滑动的时候就可以实现自动播放。

Android5.0上WebView中Http和Https混合问题

    /**
     * Android5.0上 WebView中Http和Https混合问题
     * MIXED_CONTENT_ALWAYS_ALLOW:允许从任何来源加载内容,即使起源是不安全的;
     * MIXED_CONTENT_NEVER_ALLOW:不允许Https加载Http的内容,即不允许从安全的起源去加载一个不安全的资源;(默认)
     * MIXED_CONTENT_COMPATIBILITY_MODE:当涉及到混合式内容时,WebView 会尝试去兼容最新Web浏览器的风格。
     **/
    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
        mywebview.getSettings().setMixedContentMode(WebSettings.MIXED_CONTENT_ALWAYS_ALLOW);
    }

在Android5.0上,为了提高安全性,https的页面不能直接访问http,导致一些功能不可用,以上代码可以解决这个问题。

请求焦点时是否需要设置节点获取焦点

    //调用requestFocus(int, android.graphics.Rect)时是否需要设置节点获取焦点,默认值为true。
    mywebview.getSettings().setNeedInitialFocus(true);

这个默认就是ture,不用管了。

插件相关

    //mywebview.getSettings().setPluginsEnabled();//现在这个已经不再支持,被setPluginState替代
    //在API18以上已废弃。未来将不支持插件,不要使用。告诉WebView启用、禁用或者有即用(on demand)的插件,即用模式是指如果存在一个可以处理嵌入内容的插件,会显示一个占位图标,点击时开启。默认值OFF。
    //mywebview.getSettings().setPluginState(WebSettings.PluginState.ON);

以后的webview版本中都不会使用到插件,不用管,知道就行了。

调整线程优先级

    //在API18以上已废弃。不建议调整线程优先级,未来版本不会支持这样做。设置绘制线程的优先级。不像其他设置,同一进程中只需调用一次,默认值NORMAL
    //mywebview.getSettings().setRenderPriority(WebSettings.RenderPriority priority.);

官方都不建议调整优先级了,不用管。

webview安全浏览

    //设置是否启用安全浏览。安全浏览功能允许WebView通过验证链接来防范恶意软件和网络钓鱼攻击。使用清单标签可以禁用所有WebView的安全浏览功能。清单标记的优先级低于此API。
    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
        mywebview.getSettings().setSafeBrowsingEnabled(true);
    }

在Android8.0之后新增的特性,可以用代码控制安全浏览,其标签就是

    
Android控件<第二十三篇>:超详细的Webview攻略(一)_第5张图片
图片.png

由于文章太长,官方不让发布,所以文章就一分为二了

超详细的Webview攻略(二)

你可能感兴趣的:(Android控件<第二十三篇>:超详细的Webview攻略(一))