通常我們?cè)谧约洪_(kāi)發(fā)的 APP 中打開(kāi)網(wǎng)頁(yè)無(wú)非兩種方法: 一是跳轉(zhuǎn)到系統(tǒng)自帶的瀏覽器阳惹,二是使用 WebView 控件加載頁(yè)面。使用 WebView 控件的好處就是可以通過(guò)各種 api 接口來(lái)定制各種行為眶俩,常用的幾個(gè)設(shè)置地方為WebSettings莹汤、JavaScriptInterface、WebViewClient和WebChromeClient颠印。平時(shí)出現(xiàn)的問(wèn)題都可以通過(guò)修改這些設(shè)置來(lái)解決纲岭。
WebView.jpg
使用了 WebView 還是跳轉(zhuǎn)到了系統(tǒng)自帶的瀏覽器?
很簡(jiǎn)單的解決方法线罕,為你的 webview 設(shè)置一個(gè)新的 WebViewClient止潮。
webView.setWebViewClient(newWebViewClient(){@OverridepublicbooleanshouldOverrideUrlLoading(WebView view, String url){? ? ? ? view.loadUrl(url);returntrue;? ? }});
// 或者直接添加,效果是一樣的webView.setWebViewClient(newWebViewClient());
獲取網(wǎng)頁(yè)的標(biāo)題和圖標(biāo)
通過(guò) WebChromeClient 可以獲取到這些信息钞楼。
webView.setWebChromeClient(newWebChromeClient() {@OverridepublicvoidonReceivedTitle(WebView view, String title){super.onReceivedTitle(view, title);? ? ? ? setTitle(title);? ? }@OverridepublicvoidonReceivedIcon(WebView view, Bitmap icon){super.onReceivedIcon(view, icon);? ? ? ? setIcon(icon);? ? }});
但是喇闸,這里有個(gè)問(wèn)題,當(dāng)通過(guò)webView.goBack()方式返回上一級(jí)Web頁(yè)面的時(shí)候不會(huì)觸發(fā)這個(gè)方法询件,因此會(huì)導(dǎo)致標(biāo)題無(wú)法跟隨歷史記錄返回上一級(jí)頁(yè)面仅偎。所以需要在onPageFinished()中對(duì)界面標(biāo)題重新設(shè)置。
webView.setWebViewClient(newWebViewClient(){@OverridepublicvoidonPageFinished(WebView view, String url){super.onPageFinished(view, url);? ? ? ? setTitle(String.valueOf(view.getTitle()));? ? }});
返回鍵實(shí)現(xiàn)網(wǎng)頁(yè)的后退鍵
在 WebView 中可以通過(guò) goBack() 方法后退到歷史記錄的上一項(xiàng)。
// 在 Actvity 中監(jiān)聽(tīng)返回鍵按鈕@OverridepublicvoidonBackPressed(){if(webView.canGoBack())? ? ? ? ? ? webView.goBack();elsesuper.onBackPressed();? ? }
設(shè)置 WebView 的 header
在 WebView 的loadUrl()方法中傳入 Header 參數(shù)即可。
publicvoidloadURLWithHTTPHeaders(){finalString url ="http://cpacm.net";? ? WebView webView =newWebView(getActivity());? ? Map extraHeaders =newHashMap();? ? extraHeaders.put("Referer","http://www.google.com");? ? webView.loadUrl(url, extraHeaders);}
設(shè)置 WebView 的 User-Agent
不要試圖在 Header 里面去修改炮赦,而是在 WebSettings 修改
webView.getSettings().setUserAgentString("Mozilla/5.0 (Windows NT 10.0; WOW64; rv:50.0) Gecko/20100101 Firefox/50.0");
如何設(shè)置 WebView 的緩存
當(dāng)需要本地緩存網(wǎng)頁(yè)的時(shí)候就需要打開(kāi) WebViewSettings 的緩存開(kāi)關(guān),這樣子當(dāng)下次進(jìn)到該頁(yè)面無(wú)網(wǎng)絡(luò)的情況下也能打開(kāi)頁(yè)面甩牺。
WebSettings settings = webView.getSettings();settings.setAppCacheEnabled(true);//啟用應(yīng)用緩存settings.setDomStorageEnabled(true);//啟用或禁用DOM緩存。settings.setDatabaseEnabled(true);//啟用或禁用DOM緩存峰弹。if(SystemUtil.isNetworkConnected()) {//判斷是否聯(lián)網(wǎng)settings.setCacheMode(WebSettings.LOAD_DEFAULT);//默認(rèn)的緩存使用模式}else{? ? settings.setCacheMode(WebSettings.LOAD_CACHE_ONLY);//不從網(wǎng)絡(luò)加載數(shù)據(jù),只從緩存加載數(shù)據(jù)。}
無(wú)法下載文件介陶?
在自己寫(xiě)的 WebView 下是無(wú)法直接下載文件,需要自己監(jiān)聽(tīng)下載事件并對(duì)下載的動(dòng)作進(jìn)行處理色建。
/**
* 當(dāng)下載文件時(shí)打開(kāi)系統(tǒng)自帶的瀏覽器進(jìn)行下載哺呜,當(dāng)然也可以對(duì)捕獲到的 url 進(jìn)行處理在應(yīng)用內(nèi)下載。
**/webView.setDownloadListener(newFileDownLoadListener());privateclassFileDownLoadListenerimplementsDownloadListener{@OverridepublicvoidonDownloadStart(String url, String userAgent, String contentDisposition, String mimetype,longcontentLength){? ? ? ? Uri uri = Uri.parse(url);? ? ? ? Intent intent =newIntent(Intent.ACTION_VIEW, uri);? ? ? ? startActivity(intent);? ? }}
無(wú)法打開(kāi)文件選擇器箕戳?
通過(guò)重寫(xiě)WebChromeClient來(lái)實(shí)現(xiàn)點(diǎn)擊<input type='file'>來(lái)打開(kāi)系統(tǒng)文件選擇器某残。
一個(gè)完整的Activity示例
publicclassMainActivityextendsAppCompatActivity{/** Android 5.0以下版本的文件選擇回調(diào) */protectedValueCallback mFileUploadCallbackFirst;/** Android 5.0及以上版本的文件選擇回調(diào) */protectedValueCallback mFileUploadCallbackSecond;protectedstaticfinalintREQUEST_CODE_FILE_PICKER =51426;protectedString mUploadableFileTypes ="image/*";privateWebView mWebView;@OverrideprotectedvoidonCreate(Bundle savedInstanceState){super.onCreate(savedInstanceState);? ? ? ? setContentView(R.layout.activity_main);? ? ? ? initWebView();? ? }privatevoidinitWebView(){? ? ? ? mWebView = (WebView) findViewById(R.id.my_webview);? ? ? ? mWebView.loadUrl("file:///android_asset/index.html");? ? ? ? mWebView.setWebChromeClient(newOpenFileChromeClient());? ? }privateclassOpenFileChromeClientextendsWebChromeClient{//? Android 2.2 (API level 8)到Android 2.3 (API level 10)版本選擇文件時(shí)會(huì)觸發(fā)該隱藏方法@SuppressWarnings("unused")publicvoidopenFileChooser(ValueCallback<Uri> uploadMsg){? ? ? ? ? ? openFileChooser(uploadMsg,null);? ? ? ? }// Android 3.0 (API level 11)到 Android 4.0 (API level 15))版本選擇文件時(shí)會(huì)觸發(fā)国撵,該方法為隱藏方法publicvoidopenFileChooser(ValueCallback<Uri> uploadMsg, String acceptType){? ? ? ? ? ? openFileChooser(uploadMsg, acceptType,null);? ? ? ? }// Android 4.1 (API level 16) -- Android 4.3 (API level 18)版本選擇文件時(shí)會(huì)觸發(fā),該方法為隱藏方法@SuppressWarnings("unused")publicvoidopenFileChooser(ValueCallback<Uri> uploadMsg, String acceptType, String capture){? ? ? ? ? ? openFileInput(uploadMsg,null,false);? ? ? ? }// Android 5.0 (API level 21)以上版本會(huì)觸發(fā)該方法玻墅,該方法為公開(kāi)方法@SuppressWarnings("all")publicbooleanonShowFileChooser(WebView webView, ValueCallback<Uri[]> filePathCallback, WebChromeClient.FileChooserParams fileChooserParams){if(Build.VERSION.SDK_INT >=21) {finalbooleanallowMultiple = fileChooserParams.getMode() == FileChooserParams.MODE_OPEN_MULTIPLE;//是否支持多選openFileInput(null, filePathCallback, allowMultiple);returntrue;? ? ? ? ? ? }else{returnfalse;? ? ? ? ? ? }? ? ? ? }? ? }@SuppressLint("NewApi")protectedvoidopenFileInput(finalValueCallback fileUploadCallbackFirst,finalValueCallback fileUploadCallbackSecond,finalbooleanallowMultiple){//Android 5.0以下版本if(mFileUploadCallbackFirst !=null) {? ? ? ? ? ? mFileUploadCallbackFirst.onReceiveValue(null);? ? ? ? }? ? ? ? mFileUploadCallbackFirst = fileUploadCallbackFirst;//Android 5.0及以上版本if(mFileUploadCallbackSecond !=null) {? ? ? ? ? ? mFileUploadCallbackSecond.onReceiveValue(null);? ? ? ? }? ? ? ? mFileUploadCallbackSecond = fileUploadCallbackSecond;? ? ? ? Intent i =newIntent(Intent.ACTION_GET_CONTENT);? ? ? ? i.addCategory(Intent.CATEGORY_OPENABLE);if(allowMultiple) {if(Build.VERSION.SDK_INT >=18) {? ? ? ? ? ? ? ? i.putExtra(Intent.EXTRA_ALLOW_MULTIPLE,true);? ? ? ? ? ? }? ? ? ? }? ? ? ? i.setType(mUploadableFileTypes);? ? ? ? startActivityForResult(Intent.createChooser(i,"選擇文件"), REQUEST_CODE_FILE_PICKER);? ? }publicvoidonActivityResult(finalintrequestCode,finalintresultCode,finalIntent intent){if(requestCode == REQUEST_CODE_FILE_PICKER) {if(resultCode == Activity.RESULT_OK) {if(intent !=null) {//Android 5.0以下版本if(mFileUploadCallbackFirst !=null) {? ? ? ? ? ? ? ? ? ? ? ? mFileUploadCallbackFirst.onReceiveValue(intent.getData());? ? ? ? ? ? ? ? ? ? ? ? mFileUploadCallbackFirst =null;? ? ? ? ? ? ? ? ? ? }elseif(mFileUploadCallbackSecond !=null) {//Android 5.0及以上版本Uri[] dataUris =null;try{if(intent.getDataString() !=null) {? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? dataUris =newUri[] { Uri.parse(intent.getDataString()) };? ? ? ? ? ? ? ? ? ? ? ? ? ? }else{if(Build.VERSION.SDK_INT >=16) {if(intent.getClipData() !=null) {finalintnumSelectedFiles = intent.getClipData().getItemCount();? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? dataUris =newUri[numSelectedFiles];for(inti =0; i < numSelectedFiles; i++) {? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? dataUris[i] = intent.getClipData().getItemAt(i).getUri();? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? }? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? }? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? }? ? ? ? ? ? ? ? ? ? ? ? ? ? }? ? ? ? ? ? ? ? ? ? ? ? }catch(Exception ignored) { }? ? ? ? ? ? ? ? ? ? ? ? mFileUploadCallbackSecond.onReceiveValue(dataUris);? ? ? ? ? ? ? ? ? ? ? ? mFileUploadCallbackSecond =null;? ? ? ? ? ? ? ? ? ? }? ? ? ? ? ? ? ? }? ? ? ? ? ? }else{//這里mFileUploadCallbackFirst跟mFileUploadCallbackSecond在不同系統(tǒng)版本下分別持有了//WebView對(duì)象介牙,在用戶(hù)取消文件選擇器的情況下,需給onReceiveValue傳null返回值//否則WebView在未收到返回值的情況下澳厢,無(wú)法進(jìn)行任何操作环础,文件選擇器會(huì)失效if(mFileUploadCallbackFirst !=null) {? ? ? ? ? ? ? ? ? ? mFileUploadCallbackFirst.onReceiveValue(null);? ? ? ? ? ? ? ? ? ? mFileUploadCallbackFirst =null;? ? ? ? ? ? ? ? }elseif(mFileUploadCallbackSecond !=null) {? ? ? ? ? ? ? ? ? ? mFileUploadCallbackSecond.onReceiveValue(null);? ? ? ? ? ? ? ? ? ? mFileUploadCallbackSecond =null;? ? ? ? ? ? ? ? }? ? ? ? ? ? }? ? ? ? }? ? }}
怎么為 WebView 的加載添加進(jìn)度條
這里的onPageFinished()有個(gè)問(wèn)題,不能在這里監(jiān)聽(tīng)頁(yè)面是否加載完畢(我自己測(cè)試的時(shí)候剩拢,好像在重定向和加載完 iframes 時(shí)都會(huì)調(diào)用這個(gè)方法)线得。
把頁(yè)面加載完畢的判斷放在onProgressChanged()里可能會(huì)更為準(zhǔn)確。
webView.setWebChromeClient(newWebChromeClient() {@OverridepublicvoidonProgressChanged(WebView view,intposition){? ? ? ? progressBar.setProgress(position);if(position ==100) {? ? ? ? ? ? progressBar.setVisibility(View.GONE);? ? ? ? }super.onProgressChanged(view, position);? ? }});webView.setWebViewClient(newWebViewClient(){@OverridepublicvoidonPageStarted(WebView view, String url, Bitmap favicon){? ? ? ? progressBar.setVisibility(View.VISIBLE);super.onPageStarted(view, url, favicon);? ? }});
怎樣對(duì)頁(yè)面進(jìn)行 Js 注入徐伐?
首先你要在 WebView 開(kāi)啟 JavaScript,然后搭建橋梁
WebSettings webSettings = webView.getSettings();webSettings.setJavaScriptEnabled(true);webView.addJavascriptInterface(newWebAppBridge(newWebAppBridge.OauthLoginImpl() {@OverridepublicvoidgetResult(String s){//TODO}? ? ? ? }),"oauth");webView.loadUrl("javascript:"+ getAssetsJs("autologin.js"));webView.loadUrl("javascript:adduplistener()");
WebAppBridge的代碼
publicclassWebAppBridge{privateOauthLoginImpl oauthLogin;publicWebAppBridge(OauthLoginImpl oauthLogin){this.oauthLogin = oauthLogin;? ? }@JavascriptInterfacepublicvoidgetResult(String str){if(oauthLogin !=null)? ? ? ? ? ? oauthLogin.getResult(str);? ? }publicinterfaceOauthLoginImpl{voidgetResult(String s);? ? }}
簡(jiǎn)單的說(shuō)就是向網(wǎng)頁(yè)注入一段 js, 在這段 js 里面設(shè)置回調(diào)到j(luò)ava中的方法getResult()贯钩,由 WebAppBridge.getResult 來(lái)回收。
其中js的核心代碼為
oauth.getResult(str);
其中 oauth 這個(gè)名稱(chēng)要與webView.addJavascriptInterface()方法的第二個(gè)參數(shù)一樣呵晨。
具體的代碼可以參考這個(gè)項(xiàng)目中寫(xiě)的 js 注入邏輯OauthDialog
如何手動(dòng)添加 Cookie
需要獲得 CookieManager 的對(duì)象并將 cookie 設(shè)置進(jìn)去魏保。
從服務(wù)器的返回頭中取出 cookie 根據(jù)Http請(qǐng)求的客戶(hù)端不同,獲取 cookie 的方式也不同摸屠,請(qǐng)自行獲取谓罗。
/** * 將cookie設(shè)置到 WebView *@paramurl 要加載的 url *@paramcookie 要同步的 cookie */publicstaticvoidsyncCookie(String url,String cookie){if(Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP) {? ? ? ? CookieSyncManager.createInstance(context);? ? }? ? CookieManager cookieManager = CookieManager.getInstance();? ? cookieManager.setAcceptCookie(true);/**
? ? * cookie 設(shè)置形式
? ? * cookieManager.setCookie(url, "key=value;" + "domain=[your domain];path=/;")
? ? **/cookieManager.setCookie(url, cookie);}
刪除 Cookie 的方法
/**
* 這個(gè)兩個(gè)在 API level 21 被拋棄
* CookieManager.getInstance().removeSessionCookie();
* CookieManager.getInstance().removeAllCookie();
*
* 推薦使用這兩個(gè), level 21 新加的
* CookieManager.getInstance().removeSessionCookies();
* CookieManager.getInstance().removeAllCookies();
**/publicstaticvoidremoveCookies(){? ? CookieManager cookieManager = CookieManager.getInstance();? ? cookieManager.removeAllCookie();if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {? ? ? ? cookieManager.flush();? ? }else{? ? ? ? CookieSyncManager.createInstance(Application.getInstance());? ? ? ? CookieSyncManager.getInstance().sync();? ? }}
如何使 HTML5 video 在 WebView 全屏顯示
當(dāng)網(wǎng)頁(yè)全屏播放視頻時(shí)會(huì)調(diào)用WebChromeClient.onShowCustomView()方法季二,所以可以通過(guò)將 video 播放的視圖全屏達(dá)到目的檩咱。
@OverridepublicvoidonShowCustomView(View view, CustomViewCallback callback){if(viewinstanceofFrameLayout && fullScreenView !=null) {// A video wants to be shownthis.videoViewContainer = (FrameLayout) view;this.videoViewCallback = callback;? ? ? ? fullScreenView.addView(videoViewContainer,newViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT));? ? ? ? fullScreenView.setVisibility(View.VISIBLE);? ? ? ? isVideoFullscreen =true;? ? }}@OverridepublicvoidonHideCustomView(){if(isVideoFullscreen && fullScreenView !=null) {// Hide the video view, remove it, and show the non-video viewfullScreenView.setVisibility(View.INVISIBLE);? ? ? ? fullScreenView.removeView(videoViewContainer);// Call back (only in API level <19, because in API level 19+ with chromium webview it crashes)if(videoViewCallback !=null&& !videoViewCallback.getClass().getName().contains(".chromium.")) {? ? ? ? ? ? videoViewCallback.onCustomViewHidden();? ? ? ? }? ? ? ? isVideoFullscreen =false;? ? ? ? videoViewContainer =null;? ? ? ? videoViewCallback =null;? ? }}
但是很多的手機(jī)版本在網(wǎng)頁(yè)視頻播放時(shí)是不會(huì)調(diào)用這個(gè)方法的,所以這個(gè)方法局限性很大胯舷。
Android5.0上 WebView中Http和Https混合問(wèn)題
/**
* MIXED_CONTENT_ALWAYS_ALLOW:允許從任何來(lái)源加載內(nèi)容刻蚯,即使起源是不安全的;
* MIXED_CONTENT_NEVER_ALLOW:不允許Https加載Http的內(nèi)容桑嘶,即不允許從安全的起源去加載一個(gè)不安全的資源炊汹;
* MIXED_CONTENT_COMPATIBILITY_MODE:當(dāng)涉及到混合式內(nèi)容時(shí),WebView 會(huì)嘗試去兼容最新Web瀏覽器的風(fēng)格逃顶。
**/if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {? ? webView.getSettings().setMixedContentMode(WebSettings.MIXED_CONTENT_ALWAYS_ALLOW);}
如何避免 WebView 的內(nèi)存泄露問(wèn)題
可以將 Webview 的 Activity 新起一個(gè)進(jìn)程讨便,結(jié)束的時(shí)候直接System.exit(0);退出當(dāng)前進(jìn)程;
不在xml中定義 WebView以政,而是在代碼中創(chuàng)建霸褒,使用 getApplicationgContext() 作為傳遞的 Conetext;
在 Activity 銷(xiāo)毀的時(shí)候盈蛮,將 WebView 置空
@OverrideprotectedvoidonDestroy(){if(webView !=null) {? ? ? ? webView.loadDataWithBaseURL(null,"","text/html","utf-8",null);? ? ? ? webView.clearHistory();? ? ? ? ((ViewGroup) webView.getParent()).removeView(webView);? ? ? ? webView.destroy();? ? ? ? webView =null;? ? }super.onDestroy();}
總結(jié)
如果你踩到了 WebView 上的坑废菱,請(qǐng)先默哀一分鐘,然后努力找找解決方法吧,總會(huì)有人體驗(yàn)過(guò)你的悲劇殊轴,也會(huì)有人重蹈你的覆轍衰倦。
當(dāng)然 WebView 里肯定不止我上面列出來(lái)的這些問(wèn)題,如果你有更多的 WebView 問(wèn)題解決方案歡迎評(píng)論交流梳凛。
作者:cpacm
鏈接:http://www.reibang.com/p/fea5e829b30a
來(lái)源:簡(jiǎn)書(shū)
簡(jiǎn)書(shū)著作權(quán)歸作者所有耿币,任何形式的轉(zhuǎn)載都請(qǐng)聯(lián)系作者獲得授權(quán)并注明出處梳杏。