在學習WebView的時候就知道了WebView會出現很多稀奇古怪的問題,真碰上的時候還是焦頭爛額,很多問題的解決方案要在網上找很久很久很久。這里做了稍微全面的總結。
劃重點:
1.內存泄露的解決方法
2.Native獲得的cookie同步到WebView中
3.API5.0以上Ajax跨域訪問無法攜帶cookie的問題
4.Alert劫持問題
1. 內存泄露
關于內存泄漏,想要徹底解決,最好的方法是當你要用webview的時候,另外單獨開一個進程(如何單開進程請自行搜索) 去使用webview 并且當這個 進程結束時,請手動調用System.exit(0)。 但是這種情況又會有另外的問題,就是進程間的通信。
不單開進程時:
1.1 JS無法釋放,WebView在執行JS時被關閉,這些JS資源會無法釋放,一直耗電,一直占CPU,解決方法是在onStop和onResume里分別把setJavaScriptEnabled();給設置成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) {
// 如果先調用destroy()方法,則會命中if (isDestroyed()) return;這一行代碼,需要先onDetachedFromWindow(),再
// destory()
ViewParent parent = mWebView.getParent();
if (parent != null) {
((ViewGroup) parent).removeView(mWebView);
}
mWebView.stopLoading();
// 退出時調用此方法,移除綁定的服務,否則某些特定系統會報錯
mWebView.getSettings().setJavaScriptEnabled(false);
mWebView.clearHistory();
mWebView.clearAnimation();
mWebView.clearView();
mWebView.removeAllViews();
try {
mWebView.destroy();
} catch (Throwable ex) {
}
2. WebView的各種設置
2.1 首先是創建
最好不要在XML中直接添加WebView,而是預留一個FrameLayout,代碼中創建WebView,添加到FrameLayout中。
這樣能解決很多內存泄露的問題。
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();
//設置了這個屬性后我們才能在 WebView 里與我們的 Js 代碼進行交互,對于 WebApp 是非常重要的,默認是 false,
//因此我們需要設置為 true,這個本身會有漏洞,具體的下面我會講到
webSettings.setJavaScriptEnabled(true);
//設置 JS 是否可以打開 WebView 新窗口
webSettings.setJavaScriptCanOpenWindowsAutomatically(true);
//WebView 是否支持多窗口,如果設置為 true,需要重寫
//WebChromeClient#onCreateWindow(WebView, boolean, boolean, Message) 函數,默認為 false
webSettings.setSupportMultipleWindows(true);
//這個屬性用來設置 WebView 是否能夠加載圖片資源,需要注意的是,這個方法會控制所有圖片,包括那些使用 data URI 協議嵌入
//的圖片。使用 setBlockNetworkImage(boolean) 方法來控制僅僅加載使用網絡 URI 協議的圖片。需要提到的一點是如果這
//個設置從 false 變為 true 之后,所有被內容引用的正在顯示的 WebView 圖片資源都會自動加載,該標識默認值為 true。
webSettings.setLoadsImagesAutomatically(false);
//標識是否加載網絡上的圖片(使用 http 或者 https 域名的資源),需要注意的是如果 getLoadsImagesAutomatically()
//不返回 true,這個標識將沒有作用。這個標識和上面的標識會互相影響。
webSettings.setBlockNetworkImage(true);
//顯示WebView提供的縮放控件
webSettings.setDisplayZoomControls(true);
webSettings.setBuiltInZoomControls(true);
//推薦使用。打開 WebView 的 storage 功能,這樣 JS 的 localStorage,sessionStorage 對象才可以使用
webSettings.setDomStorageEnabled(true);
//推薦打開。設置是否啟動 WebView 的DB API,默認值為 false
webSettings.setDatabaseEnabled(true);
webSettings.setDatabasePath(Utils.getContext().getDir("WebDb",MODE_PRIVATE).getPath());
//打開 WebView 自帶的的 LBS 功能,這樣 JS 的 geolocation 對象才可以使用,注意manifest中的權限
webSettings.setGeolocationEnabled(true);
webSettings.setGeolocationDatabasePath("");
//設置是否打開 WebView 表單數據的保存功能
webSettings.setSaveFormData(true);
//設置 WebView 的默認 userAgent 字符串
webSettings.setUserAgentString("");
// 不推薦使用。設置緩存,是否開啟,緩存模式,緩存大小,緩存路徑
webSettings.setAppCacheEnabled(true);
webSettings.setCacheMode(WebSettings.LOAD_DEFAULT);
webSettings.setAppCacheMaxSize(10 * 1024 * 1024);
webSettings.setAppCachePath(Utils.getContext().getExternalCacheDir().getAbsolutePath());
// 這兩個一起設置,表明WebView會自適應手機屏幕的寬度
webSettings.setUseWideViewPort(true);
webSettings.setLoadWithOverviewMode(true);
//設置 WebView 的字體,可以通過這個函數,改變 WebView 的字體,默認字體為 "sans-serif"
webSettings.setStandardFontFamily("");
//設置 WebView 字體的大小,默認大小為 16
webSettings.setDefaultFontSize(20);
//設置 WebView 支持的最小字體大小,默認為 8
webSettings.setMinimumFontSize(12);
//設置文本的縮放倍數,默認為 100
webSettings.setTextZoom(2);
// 允許高版本http和https混合訪問
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
settings.setMixedContentMode(WebSettings.MIXED_CONTENT_ALWAYS_ALLOW);
}
// 支持PC上的Chrome調試WebView,具體方法請百度
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
// 只支持Debug模式下調試
if (0 != (getApplicationInfo().flags &= ApplicationInfo.FLAG_DEBUGGABLE)) {
WebView.setWebContentsDebuggingEnabled(true);
}
}
2.3 然后是WebViewClient和ChromeClient的設置
WebViewClient主要幫助WebView處理各種通知、請求事件
WebChromeClient主要輔助WebView處理Javascript的對話框、網站圖標、網站title、加載進度等
2.3.1 WebViewClient的常用設置
-
-
shouldOverrideUrlLoading方法:在點擊請求的是鏈接時會調用此方法。返回false表明交給系統處理,我們自己不干預,正常情況下這么做;返回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:加載資源時調用,每個資源(比如圖片,js,CSS等)都會調用一次。
@Override public void onLoadResource(WebView view, String url)
-
4.onPageStarted和onPageStarted:頁面加載開始和結束后會調用。
@Override public void onPageStarted(WebView view, String url, Bitmap favicon) @Override public void onPageFinished(WebView view, String url)
-
5.shouldInterceptRequest:同樣是加載資源時調用,但是這里WebView可以加載本地的資源提供給內核,若本地處理返回數據,內核不從網絡上獲取數據。從API 11時引入, API21更新重載方法。加載本地資源使用方法請看 這篇博客 的 常用資源預加載 。
//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資源都要請求一次網絡。所以這種判斷這種資源是否在本地,如果在本地,就直接返回本地的資源,不再去網上取
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的常用設置
1.onJsAlert/onJsConfirm/onJsPrompt方法,對應JS的對話框,確認框,輸入框。JS彈出對話框等時,會調用對應的方法,我們在方法中彈出AlertDialog等,顯示相應信息即可
-
2.onProgressChanged:通知應用程序當前網頁加載的進度
@Override public void onProgressChanged(WebView view, int newProgress)
-
3.onReceivedTitle:獲取網頁title標題。
獲取標題的時間主要取決于網頁前段設置標題的位置,一般設置在頁面加載前面,可以較早調用到這個函數
@Override public void onReceivedTitle(WebView view, String title)
-
4.H5播放器全屏和去全屏方法
//有H5視頻,按下全屏播放時調用的方法 @Override public void onShowCustomView(View view, CustomViewCallback callback) // 對應的取消全屏方法 @Override public void onHideCustomView()
-
5.設置WebView視頻未播放時默認顯示占位圖。關于WebView中的視頻播放的相關知識,請點這里
@Override public Bitmap getDefaultVideoPoster()
3. Native與JS的相互調用
這個網上有很多資源了,選擇一個我覺得比較全面的博客:Android:你要的WebView與 JS 交互方式 都在這里了。
這里只說重點:
Js調用Android現在一般是用WebView的addJavascriptInterface()方式,當然因為Android版本造成的漏洞不能忽視
Android調用Js,毫無疑問,evaluateJavascript()和loadUrl結合使用,根據版本判斷
常見錯誤:
-
1.線程錯誤。Js調Android時發生,Android調時也經常發生,因為調了Js,很多情況還是會回調Android。報錯信息大致為Js線程必須一致等。很好解決,Android中在UI線程即可。但是推薦WebView.post()的方式,不推薦runOnUiThread()方式
mWebView.post(new Runnable() { @Override public void run() { // TODO } });
4. Cookie問題
4.1 WebView是有自己的Cookie系統的
WebView會在本地維護每次會話的cookie(保存在data/data/package_name/app_WebView/Cookies.db)。當WebView加載URL的時候,WebView會從本地讀取該URL對應的cookie,并攜帶該cookie與服務器進行通信。
WebView通過android.webkit.CookieManager類來維護cookie。CookieManager是WebView的cookie管理類。
4.2 Native獲得Cookie,設置給WebView如何做。
很多時候我們需要將在native中的 登陸的狀態 同步到H5中避免再次登陸,這就需要用到對Cookie的管理。
問題分解:
1.在OkHttp(假定使用的是OkHttp,其他的方式獲得cookie更簡單)中獲得cookie,并儲存;
2.取得cookie并設置給WebView。
解決:
4.2.1 Okhttp中獲得cookie。
獲得cookie很簡單,只需在OkHttpClient的構建過程中加一行代碼。
List<Cookie> mCookies;
mOkHttpClient = new OkHttpClient.Builder()
...
// 下面就是對OkHttp的cookie的處理
.cookieJar(new CookieJar() {
// 對服務器返回的cookie的處理的方法
// 參數url和cookie就是cookie對應的url和cookie的值
@Override
public void saveFromResponse(HttpUrl url, List<Cookie> cookies) {
// 對cookie的處理,一般是存到內存中,使其他地方可以獲得
mCookies = cookies;
}
// 發送請求時的cookie的處理,返回的List<Cookie>即請求的cookie
@Override
public List<Cookie> loadForRequest(HttpUrl url) {
return mCookies;
//return null;
}
})
.build();
當然,也可以新建一個類,實現CookieJar,專門處理cookie問題。這里只是給出了最簡單的對cookie的處理,對于持久化等,就是另外的問題了。
4.2.2 將cookie同步到WebView中
private void syncCookies(String url, List<Cookie> cookies) {
// 一些前提設置
CookieSyncManager.createInstance(this);
final CookieManager cookieManager = CookieManager.getInstance();
cookieManager.setAcceptCookie(true);
/**
* 設置webView支持JS的Cookie的調用,5.0以上才要設置
*/
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,各種設置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一次,最好不要自己拼接,否則關于domain,path,逗號,分號等等的問題會讓人欲仙欲死。還有說法是用String不行,要用StringBuilder。所以,盡量不自己拼。
4.3 Ajax跨域訪問時,Cookie帶不過去的解決方法
問題:Native已經登錄,cookie可以設置進去,但是網頁進行了復雜的ajax操作(我也不知道什么操作),cookie帶不過去,到指定頁面還得登錄。5.0以下正常
解決:經過排查,發現高版本時問題出在ajax跳轉時,是Js對Cookie的操作,不經過WebView,正常的WebView設置沒有作用。看了很多博客,還是在StackOverFlow上發現解決方法。一行代碼
final CookieManager cookieManager = CookieManager.getInstance();
cookieManager.setAcceptCookie(true);
/**
* 設置webView支持JS的Cookie的調用,5.0以上才要設置
*/
// 大致意思: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;
}