通常我們在自己開發(fā)的 APP 中打開網(wǎng)頁無非兩種方法: 一是跳轉(zhuǎn)到系統(tǒng)自帶的瀏覽器初家,二是使用 WebView 控件加載頁面。使用 WebView 控件的好處就是可以通過各種 api 接口來定制各種行為漠秋,常用的幾個設(shè)置地方為 WebSettings、JavaScriptInterface寥假、WebViewClient 和 WebChromeClient妄壶。平時出現(xiàn)的問題都可以通過修改這些設(shè)置來解決。
使用了 WebView 還是跳轉(zhuǎn)到了系統(tǒng)自帶的瀏覽器键耕?
很簡單的解決方法寺滚,為你的 webview 設(shè)置一個新的 WebViewClient。
webView.setWebViewClient(new WebViewClient(){
@Override
public boolean shouldOverrideUrlLoading(WebView view, String url) {
view.loadUrl(url);
return true;
}
});
// 或者直接添加屈雄,效果是一樣的
webView.setWebViewClient(new WebViewClient());
獲取網(wǎng)頁的標(biāo)題和圖標(biāo)
通過 WebChromeClient 可以獲取到這些信息村视。
webView.setWebChromeClient(new WebChromeClient() {
@Override
public void onReceivedTitle(WebView view, String title) {
super.onReceivedTitle(view, title);
setTitle(title);
}
@Override
public void onReceivedIcon(WebView view, Bitmap icon) {
super.onReceivedIcon(view, icon);
setIcon(icon);
}
});
但是,這里有個問題酒奶,當(dāng)通過 webView.goBack()
方式返回上一級Web頁面的時候不會觸發(fā)這個方法蚁孔,因此會導(dǎo)致標(biāo)題無法跟隨歷史記錄返回上一級頁面。所以需要在 onPageFinished()
中對界面標(biāo)題重新設(shè)置讥蟆。
webView.setWebViewClient(new WebViewClient(){
@Override
public void onPageFinished(WebView view, String url) {
super.onPageFinished(view, url);
setTitle(String.valueOf(view.getTitle()));
}
});
返回鍵實現(xiàn)網(wǎng)頁的后退鍵
在 WebView 中可以通過 goBack() 方法后退到歷史記錄的上一項。
// 在 Actvity 中監(jiān)聽返回鍵按鈕
@Override
public void onBackPressed() {
if (webView.canGoBack())
webView.goBack();
else
super.onBackPressed();
}
設(shè)置 WebView 的 header
在 WebView 的 loadUrl()
方法中傳入 Header 參數(shù)即可纺阔。
public void loadURLWithHTTPHeaders() {
final String url = "http://cpacm.net";
WebView webView = new WebView(getActivity());
Map<String,String> extraHeaders = new HashMap<String, String>();
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)頁的時候就需要打開 WebViewSettings 的緩存開關(guān),這樣子當(dāng)下次進到該頁面無網(wǎng)絡(luò)的情況下也能打開頁面。
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ù)。
}
無法下載文件结榄?
在自己寫的 WebView 下是無法直接下載文件中贝,需要自己監(jiān)聽下載事件并對下載的動作進行處理。
/**
* 當(dāng)下載文件時打開系統(tǒng)自帶的瀏覽器進行下載臼朗,當(dāng)然也可以對捕獲到的 url 進行處理在應(yīng)用內(nèi)下載邻寿。
**/
webView.setDownloadListener(new FileDownLoadListener());
private class FileDownLoadListener implements DownloadListener {
@Override
public void onDownloadStart(String url, String userAgent, String contentDisposition, String mimetype, long contentLength) {
Uri uri = Uri.parse(url);
Intent intent = new Intent(Intent.ACTION_VIEW, uri);
startActivity(intent);
}
}
無法打開文件選擇器?
通過重寫 WebChromeClient
來實現(xiàn)點擊 <input type='file'>
來打開系統(tǒng)文件選擇器视哑。
一個完整的Activity示例
public class MainActivity extends AppCompatActivity {
/** Android 5.0以下版本的文件選擇回調(diào) */
protected ValueCallback<Uri> mFileUploadCallbackFirst;
/** Android 5.0及以上版本的文件選擇回調(diào) */
protected ValueCallback<Uri[]> mFileUploadCallbackSecond;
protected static final int REQUEST_CODE_FILE_PICKER = 51426;
protected String mUploadableFileTypes = "image/*";
private WebView mWebView;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
initWebView();
}
private void initWebView() {
mWebView = (WebView) findViewById(R.id.my_webview);
mWebView.loadUrl("file:///android_asset/index.html");
mWebView.setWebChromeClient(new OpenFileChromeClient());
}
private class OpenFileChromeClient extends WebChromeClient {
// Android 2.2 (API level 8)到Android 2.3 (API level 10)版本選擇文件時會觸發(fā)該隱藏方法
@SuppressWarnings("unused")
public void openFileChooser(ValueCallback<Uri> uploadMsg) {
openFileChooser(uploadMsg, null);
}
// Android 3.0 (API level 11)到 Android 4.0 (API level 15))版本選擇文件時會觸發(fā)俄占,該方法為隱藏方法
public void openFileChooser(ValueCallback<Uri> uploadMsg, String acceptType) {
openFileChooser(uploadMsg, acceptType, null);
}
// Android 4.1 (API level 16) -- Android 4.3 (API level 18)版本選擇文件時會觸發(fā)录别,該方法為隱藏方法
@SuppressWarnings("unused")
public void openFileChooser(ValueCallback<Uri> uploadMsg, String acceptType, String capture) {
openFileInput(uploadMsg, null, false);
}
// Android 5.0 (API level 21)以上版本會觸發(fā)該方法,該方法為公開方法
@SuppressWarnings("all")
public boolean onShowFileChooser(WebView webView, ValueCallback<Uri[]> filePathCallback, WebChromeClient.FileChooserParams fileChooserParams) {
if (Build.VERSION.SDK_INT >= 21) {
final boolean allowMultiple = fileChooserParams.getMode() == FileChooserParams.MODE_OPEN_MULTIPLE;//是否支持多選
openFileInput(null, filePathCallback, allowMultiple);
return true;
}
else {
return false;
}
}
}
@SuppressLint("NewApi")
protected void openFileInput(final ValueCallback<Uri> fileUploadCallbackFirst, final ValueCallback<Uri[]> fileUploadCallbackSecond, final boolean allowMultiple) {
//Android 5.0以下版本
if (mFileUploadCallbackFirst != null) {
mFileUploadCallbackFirst.onReceiveValue(null);
}
mFileUploadCallbackFirst = fileUploadCallbackFirst;
//Android 5.0及以上版本
if (mFileUploadCallbackSecond != null) {
mFileUploadCallbackSecond.onReceiveValue(null);
}
mFileUploadCallbackSecond = fileUploadCallbackSecond;
Intent i = new Intent(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);
}
public void onActivityResult(final int requestCode, final int resultCode, final Intent 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;
}
else if (mFileUploadCallbackSecond != null) {//Android 5.0及以上版本
Uri[] dataUris = null;
try {
if (intent.getDataString() != null) {
dataUris = new Uri[] { Uri.parse(intent.getDataString()) };
}
else {
if (Build.VERSION.SDK_INT >= 16) {
if (intent.getClipData() != null) {
final int numSelectedFiles = intent.getClipData().getItemCount();
dataUris = new Uri[numSelectedFiles];
for (int i = 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對象,在用戶取消文件選擇器的情況下晴氨,需給onReceiveValue傳null返回值
//否則WebView在未收到返回值的情況下,無法進行任何操作嚷量,文件選擇器會失效
if (mFileUploadCallbackFirst != null) {
mFileUploadCallbackFirst.onReceiveValue(null);
mFileUploadCallbackFirst = null;
}
else if (mFileUploadCallbackSecond != null) {
mFileUploadCallbackSecond.onReceiveValue(null);
mFileUploadCallbackSecond = null;
}
}
}
}
}
怎么為 WebView 的加載添加進度條
這里的 onPageFinished()
有個問題镇饮,不能在這里監(jiān)聽頁面是否加載完畢(我自己測試的時候,好像在重定向和加載完 iframes 時都會調(diào)用這個方法)耗绿。
把頁面加載完畢的判斷放在 onProgressChanged()
里可能會更為準(zhǔn)確苹支。
webView.setWebChromeClient(new WebChromeClient() {
@Override
public void onProgressChanged(WebView view, int position) {
progressBar.setProgress(position);
if (position == 100) {
progressBar.setVisibility(View.GONE);
}
super.onProgressChanged(view, position);
}
});
webView.setWebViewClient(new WebViewClient(){
@Override
public void onPageStarted(WebView view, String url, Bitmap favicon) {
progressBar.setVisibility(View.VISIBLE);
super.onPageStarted(view, url, favicon);
}
});
怎樣對頁面進行 Js 注入?
首先你要在 WebView 開啟 JavaScript,然后搭建橋梁
WebSettings webSettings = webView.getSettings();
webSettings.setJavaScriptEnabled(true);
webView.addJavascriptInterface(new WebAppBridge(new WebAppBridge.OauthLoginImpl() {
@Override
public void getResult(String s) {
//TODO
}
}),
"oauth");
webView.loadUrl("javascript:" + getAssetsJs("autologin.js"));
webView.loadUrl("javascript:adduplistener()");
WebAppBridge的代碼
public class WebAppBridge {
private OauthLoginImpl oauthLogin;
public WebAppBridge(OauthLoginImpl oauthLogin) {
this.oauthLogin = oauthLogin;
}
@JavascriptInterface
public void getResult(String str) {
if (oauthLogin != null)
oauthLogin.getResult(str);
}
public interface OauthLoginImpl {
void getResult(String s);
}
}
簡單的說就是向網(wǎng)頁注入一段 js, 在這段 js 里面設(shè)置回調(diào)到j(luò)ava中的方法 getResult()
缭乘,由 WebAppBridge.getResult 來回收沐序。
其中js的核心代碼為
oauth.getResult(str);
其中 oauth 這個名稱要與 webView.addJavascriptInterface()
方法的第二個參數(shù)一樣。
具體的代碼可以參考這個項目中寫的 js 注入邏輯 OauthDialog
如何手動添加 Cookie
需要獲得 CookieManager 的對象并將 cookie 設(shè)置進去堕绩。
從服務(wù)器的返回頭中取出 cookie 根據(jù)Http請求的客戶端不同策幼,獲取 cookie 的方式也不同,請自行獲取奴紧。
/**
* 將cookie設(shè)置到 WebView
* @param url 要加載的 url
* @param cookie 要同步的 cookie
*/
public static void syncCookie(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 的方法
/**
* 這個兩個在 API level 21 被拋棄
* CookieManager.getInstance().removeSessionCookie();
* CookieManager.getInstance().removeAllCookie();
*
* 推薦使用這兩個特姐, level 21 新加的
* CookieManager.getInstance().removeSessionCookies();
* CookieManager.getInstance().removeAllCookies();
**/
public static void removeCookies() {
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)頁全屏播放視頻時會調(diào)用 WebChromeClient.onShowCustomView()
方法,所以可以通過將 video 播放的視圖全屏達(dá)到目的黍氮。
@Override
public void onShowCustomView(View view, CustomViewCallback callback) {
if (view instanceof FrameLayout && fullScreenView != null) {
// A video wants to be shown
this.videoViewContainer = (FrameLayout) view;
this.videoViewCallback = callback;
fullScreenView.addView(videoViewContainer, new ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT));
fullScreenView.setVisibility(View.VISIBLE);
isVideoFullscreen = true;
}
}
@Override
public void onHideCustomView() {
if (isVideoFullscreen && fullScreenView != null) {
// Hide the video view, remove it, and show the non-video view
fullScreenView.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;
}
}
但是很多的手機版本在網(wǎng)頁視頻播放時是不會調(diào)用這個方法的唐含,所以這個方法局限性很大。
Android5.0上 WebView中Http和Https混合問題
/**
* MIXED_CONTENT_ALWAYS_ALLOW:允許從任何來源加載內(nèi)容沫浆,即使起源是不安全的捷枯;
* MIXED_CONTENT_NEVER_ALLOW:不允許Https加載Http的內(nèi)容,即不允許從安全的起源去加載一個不安全的資源专执;
* MIXED_CONTENT_COMPATIBILITY_MODE:當(dāng)涉及到混合式內(nèi)容時淮捆,WebView 會嘗試去兼容最新Web瀏覽器的風(fēng)格。
**/
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
webView.getSettings().setMixedContentMode(WebSettings.MIXED_CONTENT_ALWAYS_ALLOW);
}
如何避免 WebView 的內(nèi)存泄露問題
- 可以將 Webview 的 Activity 新起一個進程,結(jié)束的時候直接System.exit(0);退出當(dāng)前進程攀痊;
- 不在xml中定義 WebView桐腌,而是在代碼中創(chuàng)建,使用 getApplicationgContext() 作為傳遞的 Conetext苟径;
- 在 Activity 銷毀的時候案站,將 WebView 置空
@Override
protected void onDestroy() {
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 上的坑,請先默哀一分鐘棘街,然后努力找找解決方法吧蟆盐,總會有人體驗過你的悲劇,也會有人重蹈你的覆轍蹬碧。
當(dāng)然 WebView 里肯定不止我上面列出來的這些問題舱禽,如果你有更多的 WebView 問題解決方案歡迎評論交流。