在學(xué)習(xí)WebView的時候就知道了WebView會出現(xiàn)很多稀奇古怪的問題夹攒,真碰上的時候還是焦頭爛額太闺,很多問題的解決方案要在網(wǎng)上找很久很久很久宠页。這里做了稍微全面的總結(jié)葵孤。
劃重點:
1.內(nèi)存泄露的解決方法
2.Native獲得的cookie同步到WebView中
3.API5.0以上Ajax跨域訪問無法攜帶cookie的問題
4.Alert劫持問題
1. 內(nèi)存泄露
關(guān)于內(nèi)存泄漏鼻由,想要徹底解決暇榴,最好的方法是當你要用webview的時候,另外單獨開一個進程(如何單開進程請自行搜索) 去使用webview 并且當這個 進程結(jié)束時嗡靡,請手動調(diào)用System.exit(0)跺撼。 但是這種情況又會有另外的問題窟感,就是進程間的通信讨彼。
不單開進程時:
1.1 JS無法釋放,WebView在執(zhí)行JS時被關(guān)閉柿祈,這些JS資源會無法釋放哈误,一直耗電哩至,一直占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中對webView的銷毀做處理菩貌,下面是我覺得比較好的方式(主要是處理的比較全面)
if (mWebView != null) {
// 如果先調(diào)用destroy()方法,則會命中if (isDestroyed()) return;這一行代碼重荠,需要先onDetachedFromWindow()箭阶,再
// destory()
ViewParent parent = mWebView.getParent();
if (parent != null) {
((ViewGroup) parent).removeView(mWebView);
}
mWebView.stopLoading();
// 退出時調(diào)用此方法,移除綁定的服務(wù)戈鲁,否則某些特定系統(tǒng)會報錯
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ù)留一個FrameLayout,代碼中創(chuàng)建WebView婆殿,添加到FrameLayout中诈乒。
這樣能解決很多內(nèi)存泄露的問題。
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è)置了這個屬性后我們才能在 WebView 里與我們的 Js 代碼進行交互怕磨,對于 WebApp 是非常重要的,默認是 false消约,
//因此我們需要設(shè)置為 true肠鲫,這個本身會有漏洞,具體的下面我會講到
webSettings.setJavaScriptEnabled(true);
//設(shè)置 JS 是否可以打開 WebView 新窗口
webSettings.setJavaScriptCanOpenWindowsAutomatically(true);
//WebView 是否支持多窗口或粮,如果設(shè)置為 true滩届,需要重寫
//WebChromeClient#onCreateWindow(WebView, boolean, boolean, Message) 函數(shù),默認為 false
webSettings.setSupportMultipleWindows(true);
//這個屬性用來設(shè)置 WebView 是否能夠加載圖片資源被啼,需要注意的是帜消,這個方法會控制所有圖片,包括那些使用 data URI 協(xié)議嵌入
//的圖片浓体。使用 setBlockNetworkImage(boolean) 方法來控制僅僅加載使用網(wǎng)絡(luò) URI 協(xié)議的圖片泡挺。需要提到的一點是如果這
//個設(shè)置從 false 變?yōu)?true 之后,所有被內(nèi)容引用的正在顯示的 WebView 圖片資源都會自動加載命浴,該標識默認值為 true娄猫。
webSettings.setLoadsImagesAutomatically(false);
//標識是否加載網(wǎng)絡(luò)上的圖片(使用 http 或者 https 域名的資源),需要注意的是如果 getLoadsImagesAutomatically()
//不返回 true生闲,這個標識將沒有作用媳溺。這個標識和上面的標識會互相影響。
webSettings.setBlockNetworkImage(true);
//顯示W(wǎng)ebView提供的縮放控件
webSettings.setDisplayZoomControls(true);
webSettings.setBuiltInZoomControls(true);
//推薦使用碍讯。打開 WebView 的 storage 功能悬蔽,這樣 JS 的 localStorage,sessionStorage 對象才可以使用
webSettings.setDomStorageEnabled(true);
//推薦打開。設(shè)置是否啟動 WebView 的DB API捉兴,默認值為 false
webSettings.setDatabaseEnabled(true);
webSettings.setDatabasePath(Utils.getContext().getDir("WebDb",MODE_PRIVATE).getPath());
//打開 WebView 自帶的的 LBS 功能蝎困,這樣 JS 的 geolocation 對象才可以使用录语,注意manifest中的權(quán)限
webSettings.setGeolocationEnabled(true);
webSettings.setGeolocationDatabasePath("");
//設(shè)置是否打開 WebView 表單數(shù)據(jù)的保存功能
webSettings.setSaveFormData(true);
//設(shè)置 WebView 的默認 userAgent 字符串
webSettings.setUserAgentString("");
// 不推薦使用。設(shè)置緩存禾乘,是否開啟澎埠,緩存模式,緩存大小始藕,緩存路徑
webSettings.setAppCacheEnabled(true);
webSettings.setCacheMode(WebSettings.LOAD_DEFAULT);
webSettings.setAppCacheMaxSize(10 * 1024 * 1024);
webSettings.setAppCachePath(Utils.getContext().getExternalCacheDir().getAbsolutePath());
// 這兩個一起設(shè)置蒲稳,表明WebView會自適應(yīng)手機屏幕的寬度
webSettings.setUseWideViewPort(true);
webSettings.setLoadWithOverviewMode(true);
//設(shè)置 WebView 的字體,可以通過這個函數(shù)伍派,改變 WebView 的字體弟塞,默認字體為 "sans-serif"
webSettings.setStandardFontFamily("");
//設(shè)置 WebView 字體的大小,默認大小為 16
webSettings.setDefaultFontSize(20);
//設(shè)置 WebView 支持的最小字體大小拙已,默認為 8
webSettings.setMinimumFontSize(12);
//設(shè)置文本的縮放倍數(shù)决记,默認為 100
webSettings.setTextZoom(2);
// 允許高版本http和https混合訪問
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
settings.setMixedContentMode(WebSettings.MIXED_CONTENT_ALWAYS_ALLOW);
}
// 支持PC上的Chrome調(diào)試WebView,具體方法請百度
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處理各種通知倍踪、請求事件
WebChromeClient主要輔助WebView處理Javascript的對話框系宫、網(wǎng)站圖標、網(wǎng)站title建车、加載進度等
2.3.1 WebViewClient的常用設(shè)置
-
-
shouldOverrideUrlLoading方法:在點擊請求的是鏈接時會調(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請求。2.1以上版本月洛,前提setJavaScriptEnabled(true);
@Override public void onReceivedSslError(WebView view, SslErrorHandler handler, SslError error) { //handler.cancel(); 默認的處理方式何恶,WebView變成空白頁 handler.proceed();//接受證書 //handleMessage(Message msg); 其他處理 }
-
3.onLoadResource:加載資源時調(diào)用,每個資源(比如圖片嚼黔,js细层,CSS等)都會調(diào)用一次。
@Override public void onLoadResource(WebView view, String url)
-
4.onPageStarted和onPageStarted:頁面加載開始和結(jié)束后會調(diào)用唬涧。
@Override public void onPageStarted(WebView view, String url, Bitmap favicon) @Override public void onPageFinished(WebView view, String url)
-
5.shouldInterceptRequest:同樣是加載資源時調(diào)用疫赎,但是這里WebView可以加載本地的資源提供給內(nèi)核,若本地處理返回數(shù)據(jù)碎节,內(nèi)核不從網(wǎng)絡(luò)上獲取數(shù)據(jù)捧搞。從API 11時引入, API21更新重載方法。加載本地資源使用方法請看 這篇博客 的 常用資源預(yù)加載 实牡。
//API 22添加的重載方法 @Override public WebResourceResponse shouldInterceptRequest(WebView view, WebResourceRequest request) @Override public WebResourceResponse shouldInterceptRequest(WebView view, String url)
因為上面的博客被刪除了陌僵,在這里寫一下如何加載本地資源轴合,包括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) {
// 因為加載每個圖片题涨,Js資源都要請求一次網(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方法闰渔,對應(yīng)JS的對話框席函,確認框,輸入框冈涧。JS彈出對話框等時茂附,會調(diào)用對應(yīng)的方法,我們在方法中彈出AlertDialog等督弓,顯示相應(yīng)信息即可
-
2.onProgressChanged:通知應(yīng)用程序當前網(wǎng)頁加載的進度
@Override public void onProgressChanged(WebView view, int newProgress)
-
3.onReceivedTitle:獲取網(wǎng)頁title標題营曼。
獲取標題的時間主要取決于網(wǎng)頁前段設(shè)置標題的位置,一般設(shè)置在頁面加載前面愚隧,可以較早調(diào)用到這個函數(shù)
@Override public void onReceivedTitle(WebView view, String title)
-
4.H5播放器全屏和去全屏方法
//有H5視頻蒂阱,按下全屏播放時調(diào)用的方法 @Override public void onShowCustomView(View view, CustomViewCallback callback) // 對應(yīng)的取消全屏方法 @Override public void onHideCustomView()
-
5.設(shè)置WebView視頻未播放時默認顯示占位圖。關(guān)于WebView中的視頻播放的相關(guān)知識狂塘,請點這里
@Override public Bitmap getDefaultVideoPoster()
3. Native與JS的相互調(diào)用
這個網(wǎng)上有很多資源了录煤,選擇一個我覺得比較全面的博客:Android:你要的WebView與 JS 交互方式 都在這里了。
這里只說重點:
Js調(diào)用Android現(xiàn)在一般是用WebView的addJavascriptInterface()方式荞胡,當然因為Android版本造成的漏洞不能忽視
Android調(diào)用Js辐赞,毫無疑問,evaluateJavascript()和loadUrl結(jié)合使用硝训,根據(jù)版本判斷
常見錯誤:
-
1.線程錯誤响委。Js調(diào)Android時發(fā)生,Android調(diào)時也經(jīng)常發(fā)生窖梁,因為調(diào)了Js赘风,很多情況還是會回調(diào)Android。報錯信息大致為Js線程必須一致等纵刘。很好解決邀窃,Android中在UI線程即可。但是推薦WebView.post()的方式,不推薦runOnUiThread()方式
mWebView.post(new Runnable() { @Override public void run() { // TODO } });
4. Cookie問題
4.1 WebView是有自己的Cookie系統(tǒng)的
WebView會在本地維護每次會話的cookie(保存在data/data/package_name/app_WebView/Cookies.db)瞬捕。當WebView加載URL的時候,WebView會從本地讀取該URL對應(yīng)的cookie鞍历,并攜帶該cookie與服務(wù)器進行通信。
WebView通過android.webkit.CookieManager類來維護cookie肪虎。CookieManager是WebView的cookie管理類劣砍。
4.2 Native獲得Cookie,設(shè)置給WebView如何做扇救。
很多時候我們需要將在native中的 登陸的狀態(tài) 同步到H5中避免再次登陸刑枝,這就需要用到對Cookie的管理。
問題分解:
1.在OkHttp(假定使用的是OkHttp迅腔,其他的方式獲得cookie更簡單)中獲得cookie装畅,并儲存;
2.取得cookie并設(shè)置給WebView沧烈。
解決:
4.2.1 Okhttp中獲得cookie掠兄。
獲得cookie很簡單,只需在OkHttpClient的構(gòu)建過程中加一行代碼锌雀。
List<Cookie> mCookies;
mOkHttpClient = new OkHttpClient.Builder()
...
// 下面就是對OkHttp的cookie的處理
.cookieJar(new CookieJar() {
// 對服務(wù)器返回的cookie的處理的方法
// 參數(shù)url和cookie就是cookie對應(yīng)的url和cookie的值
@Override
public void saveFromResponse(HttpUrl url, List<Cookie> cookies) {
// 對cookie的處理蚂夕,一般是存到內(nèi)存中,使其他地方可以獲得
mCookies = cookies;
}
// 發(fā)送請求時的cookie的處理汤锨,返回的List<Cookie>即請求的cookie
@Override
public List<Cookie> loadForRequest(HttpUrl url) {
return mCookies;
//return null;
}
})
.build();
當然双抽,也可以新建一個類,實現(xiàn)CookieJar闲礼,專門處理cookie問題牍汹。這里只是給出了最簡單的對cookie的處理,對于持久化等柬泽,就是另外的問題了慎菲。
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);驗證是否將cookie同步進去
//KLog.i("newCookie: " + newCookie);
}
這里有幾點要注意:
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的添加時包警,最好是一個cookie撵摆,set一次,最好不要自己拼接害晦,否則關(guān)于domain特铝,path,逗號,分號等等的問題會讓人欲仙欲死鲫剿。還有說法是用String不行鳄逾,要用StringBuilder。所以灵莲,盡量不自己拼雕凹。
4.3 Ajax跨域訪問時,Cookie帶不過去的解決方法
問題:Native已經(jīng)登錄笆呆,cookie可以設(shè)置進去请琳,但是網(wǎng)頁進行了復(fù)雜的ajax操作(我也不知道什么操作)粱挡,cookie帶不過去赠幕,到指定頁面還得登錄。5.0以下正常
解決:經(jīng)過排查询筏,發(fā)現(xiàn)高版本時問題出在ajax跳轉(zhuǎn)時榕堰,是Js對Cookie的操作,不經(jīng)過WebView嫌套,正常的WebView設(shè)置沒有作用逆屡。看了很多博客踱讨,還是在StackOverFlow上發(fā)現(xiàn)解決方法魏蔗。一行代碼
final CookieManager cookieManager = CookieManager.getInstance();
cookieManager.setAcceptCookie(true);
/**
* 設(shè)置webView支持JS的Cookie的調(diào)用,5.0以上才要設(shè)置
*/
// 大致意思:mWebView接收第三方對Cookie的操作痹筛,也就是支持Js對cookie的操作
cookieManager.setAcceptThirdPartyCookies(mWebView, true);
5. 棘手問題
5.1 Alert劫持
Alert劫持:Alert只會彈出一次莺治,并且WebView會卡死。重新加載都不行帚稠,必須殺死進程谣旁,重新打開App
解決方法很簡單,在自定義的onJsAlert方法中加一行代碼
@Override
public boolean onJsAlert(WebView view, String url, String message, JsResult result) {
...
result.confirm();// 不加這行代碼滋早,會造成Alert劫持:Alert只會彈出一次榄审,并且WebView會卡死
return true;
}