Andriod WebView 填坑小結(jié)

在學(xué)習(xí)WebView的時(shí)候就知道了WebView會(huì)出現(xiàn)很多稀奇古怪的問(wèn)題,真碰上的時(shí)候還是焦頭爛額,很多問(wèn)題的解決方案要在網(wǎng)上找很久很久很久。這里做了稍微全面的總結(jié)。

劃重點(diǎn):

1.內(nèi)存泄露的解決方法

2.Native獲得的cookie同步到WebView中

3.API5.0以上Ajax跨域訪問(wèn)無(wú)法攜帶cookie的問(wèn)題

4.Alert劫持問(wèn)題

1. 內(nèi)存泄露

關(guān)于內(nèi)存泄漏,想要徹底解決,最好的方法是當(dāng)你要用webview的時(shí)候,另外單獨(dú)開一個(gè)進(jìn)程(如何單開進(jìn)程請(qǐng)自行搜索) 去使用webview 并且當(dāng)這個(gè) 進(jìn)程結(jié)束時(shí),請(qǐng)手動(dòng)調(diào)用System.exit(0)。 但是這種情況又會(huì)有另外的問(wèn)題,就是進(jìn)程間的通信。

不單開進(jìn)程時(shí):

1.1 JS無(wú)法釋放,WebView在執(zhí)行JS時(shí)被關(guān)閉,這些JS資源會(huì)無(wú)法釋放,一直耗電,一直占CPU,解決方法是在onStop和onResume里分別把setJavaScriptEnabled();給設(shè)置成false和true。
@Override
protected void onResume() {
    mWebView.getSettings().setJavaScriptEnabled(true);
    super.onResume();
}
@Override
protected void onStop() {
    mWebView.getSettings().setJavaScriptEnabled(false);
    super.onStop();
}
1.2在onDestroy中對(duì)webView的銷毀做處理,下面是我覺(jué)得比較好的方式(主要是處理的比較全面)
if (mWebView != null) {
// 如果先調(diào)用destroy()方法,則會(huì)命中if (isDestroyed()) return;這一行代碼,需要先onDetachedFromWindow(),再
// destory()
ViewParent parent = mWebView.getParent();
if (parent != null) {
    ((ViewGroup) parent).removeView(mWebView);
}

mWebView.stopLoading();
// 退出時(shí)調(diào)用此方法,移除綁定的服務(wù),否則某些特定系統(tǒng)會(huì)報(bào)錯(cuò)
mWebView.getSettings().setJavaScriptEnabled(false);
mWebView.clearHistory();
mWebView.clearAnimation();
mWebView.clearView();
mWebView.removeAllViews();

try {
    mWebView.destroy();
} catch (Throwable ex) {
}

2. WebView的各種設(shè)置

2.1 首先是創(chuàng)建

最好不要在XML中直接添加WebView,而是預(yù)留一個(gè)FrameLayout,代碼中創(chuàng)建WebView,添加到FrameLayout中。
這樣能解決很多內(nèi)存泄露的問(wèn)題。

mWebViewContainer = (FrameLayout) findViewById(R.id.web_view_container);
mWebView = new WebView(WebViewActivity.this);
mWebViewContainer.addView(mWebView, new FrameLayout.LayoutParams(
                FrameLayout.LayoutParams.MATCH_PARENT,
                FrameLayout.LayoutParams.MATCH_PARENT));

2.2 然后是settings,N多種方法

WebSettings webSettings = webView.getSettings();
//設(shè)置了這個(gè)屬性后我們才能在 WebView 里與我們的 Js 代碼進(jìn)行交互,對(duì)于 WebApp 是非常重要的,默認(rèn)是 false,
//因此我們需要設(shè)置為 true,這個(gè)本身會(huì)有漏洞,具體的下面我會(huì)講到
webSettings.setJavaScriptEnabled(true);

//設(shè)置 JS 是否可以打開 WebView 新窗口
webSettings.setJavaScriptCanOpenWindowsAutomatically(true);

//WebView 是否支持多窗口,如果設(shè)置為 true,需要重寫 
//WebChromeClient#onCreateWindow(WebView, boolean, boolean, Message) 函數(shù),默認(rèn)為 false
webSettings.setSupportMultipleWindows(true);

//這個(gè)屬性用來(lái)設(shè)置 WebView 是否能夠加載圖片資源,需要注意的是,這個(gè)方法會(huì)控制所有圖片,包括那些使用 data URI 協(xié)議嵌入
//的圖片。使用 setBlockNetworkImage(boolean) 方法來(lái)控制僅僅加載使用網(wǎng)絡(luò) URI 協(xié)議的圖片。需要提到的一點(diǎn)是如果這
//個(gè)設(shè)置從 false 變?yōu)?true 之后,所有被內(nèi)容引用的正在顯示的 WebView 圖片資源都會(huì)自動(dòng)加載,該標(biāo)識(shí)默認(rèn)值為 true。
webSettings.setLoadsImagesAutomatically(false);
//標(biāo)識(shí)是否加載網(wǎng)絡(luò)上的圖片(使用 http 或者 https 域名的資源),需要注意的是如果 getLoadsImagesAutomatically() 
//不返回 true,這個(gè)標(biāo)識(shí)將沒(méi)有作用。這個(gè)標(biāo)識(shí)和上面的標(biāo)識(shí)會(huì)互相影響。
webSettings.setBlockNetworkImage(true);

//顯示W(wǎng)ebView提供的縮放控件
webSettings.setDisplayZoomControls(true);
webSettings.setBuiltInZoomControls(true);

//推薦使用。打開 WebView 的 storage 功能,這樣 JS 的 localStorage,sessionStorage 對(duì)象才可以使用
webSettings.setDomStorageEnabled(true);
//推薦打開。設(shè)置是否啟動(dòng) WebView 的DB API,默認(rèn)值為 false
webSettings.setDatabaseEnabled(true);
webSettings.setDatabasePath(Utils.getContext().getDir("WebDb",MODE_PRIVATE).getPath());

//打開 WebView 自帶的的 LBS 功能,這樣 JS 的 geolocation 對(duì)象才可以使用,注意manifest中的權(quán)限
webSettings.setGeolocationEnabled(true);
webSettings.setGeolocationDatabasePath("");

//設(shè)置是否打開 WebView 表單數(shù)據(jù)的保存功能
webSettings.setSaveFormData(true);

//設(shè)置 WebView 的默認(rèn) userAgent 字符串
webSettings.setUserAgentString("");

// 不推薦使用。設(shè)置緩存,是否開啟,緩存模式,緩存大小,緩存路徑
webSettings.setAppCacheEnabled(true);
webSettings.setCacheMode(WebSettings.LOAD_DEFAULT);
webSettings.setAppCacheMaxSize(10 * 1024 * 1024); 
webSettings.setAppCachePath(Utils.getContext().getExternalCacheDir().getAbsolutePath());

// 這兩個(gè)一起設(shè)置,表明WebView會(huì)自適應(yīng)手機(jī)屏幕的寬度
webSettings.setUseWideViewPort(true);
webSettings.setLoadWithOverviewMode(true);

//設(shè)置 WebView 的字體,可以通過(guò)這個(gè)函數(shù),改變 WebView 的字體,默認(rèn)字體為 "sans-serif"
webSettings.setStandardFontFamily("");
//設(shè)置 WebView 字體的大小,默認(rèn)大小為 16
webSettings.setDefaultFontSize(20);
//設(shè)置 WebView 支持的最小字體大小,默認(rèn)為 8
webSettings.setMinimumFontSize(12);

//設(shè)置文本的縮放倍數(shù),默認(rèn)為 100
webSettings.setTextZoom(2);

// 允許高版本http和https混合訪問(wèn)
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
    settings.setMixedContentMode(WebSettings.MIXED_CONTENT_ALWAYS_ALLOW);
}

// 支持PC上的Chrome調(diào)試WebView,具體方法請(qǐng)百度
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
    // 只支持Debug模式下調(diào)試
    if (0 != (getApplicationInfo().flags &= ApplicationInfo.FLAG_DEBUGGABLE)) {
        WebView.setWebContentsDebuggingEnabled(true);
    }
}

2.3 然后是WebViewClient和ChromeClient的設(shè)置

  • WebViewClient主要幫助WebView處理各種通知、請(qǐng)求事件

  • WebChromeClient主要輔助WebView處理Javascript的對(duì)話框、網(wǎng)站圖標(biāo)、網(wǎng)站title、加載進(jìn)度等

2.3.1 WebViewClient的常用設(shè)置

    1. shouldOverrideUrlLoading方法:在點(diǎn)擊請(qǐng)求的是鏈接時(shí)會(huì)調(diào)用此方法。返回false表明交給系統(tǒng)處理,我們自己不干預(yù),正常情況下這么做;返回true,表明在這里攔截了WebView加載的事件,具體如何處理自己看。

      一般情況下

    @RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)
    @Override
    public boolean shouldOverrideUrlLoading(WebView view, WebResourceRequest request) {
        view.loadUrl(request.getUrl().toString());
        return true;
    }
    
    @Override
    public boolean shouldOverrideUrlLoading(WebView view, String url) {
        view.loadUrl(url);
        return true;
    }
    
  • 2.onReceivedSslError:處理https請(qǐng)求。2.1以上版本,前提setJavaScriptEnabled(true);

    @Override
    public void onReceivedSslError(WebView view, SslErrorHandler handler, SslError error) {
        //handler.cancel(); 默認(rèn)的處理方式,WebView變成空白頁(yè)
        handler.proceed();//接受證書
        //handleMessage(Message msg); 其他處理
    }
    
  • 3.onLoadResource:加載資源時(shí)調(diào)用,每個(gè)資源(比如圖片,js,CSS等)都會(huì)調(diào)用一次。

    @Override
    public void onLoadResource(WebView view, String url) 
    
  • 4.onPageStarted和onPageStarted:頁(yè)面加載開始和結(jié)束后會(huì)調(diào)用。

    @Override
    public void onPageStarted(WebView view, String url, Bitmap favicon)
      
    @Override
    public void onPageFinished(WebView view, String url) 
    
  • 5.shouldInterceptRequest:同樣是加載資源時(shí)調(diào)用,但是這里WebView可以加載本地的資源提供給內(nèi)核,若本地處理返回?cái)?shù)據(jù),內(nèi)核不從網(wǎng)絡(luò)上獲取數(shù)據(jù)。從API 11時(shí)引入, API21更新重載方法。加載本地資源使用方法請(qǐng)看 這篇博客常用資源預(yù)加載

    //API 22添加的重載方法
    @Override
    public WebResourceResponse shouldInterceptRequest(WebView view, WebResourceRequest request) 
      
    @Override
    public WebResourceResponse shouldInterceptRequest(WebView view, String url) 
    

因?yàn)樯厦娴牟┛捅粍h除了,在這里寫一下如何加載本地資源,包括Js文件,圖片文件等等

這里舉例:需要加載本地的Js文件jsTest.js和icon.png。注意,這里需要把文件放在src/main/assests下

@RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)
@Override
public WebResourceResponse shouldInterceptRequest(WebView view, WebResourceRequest request) {
    String url = request.getUrl().toString();

    if (!TextUtils.isEmpty(url) && url.contains("jsTest.js")) {
        return editResponse("jsTest.js");
    } else if (!TextUtils.isEmpty(url) && url.contains("icon.png")) {
        return editResponse("icon.png");
    }

    return super.shouldInterceptRequest(view, request);
}

@Override
public WebResourceResponse shouldInterceptRequest(WebView view, String url) {
      // 因?yàn)榧虞d每個(gè)圖片,Js資源都要請(qǐng)求一次網(wǎng)絡(luò)。所以這種判斷這種資源是否在本地,如果在本地,就直接返回本地的資源,不再去網(wǎng)上取
    if (url.contains("jsTest.js")) {
        return editResponse("jsTest.js");
    } else if (url.contains("icon.png")) {
        return editResponse("icon.png");
    }
    return super.shouldInterceptRequest(view, url);
}

/**
* 自定義的得到src/main/assests下資源文件的方法
*/
private WebResourceResponse editResponse(String fileName) {
    try {
        return new WebResourceResponse("application/x-javascript", "utf-8", getAssets().open(fileName));
    } catch (IOException e) {
        e.printStackTrace();
    }
    //需處理特殊情況
    return null;
}

2.3.2 ChromeClient的常用設(shè)置

  • 1.onJsAlert/onJsConfirm/onJsPrompt方法,對(duì)應(yīng)JS的對(duì)話框,確認(rèn)框,輸入框。JS彈出對(duì)話框等時(shí),會(huì)調(diào)用對(duì)應(yīng)的方法,我們?cè)诜椒ㄖ袕棾鯝lertDialog等,顯示相應(yīng)信息即可

  • 2.onProgressChanged:通知應(yīng)用程序當(dāng)前網(wǎng)頁(yè)加載的進(jìn)度

    @Override
    public void onProgressChanged(WebView view, int newProgress)
    
  • 3.onReceivedTitle:獲取網(wǎng)頁(yè)title標(biāo)題。

    獲取標(biāo)題的時(shí)間主要取決于網(wǎng)頁(yè)前段設(shè)置標(biāo)題的位置,一般設(shè)置在頁(yè)面加載前面,可以較早調(diào)用到這個(gè)函數(shù)

    @Override
    public void onReceivedTitle(WebView view, String title)
    
  • 4.H5播放器全屏和去全屏方法

    //有H5視頻,按下全屏播放時(shí)調(diào)用的方法
    @Override
    public void onShowCustomView(View view, CustomViewCallback callback)
    // 對(duì)應(yīng)的取消全屏方法
    @Override
    public void onHideCustomView()
    
  • 5.設(shè)置WebView視頻未播放時(shí)默認(rèn)顯示占位圖。關(guān)于WebView中的視頻播放的相關(guān)知識(shí),請(qǐng)點(diǎn)這里

    @Override
    public Bitmap getDefaultVideoPoster()
    

3. Native與JS的相互調(diào)用

這個(gè)網(wǎng)上有很多資源了,選擇一個(gè)我覺(jué)得比較全面的博客:Android:你要的WebView與 JS 交互方式 都在這里了。

這里只說(shuō)重點(diǎn):

Js調(diào)用Android現(xiàn)在一般是用WebView的addJavascriptInterface()方式,當(dāng)然因?yàn)锳ndroid版本造成的漏洞不能忽視

Android調(diào)用Js,毫無(wú)疑問(wèn),evaluateJavascript()和loadUrl結(jié)合使用,根據(jù)版本判斷

常見(jiàn)錯(cuò)誤:

  • 1.線程錯(cuò)誤。Js調(diào)Android時(shí)發(fā)生,Android調(diào)時(shí)也經(jīng)常發(fā)生,因?yàn)檎{(diào)了Js,很多情況還是會(huì)回調(diào)Android。報(bào)錯(cuò)信息大致為Js線程必須一致等。很好解決,Android中在UI線程即可。但是推薦WebView.post()的方式,不推薦runOnUiThread()方式

    mWebView.post(new Runnable() {
        @Override
        public void run() {
            // TODO
        }
    });
    

4. Cookie問(wèn)題

4.1 WebView是有自己的Cookie系統(tǒng)的

WebView會(huì)在本地維護(hù)每次會(huì)話的cookie(保存在data/data/package_name/app_WebView/Cookies.db)。當(dāng)WebView加載URL的時(shí)候,WebView會(huì)從本地讀取該URL對(duì)應(yīng)的cookie,并攜帶該cookie與服務(wù)器進(jìn)行通信。
WebView通過(guò)android.webkit.CookieManager類來(lái)維護(hù)cookie。CookieManager是WebView的cookie管理類。

4.2 Native獲得Cookie,設(shè)置給WebView如何做。

很多時(shí)候我們需要將在native中的 登陸的狀態(tài) 同步到H5中避免再次登陸,這就需要用到對(duì)Cookie的管理。

問(wèn)題分解:
1.在OkHttp(假定使用的是OkHttp,其他的方式獲得cookie更簡(jiǎn)單)中獲得cookie,并儲(chǔ)存;
2.取得cookie并設(shè)置給WebView。

解決:

4.2.1 Okhttp中獲得cookie。

獲得cookie很簡(jiǎn)單,只需在OkHttpClient的構(gòu)建過(guò)程中加一行代碼。

List<Cookie> mCookies;
mOkHttpClient = new OkHttpClient.Builder()
        ...
        // 下面就是對(duì)OkHttp的cookie的處理
        .cookieJar(new CookieJar() {
                    // 對(duì)服務(wù)器返回的cookie的處理的方法
                    // 參數(shù)url和cookie就是cookie對(duì)應(yīng)的url和cookie的值
                    @Override
                    public void saveFromResponse(HttpUrl url, List<Cookie> cookies) {
                        // 對(duì)cookie的處理,一般是存到內(nèi)存中,使其他地方可以獲得
                        mCookies = cookies;
                    }
                    // 發(fā)送請(qǐng)求時(shí)的cookie的處理,返回的List<Cookie>即請(qǐng)求的cookie
                    @Override
                    public List<Cookie> loadForRequest(HttpUrl url) {
                        return mCookies;
                        //return null;
                    }
                })
        .build();

當(dāng)然,也可以新建一個(gè)類,實(shí)現(xiàn)CookieJar,專門處理cookie問(wèn)題。這里只是給出了最簡(jiǎn)單的對(duì)cookie的處理,對(duì)于持久化等,就是另外的問(wèn)題了。

4.2.2 將cookie同步到WebView中

private void syncCookies(String url, List<Cookie> cookies) {
    // 一些前提設(shè)置
    CookieSyncManager.createInstance(this);
    final CookieManager cookieManager = CookieManager.getInstance();
    cookieManager.setAcceptCookie(true);
    /**
     * 設(shè)置webView支持JS的Cookie的調(diào)用,5.0以上才要設(shè)置
     */
    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
        cookieManager.setAcceptThirdPartyCookies(mWebView, true);
    }
    //cookieManager.removeAllCookie();
    // 向WebView中添加Cookie,
    for (Cookie cookie : cookies) {
        cookieManager.setCookie(url, cookie.toString());
    }
    // 刷新,同步
    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP_MR1) {
        CookieManager.getInstance().flush();
    } 
    CookieSyncManager.getInstance().sync();
    //String newCookie = cookieManager.getCookie(url);驗(yàn)證是否將cookie同步進(jìn)去
    //KLog.i("newCookie: " + newCookie);
}

這里有幾點(diǎn)要注意:

1.順序。

// 1.先初始化WebView,各種設(shè)置setting,webViewClient和chromeClient等
initWebView();
// 2.再獲得,并同步cookie
// MyCookieJar.getInstance().getCookies()可以換成ApiManager.getInstance().getCookies()等
syncCookies(url, MyCookieJar.getInstance().getCookies());
// 3.最后加載url
mWebView.loadUrl(url);

2.cookie的添加時(shí),最好是一個(gè)cookie,set一次,最好不要自己拼接,否則關(guān)于domain,path,逗號(hào),分號(hào)等等的問(wèn)題會(huì)讓人欲仙欲死。還有說(shuō)法是用String不行,要用StringBuilder。所以,盡量不自己拼。

4.3 Ajax跨域訪問(wèn)時(shí),Cookie帶不過(guò)去的解決方法

問(wèn)題:Native已經(jīng)登錄,cookie可以設(shè)置進(jìn)去,但是網(wǎng)頁(yè)進(jìn)行了復(fù)雜的ajax操作(我也不知道什么操作),cookie帶不過(guò)去,到指定頁(yè)面還得登錄。5.0以下正常

解決:經(jīng)過(guò)排查,發(fā)現(xiàn)高版本時(shí)問(wèn)題出在ajax跳轉(zhuǎn)時(shí),是Js對(duì)Cookie的操作,不經(jīng)過(guò)WebView,正常的WebView設(shè)置沒(méi)有作用??戳撕芏嗖┛?,還是在StackOverFlow上發(fā)現(xiàn)解決方法。一行代碼

final CookieManager cookieManager = CookieManager.getInstance();
cookieManager.setAcceptCookie(true);
/**
 * 設(shè)置webView支持JS的Cookie的調(diào)用,5.0以上才要設(shè)置
 */
// 大致意思:mWebView接收第三方對(duì)Cookie的操作,也就是支持Js對(duì)cookie的操作
cookieManager.setAcceptThirdPartyCookies(mWebView, true);

5. 棘手問(wèn)題

5.1 Alert劫持

Alert劫持:Alert只會(huì)彈出一次,并且WebView會(huì)卡死。重新加載都不行,必須殺死進(jìn)程,重新打開App

解決方法很簡(jiǎn)單,在自定義的onJsAlert方法中加一行代碼

@Override
public boolean onJsAlert(WebView view, String url, String message, JsResult result) {
    ...

    result.confirm();// 不加這行代碼,會(huì)造成Alert劫持:Alert只會(huì)彈出一次,并且WebView會(huì)卡死
  
    return true;
}

6. 其他

6.1 帶進(jìn)度條的WebView(TODO)

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • 這篇博客主要來(lái)介紹 WebView 的相關(guān)使用方法,常見(jiàn)的幾個(gè)漏洞,開發(fā)中可能遇到的坑和最后解決相應(yīng)漏洞的源碼,以...
    Shawn_Dut閱讀 7,563評(píng)論 3 55
  • 一、WebView 谷歌提供的系統(tǒng)組件,用來(lái)加載和展現(xiàn)html網(wǎng)頁(yè),其采用webkit內(nèi)核驅(qū)動(dòng),來(lái)實(shí)現(xiàn)網(wǎng)頁(yè)瀏覽功能...
    閑庭閱讀 7,216評(píng)論 2 12
  • 前言 關(guān)于UIWebView的介紹,相信看過(guò)上文的小伙伴們,已經(jīng)大概清楚了吧,如果有問(wèn)題,歡迎提問(wèn)。 本文是本系列...
    CoderLF閱讀 9,366評(píng)論 2 12
  • 0. 前言 前面有被用戶投訴 APP 流量消耗厲害: 于是乎考慮了流量方面的問(wèn)題。暫時(shí) APP 中涉及流量的幾個(gè)方...
    zyl06閱讀 24,476評(píng)論 5 63
  • 我好想坐著飛船去找你在1970年的夜孤獨(dú)或許是解藥 你好像坐著飛船去找我在2020年的春天孤獨(dú)或許是毒藥 你說(shuō),月...
    北城十堯閱讀 599評(píng)論 7 5

友情鏈接更多精彩內(nèi)容