超詳細的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是根據需求而定的。

如果涉及到網絡,需要註冊網絡權限
<uses-permission android:name="android.permission.INTERNET"/>
網頁加載

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

    String data= "<html>###標題###<body><p>我是段落</p></body></html>";
    mywebview.loadData(data, "text/html", "UTF8");

輸出結果如下:


感覺奇怪的是,命名encoding指明的是“utf-8”,但是還是亂碼了,查詢了一些資料才發現,這裏encoding其實是不生效的,這是原生的一個bug。
解決方案是改成如下代碼:

    String data= "<html>###標題###<body><p>我是段落</p></body></html>";
    mywebview.loadData(data, "text/html;charset=UTF-8", null);

輸出結果是:


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

    String data= "<html>###標題###<body><p>我是段落</p></body></html>";
    mywebview.loadDataWithBaseURL(null, data, "text/html", "UTF-8", null);

輸出效果如下:


該方法的encoding是生效的,這個方法是官方比較推薦的方法。官方之所以推薦使用這個方法不僅僅是因爲encoding,官方文檔中有“同源策略”的概念,這個也許需要結合代碼才能理解:

在網上隨便找了兩個同源圖片:

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

String data = "<img src='/uploads/allimg/110825/3-110R5133545427.jpg' /><p/><img src='/uploads/allimg/120302/3-1203021T03E04.jpg'/>";
String baseUrl = "http://img.daimg.com";
mywebview.loadDataWithBaseURL(baseUrl, data, "text/html", "utf-8", null);

data裏面的圖片路勁是相對路勁,在加載數據時,指定baseUrl之後纔可以正確在webview中加載圖片,效果如下:

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

加載網絡圖片

//加載網絡圖片
mywebview.loadUrl("http://img.daimg.com/uploads/allimg/110825/3-110R5133545427.jpg");

加載一個網址

//加載一個網址
mywebview.loadUrl("https://www.baidu.com");


Map<String, String> 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");

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 <T>
 * @return
 */
private <T> Map<String,String> getAllFields(T t){
    Field[] field = t.getClass().getDeclaredFields();
    Map<String,String> 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<String,String> params) throws UnsupportedEncodingException {
    if(params.size() ==0){
        return null;
    }
    StringBuilder builder = new StringBuilder();
    Set<String> keys = params.keySet();
    Iterator<String> 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之後新增的特性,可以用代碼控制安全瀏覽,其標籤就是

    <meta-data android:name="android.webkit.WebView.EnableSafeBrowsing"
        android:value="true" />

由於文章太長,官方不讓發佈,所以文章就一分爲二了

超詳細的Webview攻略(二)

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章