目录
坑1:点击网页中的按钮无响应
场景:两个H5页面,H1,H2,H2里面有一个按钮与JS交互,第一种情况:先进入H1页面,返回后在进入H2页面,点击按钮无响应,第二种情况:先进如H2页面,点击按钮正常,返回进入H1,在重新进入H2,点击无反应。
针对这种情况,发现导致以上这两种情况的原因是WebView属性设置错误导致的。
webView.onResume(),//激活webview为活跃状态,能正常执行网页的响应
webView.onPause(),//当页面被失去焦点被切换到后台不可见时,需要执行onPause(),通知内核暂停所有的动作,比如DOM的解析、plugin的执行、JS的执行
webView.onpauseTimers(),//当应用程序(存在webview)被切换到后台时,这个方法不仅仅针对当前页面内的webview而是整个应用程序中所有页面的webview,它会暂停所有webview的layout,parsing,javascripttimer。降低CPU功耗
webView.resumeTimers(),//恢复pauseTimers状态
看了这几个属性的注释,恍然大悟,赶紧检查代码,果真如此,在H1页面的onPause()方法中设置了webView.onpauseTimers(),导致只要进入H1在返回就会调用这个属性,导致全局的WebView都无法响应。
坑2:WebView加载网页不显示图片
android4.4以后google对http与https的安全认证方式进行了严格的区分,WebView默认不支持同时加载Https和Http混合模式。
在android4.4以前比如在webview里面播放视频是没有问题,比如优酷,腾讯视频都是正常播放,但是某天测试跟你反应腾讯视频不能播放了,然后你就懵逼了,明明之前是一直正常的为什么现在不行了,而且还是某些视频不能播放,这才是最骚的。作为一个有经验的程序猿应该第一反应到是android版本的兼容问题,没错这是google在4.4版本以后对http与https进行了安全认证的区分,4.4以后默认不支持https这种方式,但是google是提供向下兼容的所以提供了三种模式来适应不同的场景:
1、MIXED_CONTENT_NEVER_ALLOW:Webview不允许一个安全的站点(https)去加载非安全的站点内容(http),比如,https网页内容的图片是http链接。强烈建议App使用这种模式,因为这样更安全。
2、MIXED_CONTENT_ALWAYS_ALLOW:在这种模式下,WebView是可以在一个安全的站点(Https)里加载非安全的站点内容(Http),这是WebView最不安全的操作模式,尽可能地不要使用这种模式。
3、MIXED_CONTENT_COMPATIBILITY_MODE:在这种模式下,当涉及到混合式内容时,WebView会尝试去兼容最新Web浏览器的风格。一些不安全的内容(Http)能被加载到一个安全的站点上(Https),而其他类型的内容将会被阻塞。这些内容的类型是被允许加载还是被阻塞可能会随着版本的不同而改变,并没有明确的定义。这种模式主要用于在App里面不能控制内容的渲染,但是又希望在一个安全的环境下运行。
webView加载https的网页时,如果网页中的图片引用是http,Android5.0以上的手机webView默认不允许混合模式,https当中不能加载http资源,所以需要手动设置开启。
if(Build.VERSION.SDK_INT>=Build.VERSION_CODES.LOLLIPOP){
webSettings.setMixedContentMode(WebSettings.MIXED_CONTENT_ALWAYS_ALLOW);
}
坑3:WebView加载网页中无法播放视频
在webView所在Activity的AndroidManifest.xml中添加属性android:hardwareAccelerated="true"
坑4:WebView打开多个H5页面,返回键无效
设置WebView的webViewClient时,重写此方法
mWebView.setWebViewClient(new InnerWebViewClient());
@Override
public boolean shouldOverrideUrlLoading(WebView view, String url) {
return false;
}
坑5:android4.4以后webview默认不保存cookie状态
很多时候我们会在webview上进行第三方登录之类的需求,比如qq,微博之类的,但是在4.4以后
你会发现qq或者微博登录会在qq上显示授权登录成功但是webview上面却显示未登录状态,会存在一直登录不
上的现象,但是在4.4以前是正常的,这个也是因为android4.4以后google为了安全性的要求默认不保存cookie状态,
google也提供了API可以设置在4.4以后进行cookie保存,代码设置如下:记得判断版本
if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP){
//android5.0以后webview默认不在保存cookie所以会导致第三方登录状态无法保存
CookieManager cookieManager = CookieManager.getInstance();
cookieManager.setAcceptThirdPartyCookies(webView,true);
}
坑6:WebView 加载https 白屏以及重定向加载
(转载自https://blog.csdn.net/android_freshman/article/details/79461671)
1.针对正常的webView 加载内核:
1-1. 启用mixed content
在Android5.0中,WebView方面做了些修改,如果你的系统target api为21以上:
系统默认禁止了mixed content和第三方cookie。可以使用setMixedContentMode() 和 setAcceptThirdPartyCookies()以分别启用。
系统现在可以智能选择HTML文档的portion来绘制。这种新特性可以减少内存footprint并改进性能。若要一次性渲染整个HTML文档,可以调用这个方法enableSlowWholeDocumentDraw()
如果你的app的target api低于21:系统允许mixed content和第三方cookie,并且总是一次性渲染整个HTML文档。
在使用WebView的类中添加如下代码:
// android 5.0以上默认不支持Mixed Content
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
webView.getSettings().setMixedContentMode(
WebSettings.MIXED_CONTENT_COMPATIBILITY_MODE);
}
1-2. 设置WebView接受所有网站的证书
在认证证书不被Android所接受的情况下,我们可以通过设置重写WebViewClient的onReceivedSslError方法在其中设置接受所有网站的证书来解决,具体代码如下:
//重写 onReceivedSslError 内的super 方法需要注释掉
mWebView.setWebViewClient(new WebViewClient(){
@Override
public void onPageFinished(WebView webView, String s) {
super.onPageFinished(webView, s);
Log.d(TAG,"setWebViewClient onPageFinished");
topTitle.setText(webView.getTitle());
closeLoadingAnimal();
}
@Override
public boolean shouldOverrideUrlLoading(WebView webView, String url) {
Log.d(TAG,"setWebViewClient shouldOverrideUrlLoading url="+url);
if (StringUtil.isEmpty(url)) {
return false;
}
dealUrl(url);
return true;
}
@Override
public void onReceivedSslError(WebView webView, SslErrorHandler sslErrorHandler, SslError sslError) {
Log.e(TAG,"onReceivedSslError sslError="+sslError.toString());
if(sslError.getPrimaryError() == android.net.http.SslError.SSL_INVALID ){// 校验过程遇到了bug
sslErrorHandler.proceed();
}else{
sslErrorHandler.cancel();
}
}
});
2.针对腾讯TBS x5内核:
2-1.同样需要启用 mixed content,对于 html 页面内有https 和 http 图片混合的相关的连接地址,同样适用
引用x5 内核的相关引用
settings.setMixedContentMode(WebSettings.LOAD_NORMAL);//处理http 和 https 图片混合的问题
2-2:设置WebView接受所有网站的证书 同上 1-2
3.处理重定向的问题:
注意:有些网页需要支持Dom 存储(可处理部分白屏的问题)
settings.setDomStorageEnabled(true);
按照API的说明 Sets whether the DOM storage API is enabled. The default value is false.
也就是是否开启本地DOM存储。应该是Html 5中的localStorage(可以使用Android4.4手机和Chrome Inspcet Device联调),用于持久化的本地存储,除非主动删除数据,否则数据是永远不会过期的,绝大多数的浏览器都是支持 localStorage 的,但是鉴于它的安全特性(任何人都能读取到它,尽管有相应的限制,将敏感数据存储在这里依然不是明智之举),Android 默认是关闭该功能的。
因为页面内部,有图片地址连接 ,股票连接,等等,所有对相关的地址进行了拦截,我犯的错误是 重定向后显示的新连接 我没有进行处理。所以显示一直只加载中(白屏)。
对于 拦截到的url ,进行不同的处理,如果遇到重定向的页面特殊处理,我这里是重新跳转新的页面
IntentUtils.toWebshell(BaseDetailsWebviewActivity.this, url);
如果是当前页面 webView.load(url);也可以,针对不同情况自行处
@Override
public boolean shouldOverrideUrlLoading(WebView view, String url) { // 网页超链点击处理
Log.e("baseWebDetail", "shouldOverrideUrlLoading url " + url+"\n");
if (url == null) {
return false;
}
//判断重定向的方式一
WebView.HitTestResult hitTestResult = view.getHitTestResult();
if (url.startsWith("tel") || url.startsWith("mailto") || url.endsWith(".apk") || url.endsWith(".download")) {
dealApplicationUrl(url);
} else if (url.startsWith("xxx://")) {
dealSchemaUrl(url);
} else if (url.startsWith("xxx://")) {
dealWebFontUrl(url);
} else if (url.startsWith("xxx://imgclick")) {
showBigImages(url);
} else if (url.contains("/stock/xxx/")) {
dealStockUrl(url);
} else {
isRedirect = false;
//重定向判断
if(hitTestResult.getType() == WebView.HitTestResult.UNKNOWN_TYPE) {
share.setVisibility(View.VISIBLE);
collection.setVisibility(View.VISIBLE);
mCommentLayout.setVisibility(View.GONE);
isRedirect = true;
return false;
}
IntentUtils.toWebshell(BaseDetailsWebviewActivity.this, url);
}
return true;
}
坑7:Webview无法正常唤起系统相册来选择图片
解决问题之前我们先来说说WebView
上传文件的逻辑:当我们在Web页面上点击选择文件的控件(<input type="file">
)时,会回调WebChromeClient
下的openFileChooser()
(5.0及以上系统回调onShowFileChooser()
)。这个时候我们在openFileChooser
方法中通过Intent
打开系统相册或者支持该Intent
的第三方应用来选择图片。like this:
public void openFileChooser(ValueCallback<Uri> valueCallback, String acceptType, String capture) {
uploadMessage = valueCallback;
openImageChooserActivity();
}
private void openImageChooserActivity() {
Intent i = new Intent(Intent.ACTION_GET_CONTENT);
i.addCategory(Intent.CATEGORY_OPENABLE);
i.setType("image/*");
startActivityForResult(Intent.createChooser(i,
"Image Chooser"), FILE_CHOOSER_RESULT_CODE);
}
最后我们在onActivityResult()
中将选择的图片内容通过ValueCallback
的onReceiveValue
方法返回给WebView
,然后通过js上传。
注:ValueCallbacks
是WebView
组件通过openFileChooser()
或者onShowFileChooser()
提供给我们的,它里面包含了一个或者一组Uri
,然后我们在onActivityResult()
里将Uri
传给ValueCallbacks
的onReceiveValue()
方法,这样WebView
就知道我们选择了什么文件。
低版本的回调方法是openFileChooser,5.0上将回调方法该为了onShowFileChooser
。所以为了解决这一问题,兼容各个版本,我们需要对openFileChooser()
进行重载,同时针对5.0及以上系统提供onShowFileChooser()
方法:
private ValueCallback<Uri> uploadMessage;
private ValueCallback<Uri[]> uploadMessageAboveL;
private final static int FILE_CHOOSER_RESULT_CODE = 10000;
。。。。。。。
webview.setWebChromeClient(new WebChromeClient() {
// For Android < 3.0
public void openFileChooser(ValueCallback<Uri> valueCallback) {
uploadMessage = valueCallback;
openImageChooserActivity();
}
// For Android >= 3.0
public void openFileChooser(ValueCallback valueCallback, String acceptType) {
uploadMessage = valueCallback;
openImageChooserActivity();
}
//For Android >= 4.1
public void openFileChooser(ValueCallback<Uri> valueCallback, String acceptType, String capture) {
uploadMessage = valueCallback;
openImageChooserActivity();
}
// For Android >= 5.0
@Override
public boolean onShowFileChooser(WebView webView, ValueCallback<Uri[]> filePathCallback, WebChromeClient.FileChooserParams fileChooserParams) {
uploadMessageAboveL = filePathCallback;
openImageChooserActivity();
return true;
}
});
private void openImageChooserActivity() {
Intent i = new Intent(Intent.ACTION_GET_CONTENT);
i.addCategory(Intent.CATEGORY_OPENABLE);
i.setType("image/*");
startActivityForResult(Intent.createChooser(i, "Image Chooser"), FILE_CHOOSER_RESULT_CODE);
}
onShowFileChooser()
中的ValueCallback
包含了一组Uri(Uri[])
,所以针对5.0及以上系统,我们还需要对onActivityResult()
做一点点处理。
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
super.onActivityResult(requestCode, resultCode, data);
if (requestCode == FILE_CHOOSER_RESULT_CODE) {
if (null == uploadMessage && null == uploadMessageAboveL) return;
Uri result = data == null || resultCode != RESULT_OK ? null : data.getData();
if (uploadMessageAboveL != null) {
onActivityResultAboveL(requestCode, resultCode, data);
} else if (uploadMessage != null) {
uploadMessage.onReceiveValue(result);
uploadMessage = null;
}
}
}
@TargetApi(Build.VERSION_CODES.LOLLIPOP)
private void onActivityResultAboveL(int requestCode, int resultCode, Intent intent) {
if (requestCode != FILE_CHOOSER_RESULT_CODE || uploadMessageAboveL == null)
return;
Uri[] results = null;
if (resultCode == Activity.RESULT_OK) {
if (intent != null) {
String dataString = intent.getDataString();
ClipData clipData = intent.getClipData();
if (clipData != null) {
results = new Uri[clipData.getItemCount()];
for (int i = 0; i < clipData.getItemCount(); i++) {
ClipData.Item item = clipData.getItemAt(i);
results[i] = item.getUri();
}
}
if (dataString != null)
results = new Uri[]{Uri.parse(dataString)};
}
}
uploadMessageAboveL.onReceiveValue(results);
uploadMessageAboveL = null;
}
注意:做完了这些别忘了混淆openFileChooser(),
否则release包还是无效
-keepclassmembers class * extends android.webkit.WebChromeClient{
public void openFileChooser(...);
}
坑8:WebView在可编辑状态下无法打开超链接
是由于设置了mSettings.setSupportMultipleWindows(true)是要在新窗口中打开网页;必须重写WebChromeClient的onCreateWindow方法。如果只在APP内部当前页直接打开,设置mSettings.setSupportMultipleWindows(false)就行了
setSupportMultipleWindows默认的是false,也就是说WebView默人不支持新窗口,但是这个不是说WebView不能打开多个页面了,只是你点击页面上的连接,当它的target属性是_blank时。它会在当前你所看到的页面继续加载那个连接。而不是重新打开一个窗口。
当你设置为true时,就代表你想要你的WebView支持多窗口,但是一旦设置为true,必须要重写WebChromeClient的onCreateWindow方法。
@Override
public boolean onCreateWindow(WebView view, boolean isDialog, boolean isUserGesture, Message resultMsg) {
}
view :请求新窗口的WebView
isDialog : 如果是true,代表这个新窗口只是个对话框,如果是false,则是一个整体的大小的窗口
isUserGesture :如果是true,代表这个请求是用户触发的,例如点击一个页面上的一个连接
resultMsg :当一个新的WebView被创建时这个只被传递给他,resultMsg.obj是一个WebViewTransport的对象,它被用来传送给新创建的WebView,使用方法:WebView.WebViewTransport.setWebView(WebView)
返回值:这个方法如果返回true,代表这个主机应用会创建一个新的窗口,否则应该返回fasle。如果你返回了false,但是依然发送resulMsg会导致一个未知的结果。
如果我们仅仅是将WebView嵌入我们自己的应用然后加载网页,很少有必要去设置支持多窗口。只在当前窗口加载新的网页就可。
下面是重写onCreateWindow的必要代码:
WebView.WebViewTransport transport = (WebView.WebViewTransport) msg.obj;
transport.setWebView(webview); //此webview可以是一般新创建的
msg.sendToTarget();