Webview常见问题汇总

前言

通常我们在自己开发的 APP 中打开网页无非两种方法: 一是跳转到系统自带的浏览器,二是使用 WebView 控件加载页面。使用 WebView 控件的好处就是可以通过各种 api 接口来定制各种行为,常用的几个设置地方为 WebSettings、JavaScriptInterface、WebViewClient 和WebChromeClient。平时出现的问题都可以通过修改这些设置来解决。

问题总结

1、使用了 WebView 还是跳转到了系统自带的浏览器?

很简单的解决方法,为你的 webview 设置一个新的 WebViewClient。

1
2
3
4
5
6
7
webView.setWebViewClient(new WebViewClient(){
@Override
public boolean shouldOverrideUrlLoading(WebView view, String url) {
view.loadUrl(url);
return true;
}
});
1
2
// 或者直接添加,效果是一样的
webView.setWebViewClient(new WebViewClient());

2、获取网页的标题和图标

通过 WebChromeClient 可以获取到这些信息。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
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);
}

});

但是,这里有个问题,当通过 webView.goBack() 方式返回上一级Web页面的时候不会触发这个方法,因此会导致标题无法跟随历史记录返回上一级页面。所以需要在 onPageFinished() 中对界面标题重新设置。

1
2
3
4
5
6
7
webView.setWebViewClient(new WebViewClient(){
@Override
public void onPageFinished(WebView view, String url) {
super.onPageFinished(view, url);
setTitle(String.valueOf(view.getTitle()));
}
});

3、返回键实现网页的后退键

在 WebView 中可以通过 goBack() 方法后退到历史记录的上一项。

1
2
3
4
5
6
7
8
// 在 Actvity 中监听返回键按钮
@Override
public void onBackPressed() {
if (webView.canGoBack())
webView.goBack();
else
super.onBackPressed();
}

4、设置 WebView 的 header

在 WebView 的 loadUrl() 方法中传入 Header 参数即可。

1
2
3
4
5
6
7
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);
}

5、设置 WebView 的 User-Agent

不要试图在 Header 里面去修改,而是在 WebSettings 修改

1
webView.getSettings().setUserAgentString("Mozilla/5.0 (Windows NT 10.0; WOW64; rv:50.0) Gecko/20100101 Firefox/50.0");

6、如何设置 WebView 的缓存

当需要本地缓存网页的时候就需要打开 WebViewSettings 的缓存开关,这样子当下次进到该页面无网络的情况下也能打开页面。

1
2
3
4
5
6
7
8
9
WebSettings settings = webView.getSettings();
settings.setAppCacheEnabled(true); //启用应用缓存
settings.setDomStorageEnabled(true); //启用或禁用DOM缓存。
settings.setDatabaseEnabled(true); //启用或禁用DOM缓存。
if (SystemUtil.isNetworkConnected()) { //判断是否联网
settings.setCacheMode(WebSettings.LOAD_DEFAULT); //默认的缓存使用模式
} else {
settings.setCacheMode(WebSettings.LOAD_CACHE_ONLY); //不从网络加载数据,只从缓存加载数据。
}

当我们加载Html时候,会在我们data/应用package下生成database与cache两个文件夹:
我们请求的Url记录是保存在webviewCache.db里,而url的内容是保存在webviewCache文件夹下.
WebView中存在着两种缓存:网页数据缓存(存储打开过的页面及资源)、H5缓存(即AppCache)。

网页数据缓存

WebSettings可设置缓存方式:

①LOAD_DEFAULT:默认设置,当有缓存而且没有过期使用缓存,否则使用网络数据。

②LOAD_CACHE_ELSE_NETWORK:只要有缓存就使用缓存,即使已经过期,否则使用网络数据。

③LOAD_NO_CACHE:不适用缓存,只加载网络数据。

④LOAD_CACHE_ONLY:不使用网络,只使用缓存数据。

⑤LOAD_CACHE_NORMAL: API level 17中已经废弃, 从API level 11开始作用同LOAD_DEFAULT模式

方法调用:

webSettings.setCacheMode(WebSettings.LOAD_NO_CACHE);

AppCache

查阅相关资料,总结如下:

①AppCache简介:对app内存缓存的方案,具体表现为当请求某个文件时不是从网络获取该文件,而是从本地获取。

②AppCache的好处:离线浏览 ,速度 - 已缓存资源加载得更快,减少服务器负载 - 浏览器将只从服务器下载更新过或更改过的资源。
在WebView中使用相关api
1、缓存构成
根据setAppCachePath(String appCachePath)提供的路径,在H5使用缓存过程中生成的缓存文件。
/data/data/package_name/cache/
/data/data/package_name/database/webview.db
/data/data/package_name/database/webviewCache.db
综合可以得知 webview 会将我们浏览过的网页url已经网页文件(css、图片、js等)保存到数据库表中

2、缓存模式
无模式选择,通过setAppCacheEnabled(boolean flag)设置是否打开。默认关闭,即,H5的缓存无法使用。

3、清除缓存
找到调用setAppCachePath(String appCachePath)设置缓存的路径,把它下面的文件全部删除就OK了。

4、控制大小
通过setAppCacheMaxSize(long appCacheMaxSize)设置缓存最大容量,默认为Max Integer。
同时,可能通过覆盖WebChromeClient.onReachedMaxAppCacheSize(long requiredStorage, long quota, WebStorage.QuotaUpdater quotaUpdater)来设置缓存超过先前设置的最大容量时的策略。

如:www.taobao.com的cache-control为no-cache,在模式LOAD_DEFAULT下,无论如何都会从网络上取数据,如果没有网络,就会出现错误页面;在LOAD_CACHE_ELSE_NETWORK模式下,无论是否有网络,只要本地有缓存,都使用缓存。本地没有缓存时才从网络上获取。
www.360.com.cn的cache-control为max-age=60,在两种模式下都使用本地缓存数据。

总结:根据以上两种模式,建议缓存策略为,判断是否有网络,有的话,使用LOAD_DEFAULT,无网络时,使用LOAD_CACHE_ELSE_NETWORK。

设置WebView 缓存模式

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
private void initWebView() {  

mWebView.getSettings().setJavaScriptEnabled(true);
mWebView.getSettings().setRenderPriority(RenderPriority.HIGH);
mWebView.getSettings().setCacheMode(WebSettings.LOAD_DEFAULT); //设置 缓存模式
// 开启 DOM storage API 功能
mWebView.getSettings().setDomStorageEnabled(true);
//开启 database storage API 功能
mWebView.getSettings().setDatabaseEnabled(true);
String cacheDirPath = getFilesDir().getAbsolutePath()+APP_CACAHE_DIRNAME;
// String cacheDirPath = getCacheDir().getAbsolutePath()+Constant.APP_DB_DIRNAME;
Log.i(TAG, "cacheDirPath="+cacheDirPath);
//设置数据库缓存路径
mWebView.getSettings().setDatabasePath(cacheDirPath);
//设置 Application Caches 缓存目录
mWebView.getSettings().setAppCachePath(cacheDirPath);
//开启 Application Caches 功能
mWebView.getSettings().setAppCacheEnabled(true);
}

清除缓存

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
/** 
* 清除WebView缓存
*/
public void clearWebViewCache(){

//清理Webview缓存数据库
try {
deleteDatabase("webview.db");
deleteDatabase("webviewCache.db");
} catch (Exception e) {
e.printStackTrace();
}

//WebView 缓存文件
File appCacheDir = new File(getFilesDir().getAbsolutePath()+APP_CACAHE_DIRNAME);
Log.e(TAG, "appCacheDir path="+appCacheDir.getAbsolutePath());

File webviewCacheDir = new File(getCacheDir().getAbsolutePath()+"/webviewCache");
Log.e(TAG, "webviewCacheDir path="+webviewCacheDir.getAbsolutePath());

//删除webview 缓存目录
if(webviewCacheDir.exists()){
deleteFile(webviewCacheDir);
}
//删除webview 缓存 缓存目录
if(appCacheDir.exists()){
deleteFile(appCacheDir);
}
}

完整代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
package com.example.webviewtest;  

import java.io.File;

import android.app.Activity;
import android.graphics.Bitmap;
import android.os.Bundle;
import android.util.Log;
import android.view.View;
import android.webkit.JsPromptResult;
import android.webkit.JsResult;
import android.webkit.WebChromeClient;
import android.webkit.WebSettings;
import android.webkit.WebSettings.RenderPriority;
import android.webkit.WebView;
import android.webkit.WebViewClient;
import android.widget.RelativeLayout;
import android.widget.TextView;
import android.widget.Toast;

public class MainActivity extends Activity {

private static final String TAG = MainActivity.class.getSimpleName();
private static final String APP_CACAHE_DIRNAME = "/webcache";
private TextView tv_topbar_title;
private RelativeLayout rl_loading;
private WebView mWebView;
private String url;

@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);

//url:http://m.dianhua.cn/detail/31ccb426119d3c9eaa794df686c58636121d38bc?apikey=jFaWGVHdFVhekZYWTBWV1ZHSkZOVlJWY&app=com.yulore.yellowsdk_ios&uid=355136051337627
url = "http://m.dianhua.cn/detail/31ccb426119d3c9eaa794df686c58636121d38bc?apikey=jFaWGVHdFVhekZYWTBWV1ZHSkZOVlJWY&app=com.yulore.yellowsdk_ios&uid=355136051337627";
findView();
}

private void findView() {

tv_topbar_title = (TextView) findViewById(R.id.tv_topbar_title);

rl_loading = (RelativeLayout) findViewById(R.id.rl_loading);

mWebView = (WebView) findViewById(R.id.mWebView);

initWebView();

mWebView.setWebViewClient(new WebViewClient() {

@Override
public void onLoadResource(WebView view, String url) {

Log.i(TAG, "onLoadResource url="+url);

super.onLoadResource(view, url);
}

@Override
public boolean shouldOverrideUrlLoading(WebView webview, String url) {

Log.i(TAG, "intercept url="+url);

webview.loadUrl(url);

return true;
}

@Override
public void onPageStarted(WebView view, String url, Bitmap favicon) {

Log.e(TAG, "onPageStarted");

rl_loading.setVisibility(View.VISIBLE); // 显示加载界面
}

@Override
public void onPageFinished(WebView view, String url) {

String title = view.getTitle();

Log.e(TAG, "onPageFinished WebView title=" + title);

tv_topbar_title.setText(title);
tv_topbar_title.setVisibility(View.VISIBLE);

rl_loading.setVisibility(View.GONE); // 隐藏加载界面
}

@Override
public void onReceivedError(WebView view, int errorCode, String description, String failingUrl) {

rl_loading.setVisibility(View.GONE); // 隐藏加载界面

Toast.makeText(getApplicationContext(), "",
Toast.LENGTH_LONG).show();
}
});

mWebView.setWebChromeClient(new WebChromeClient() {

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

Log.e(TAG, "onJsAlert " + message);

Toast.makeText(getApplicationContext(), message, Toast.LENGTH_SHORT).show();

result.confirm();

return true;
}

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

Log.e(TAG, "onJsConfirm " + message);

return super.onJsConfirm(view, url, message, result);
}

@Override
public boolean onJsPrompt(WebView view, String url, String message, String defaultValue, JsPromptResult result) {

Log.e(TAG, "onJsPrompt " + url);

return super.onJsPrompt(view, url, message, defaultValue, result);
}
});

mWebView.loadUrl(url);
}

private void initWebView() {

mWebView.getSettings().setJavaScriptEnabled(true);
mWebView.getSettings().setRenderPriority(RenderPriority.HIGH);
mWebView.getSettings().setCacheMode(WebSettings.LOAD_DEFAULT); //设置 缓存模式
// 开启 DOM storage API 功能
mWebView.getSettings().setDomStorageEnabled(true);
//开启 database storage API 功能
mWebView.getSettings().setDatabaseEnabled(true);
String cacheDirPath = getFilesDir().getAbsolutePath()+APP_CACAHE_DIRNAME;
// String cacheDirPath = getCacheDir().getAbsolutePath()+Constant.APP_DB_DIRNAME;
Log.i(TAG, "cacheDirPath="+cacheDirPath);
//设置数据库缓存路径
mWebView.getSettings().setDatabasePath(cacheDirPath);
//设置 Application Caches 缓存目录
mWebView.getSettings().setAppCachePath(cacheDirPath);
//开启 Application Caches 功能
mWebView.getSettings().setAppCacheEnabled(true);
}

/**
* 清除WebView缓存
*/
public void clearWebViewCache(){

//清理Webview缓存数据库
try {
deleteDatabase("webview.db");
deleteDatabase("webviewCache.db");
} catch (Exception e) {
e.printStackTrace();
}

//WebView 缓存文件
File appCacheDir = new File(getFilesDir().getAbsolutePath()+APP_CACAHE_DIRNAME);
Log.e(TAG, "appCacheDir path="+appCacheDir.getAbsolutePath());

File webviewCacheDir = new File(getCacheDir().getAbsolutePath()+"/webviewCache");
Log.e(TAG, "webviewCacheDir path="+webviewCacheDir.getAbsolutePath());

//删除webview 缓存目录
if(webviewCacheDir.exists()){
deleteFile(webviewCacheDir);
}
//删除webview 缓存 缓存目录
if(appCacheDir.exists()){
deleteFile(appCacheDir);
}
}

/**
* 递归删除 文件/文件夹
*
* @param file
*/
public void deleteFile(File file) {

Log.i(TAG, "delete file path=" + file.getAbsolutePath());

if (file.exists()) {
if (file.isFile()) {
file.delete();
} else if (file.isDirectory()) {
File files[] = file.listFiles();
for (int i = 0; i < files.length; i++) {
deleteFile(files[i]);
}
}
file.delete();
} else {
Log.e(TAG, "delete file no exists " + file.getAbsolutePath());
}
}

}

7、无法下载文件?

在自己写的 WebView 下是无法直接下载文件,需要自己监听下载事件并对下载的动作进行处理。

1
2
3
4
5
6
7
8
9
10
11
12
13
/**
* 当下载文件时打开系统自带的浏览器进行下载,当然也可以对捕获到的 url 进行处理在应用内下载。
**/
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);
}
}

8、无法打开文件选择器?

通过重写 WebChromeClient 来实现点击 来打开系统文件选择器。

一个完整的Activity示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
public class MainActivity extends AppCompatActivity {

/** Android 5.0以下版本的文件选择回调 */
protected ValueCallback<Uri> mFileUploadCallbackFirst;
/** Android 5.0及以上版本的文件选择回调 */
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)版本选择文件时会触发该隐藏方法
@SuppressWarnings("unused")
public void openFileChooser(ValueCallback<Uri> uploadMsg) {
openFileChooser(uploadMsg, null);
}

// Android 3.0 (API level 11)到 Android 4.0 (API level 15))版本选择文件时会触发,该方法为隐藏方法
public void openFileChooser(ValueCallback<Uri> uploadMsg, String acceptType) {
openFileChooser(uploadMsg, acceptType, null);
}

// Android 4.1 (API level 16) -- Android 4.3 (API level 18)版本选择文件时会触发,该方法为隐藏方法
@SuppressWarnings("unused")
public void openFileChooser(ValueCallback<Uri> uploadMsg, String acceptType, String capture) {
openFileInput(uploadMsg, null, false);
}

// Android 5.0 (API level 21)以上版本会触发该方法,该方法为公开方法
@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在不同系统版本下分别持有了
//WebView对象,在用户取消文件选择器的情况下,需给onReceiveValue传null返回值
//否则WebView在未收到返回值的情况下,无法进行任何操作,文件选择器会失效
if (mFileUploadCallbackFirst != null) {
mFileUploadCallbackFirst.onReceiveValue(null);
mFileUploadCallbackFirst = null;
}
else if (mFileUploadCallbackSecond != null) {
mFileUploadCallbackSecond.onReceiveValue(null);
mFileUploadCallbackSecond = null;
}
}
}
}

}

9、怎么为 WebView 的加载添加进度条

这里的 onPageFinished() 有个问题,不能在这里监听页面是否加载完毕(我自己测试的时候,好像在重定向和加载完 iframes 时都会调用这个方法)。

把页面加载完毕的判断放在 onProgressChanged() 里可能会更为准确。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
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);
}
});

10、怎样对页面进行 Js 注入?

首先你要在 WebView 开启 JavaScript,然后搭建桥梁

1
2
3
4
5
6
7
8
9
10
11
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的代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
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);
}
}

简单的说就是向网页注入一段 js, 在这段 js 里面设置回调到java中的方法 getResult(),由 WebAppBridge.getResult 来回收。
其中js的核心代码为:

1
oauth.getResult(str);

其中 oauth 这个名称要与 webView.addJavascriptInterface()方法的第二个参数一样。

具体的代码可以参考这个项目中写的 js 注入逻辑 OauthDialog
地址:https://github.com/cpacm/MoeMusic/blob/master/app/src/main/java/com/cpacm/moemusic/ui/widgets/dialogs/OauthDialog.java

需要获得 CookieManager 的对象并将 cookie 设置进去。

从服务器的返回头中取出 cookie 根据Http请求的客户端不同,获取 cookie 的方式也不同,请自行获取。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
/**
* 将cookie设置到 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 设置形式
* cookieManager.setCookie(url, "key=value;" + "domain=[your domain];path=/;")
**/
cookieManager.setCookie(url, cookie);
}

删除 Cookie 的方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
/**
* 这个两个在 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();
}
}

12、如何使 HTML5 video 在 WebView 全屏显示

当网页全屏播放视频时会调用 WebChromeClient.onShowCustomView() 方法,所以可以通过将 video 播放的视图全屏达到目的。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
@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;
}
}

但是很多的手机版本在网页视频播放时是不会调用这个方法的,所以这个方法局限性很大。

13、Android5.0上 WebView中Http和Https混合问题

1
2
3
4
5
6
7
8
/**
* MIXED_CONTENT_ALWAYS_ALLOW:允许从任何来源加载内容,即使起源是不安全的;
* MIXED_CONTENT_NEVER_ALLOW:不允许Https加载Http的内容,即不允许从安全的起源去加载一个不安全的资源;
* MIXED_CONTENT_COMPATIBILITY_MODE:当涉及到混合式内容时,WebView 会尝试去兼容最新Web浏览器的风格。
**/
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
webView.getSettings().setMixedContentMode(WebSettings.MIXED_CONTENT_ALWAYS_ALLOW);
}

14、如何避免 WebView 的内存泄露问题

  • 可以将 Webview 的 Activity 新起一个进程,结束的时候直接System.exit(0);退出当前进程;
  • 不在xml中定义 WebView,而是在代码中创建,使用 getApplicationgContext() 作为传递的 Conetext;
  • 在 Activity 销毁的时候,将 WebView 置空
1
2
3
4
5
6
7
8
9
10
11
@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();
}

总结

如果你踩到了 WebView 上的坑,请先默哀一分钟,然后努力找找解决方法吧,总会有人体验过你的悲剧,也会有人重蹈你的覆辙。
当然 WebView 里肯定不止我上面列出来的这些问题,如果你有更多的 WebView 问题解决方案欢迎评论交流。

坚持原创技术分享,您的支持将鼓励我继续创作!