本篇继基本使用之后,来看看都有那些实战中的功能和问题,有帮助到你请赐小心心❤
Part 1 传送门 WebView 使用和方法
Part 2 传送门 常见功能 & 问题
常见功能 & 问题
1. 加载空白页
重写 onReceivedSslError()
方法
2. WebView 与父控件滑动冲突问题
比如在一个 ViewPager 中添加了 WebView,WebView 加载了一个可滑动的界面(查看图片滑动等),当网页滑动的时候,就直接切换了另一个 Tab。就可以用 onOverScrolled
方法处理。
当 WebView 滑动时会触发 overScrollBy()
方法,在 overScrollBy()
方法里面会调用 onOverScrolled()
方法。
其中,前两个参数是 距离原点的 x 和 y 的距离,后两个是 当 View 滑动到 两侧 或 上下 时为 true
当点击的时候允许 父控件(ViewPager)滑动,当滑动到边界的时候禁止父控件滑动。
/**
* Called by {@link #overScrollBy(int, int, int, int, int, int, int, int, boolean)} to
* respond to the results of an over-scroll operation.
*
* @param scrollX New X scroll value in pixels
* @param scrollY New Y scroll value in pixels
* @param clampedX True if scrollX was clamped to an over-scroll boundary
* @param clampedY True if scrollY was clamped to an over-scroll boundary
*/
protected void onOverScrolled(int scrollX, int scrollY, boolean clampedX, boolean clampedY) {
if (clampedX) { // 滑动到两侧 允许 ViewPager 滑动
requestDisallowInterceptTouchEvent(false);
}
super.onOverScrolled(scrollX, scrollY, clampedX, clampedY);
}
@Override
public boolean onTouchEvent(MotionEvent event) {
super.onTouchEvent(event);
if (event.getAction() == MotionEvent.ACTION_DOWN) {
//disables ViewPager when user presses down
requestDisallowInterceptTouchEvent(true);
return true;
}
return true;
}
3. WebView 注入 Js 代码,实现功能
实现点击图片滑动查看
在信息流当中经常穿插着一些网页界面,提供更丰富的内容,如何能让 H5 的界面里的图片,向原生开发一样可以产生图片集并且左右滑动呢?
思路
提取网页中的
img
标签,形成图片集-
为每个图片添加点击事件
Js 方法如下,可以看到里面也调用了原生方法,用来设置 图片集 和 打开查看图片界面,其中图片链接用 (;)分号隔开
function pic(){
var imgList = "";
var imgs = document.getElementsByTagName("img");
for(var i = 0;i < imgs.length; i++){
var img = imgs[i];
imgList = imgList + img.src +";";
img.onclick = function(){
window.android.goToGallery(this.src);
}
}
window.android.setImgUrls(imgList);
}
但是要通过 WebView 把这个代码段注入进去,注意这里要把代码转化成字符串,有些符号需要转义处理。Android 也要处理 setImgUrls()
和 goToGallery()
方法
private void addJs() {
mWebVew.loadUrl("javascript:(" + getImageJS()+")()");
}
private String getImageJS(){
return
"function pic(){"
+" var imgList = \"\";"
+" var imgs = document.getElementsByTagName(\"img\");"
+" for(var i = 0;i < imgs.length; i++){"
+" var img = imgs[i];"
+" imgList = imgList + img.src + \";\";"
+" img.onclick = function(){"
+" window." + JS_NAME + ".goToGallery(this.src);"
+" }"
+" }"
+" window." + JS_NAME + ".setImgUrls(imgList);"
+"}";
}
@JavascriptInterface
public void goToGallery(String url) {
// 传到展示图片的viewPager mUrls.indexOf(url) 可以获取当前图片的 index
}
@JavascriptInterface
public void setImgUrls(String imgLists) {
String[] urls = imgLists.split(";");//url拼接成的字符串,有分号隔开
for (String url : urls) {
mUrls.add(url);
}
}
H5 链接重新处理
除了上面有图片需求,有时候H5自己带的连接也需要重新处理,比如跳转到 App 的界面
思路:获取 <a>
标签,为连接标签重新设置点击方式,同时原来的链接点击取消掉,参考代码如下:
function link(){
var href = document.getElementsByTagName("a");
for (var i = 0; i < href.length; i++) {
var a = href[i];
a.onclick = function(){
window.andorid.toWebView(this.value);
};
a.value = a.href;
a.href = "javascript:void(0)";
}
}
4. 音视频播放问题
当网页支持播放时,退出会发现声音会继续播放
有很多帖子给出方案是在 onPause 方法暂停 和 在 onResume 恢复
protected void onPause() {
if(webView != null){//暂停WebView
webView.onPause();
webView.pauseTimers();
}
super.onPause();
}
@Override
protected void onResume() {
if(webView != null){//恢复WebView
webView.onResume();
webView.resumeTimers();
}
super.onResume();
}
在实战过程中,由于我是在一个ViewPager 嵌入一个 含有 WebView 的 Fragment ,当切换 tab 的时候 是不会触发 onResume 的, 所以采用如下方案,下面用到了 reload()
方法,因为 直接用 WebView 的 resume 之类的也没有啥作用。
@Override
public void setUserVisibleHint(boolean isVisibleToUser) {
super.setUserVisibleHint(isVisibleToUser);
if (isPause && isVisibleToUser){ // (onResume) onPause 后不 reload
isPause = false;
return;
}
if (mWebView != null && !isPause && !isVisibleToUser) { // 切出时 调用 reload 停止播放
mWebView.reload();
}
}
@Override
public void onPause() {
super.onPause();
isPause = true;
}
5. 视频全屏功能
如果 WebView 含有视频资源,如果要点击视频可以全屏播放可以通过处理 WebChromeClient 中的两个方法来实现
@Override
public void onHideCustomView() {
//退出全屏
}
@Override
public void onShowCustomView(View view, CustomViewCallback callback) {
// 进入全屏
// 当网页中有视频的时候,点击全屏按键会调用这个方法,可设置网页播放全屏
}
private View mCustomView;
private ViewGroup mRootViewGroup;
@Override
public void onShowCustomView(View view, CustomViewCallback callback) {
// 进入全屏
if (mCustomView != null) {
return;
}
mCustomView = view;
mCustomView.setLayoutParams(new WindowManager.LayoutParams(
WindowManager.LayoutParams.MATCH_PARENT,
WindowManager.LayoutParams.MATCH_PARENT));
// 或者 getActivity().getWindow().getDecorView(); 获取根视图并addView
mRootViewGroup = getActivity().findViewById(android.R.id.content); 获取根视图并addView
mRootViewGroup .addView(mCustomView); // 添加到根视图
if (getActivity() != null) {
//设置横屏
getActivity().setRequestedOrientation(SCREEN_ORIENTATION_LANDSCAPE);
//设置全屏
getActivity().getWindow().setFlags(
WindowManager.LayoutParams.FLAG_FULLSCREEN,
WindowManager.LayoutParams.FLAG_FULLSCREEN);
}
}
@Override
public void onHideCustomView() {
//退出全屏
if (mCustomView == null) {
return;
}
//移除全屏视图并隐藏
mRootViewGroup.removeView(mCustomView);
mCustomView = null;
if (getActivity() != null) {
//设置竖屏
getActivity().setRequestedOrientation(SCREEN_ORIENTATION_PORTRAIT);
//清除全屏
getActivity().getWindow().clearFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN);
}
}
注意:在使用上面方法处理的时候,还需要考虑物理返回键。
有的视频容器按键异常,比如不可点击之类的,可以添加如下设置:
settings.setMediaPlaybackRequiresUserGesture(false);//SDK>18 是否支持手势控制网页媒体,比如视频的全屏
6. WebView 软键盘遮挡问题
如果通过 H5 可能做些互动功能,比如评论之类,此时会调用软键盘,会发现软键盘遮挡会遮挡输入框(这个问题很眼熟,在正常页面有时也会遮挡,真是问题不分控件)
有很多大神给的方案是创建一个 KeyBoardListener 类来处理,在我使用的项目中会在 ViewPager 中添加WebView 所以就需要改写一下,需要处理上面 tab的空间,参考方法类如下:
使用方式
KeyBoardListener mKeyBoardListener = KeyBoardListener.getInstance(getActivity(), height);
mKeyBoardListener.addListener();
KeyBoardListener.java
package cn.ninebot.webview;
import android.app.Activity;
import android.graphics.Rect;
import android.os.Build;
import android.view.View;
import android.view.ViewTreeObserver;
import android.widget.FrameLayout;
import cn.ninebot.commonlibs.utils.DisplayUtils;
import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_PORTRAIT;
public class KeyBoardListener {
private Activity activity;
private View mChildOfContent;
private int usableHeightPrevious;
private FrameLayout.LayoutParams frameLayoutParams;
private static KeyBoardListener keyBoardListener;
private static int mHeight = 0;
private static int mStatusBarHeight = 0;
private int contentHeight;
private boolean isFirst = true;
private boolean isAddListener = false;
/**
* 这个传进来的 height 是 tablayout 控件的高度, 因为 tablayout 被键盘弹起来,
* 可以传入这个值对其进行操作
*/
public static KeyBoardListener getInstance(Activity activity, int height) {
keyBoardListener = new KeyBoardListener(activity);
mHeight = height;
if (height != 0) {
mStatusBarHeight = DisplayUtils.getStatusBarHeight();
}
return keyBoardListener;
}
public KeyBoardListener(Activity activity) {
super();
this.activity = activity;
init();
}
private void init() {
FrameLayout content = (FrameLayout) activity.findViewById(android.R.id.content);
mChildOfContent = content.getChildAt(0);
frameLayoutParams = (FrameLayout.LayoutParams) mChildOfContent.getLayoutParams();
}
public void addListener() {
if (!isAddListener) {
mChildOfContent.getViewTreeObserver().addOnGlobalLayoutListener(mGlobalLayoutListener);
isAddListener = true;
isFirst = true;
}
}
public void removeListener() {
if (mChildOfContent != null && isAddListener) {
mChildOfContent.getViewTreeObserver().removeOnGlobalLayoutListener(mGlobalLayoutListener);
isAddListener = false;
}
}
ViewTreeObserver.OnGlobalLayoutListener mGlobalLayoutListener = new ViewTreeObserver.OnGlobalLayoutListener() {
public void onGlobalLayout() {
if (isFirst) {
contentHeight = mChildOfContent.getHeight();
isFirst = false;
}
possiblyResizeChildOfContent();
}
};
//重新调整跟布局的高度
private void possiblyResizeChildOfContent() {
if (activity.getRequestedOrientation() == SCREEN_ORIENTATION_PORTRAIT) { //竖屏
int usableHeightNow = computeUsableHeight();
//当前可见高度和上一次可见高度不一致 布局变动
if (usableHeightNow != usableHeightPrevious) {
int usableHeightSansKeyboard = mChildOfContent.getRootView().getHeight();
int heightDifference = usableHeightSansKeyboard - usableHeightNow;
if (heightDifference > (usableHeightSansKeyboard / 4)) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
// keyboard probably just became visible
frameLayoutParams.height = usableHeightSansKeyboard - heightDifference + mStatusBarHeight + mHeight;
} else {
frameLayoutParams.height = usableHeightSansKeyboard - heightDifference;
}
} else {
// keyboard probably just became hidden
frameLayoutParams.height = contentHeight;
}
mChildOfContent.requestLayout();
usableHeightPrevious = usableHeightNow;
}
}
}
private int computeUsableHeight() {
Rect r = new Rect();
mChildOfContent.getWindowVisibleDisplayFrame(r);
return (r.bottom - r.top);
}
}
7. WebView 安全警告
在使用 WebView 加载网页时,有时候会出现警告:您要访问的网站包含有害应用。
这是 WebView 的安全浏览保护策略,在 Android 8.0(API Level 26)开始的默认策略,被应用在所有 App 的 WebView 当中。
Google 会自己维护一套“不安全”网站的列表,并通过 Google Play 服务,同步到所有的设备上。当你要访问某些被标记为“不安全”的网站时,它就会以此“红屏”警告用户。
注意这是默认策略,虽然出发点是为了保护用户,但是有时候我们自己的 App 还是要有自主管控的权利。
在 Android 8.0(API Level 26)中,可以通过以下两种方法关闭安全策略:
方法一:
<manifest>
<meta-data
android:name="android.webkit.WebView.EnableSafeBrowsing"
android:value="false" />
<application> ... </application>
</manifest>
方法二:
WebView 的安全策略是默认开始的,如果想要关闭它,需要通过 WebSettings 这个类,其中有 setSafeBrowsingEnabled(boolean)
方法,可以用于设置是否开启安全模式。
settings.setSafeBrowsingEnabled(false);// 是否开启安全模式
此方法是一种全局的策略,也就是要么开启、要么关闭。
相关参考:
Android Developer 管理 WebView 对象
Android webview(安全策略) 出现 您要访问的网站包含有害应用
WebView 监听下载
WebView 是可以监听网页的下载链接,但是默认不会开启,所以点击不会有动作,可以使用系统 DownloadListener 可以监听下载。
webView.setDownloadListener(new DownloadListener() {
@Override
public void onDownloadStart(String url, String userAgent, String contentDisposition, String mimetype, long contentLength) {
// url 你要访问的下载链接
// userAgent 是HTTP请求头部用来标识客户端信息的字符串
// contentDisposition 为保存文件提供一个默认的文件名
// mimetype 该资源的媒体类型
// contentLength 该资源的大小
// 这几个参数都是可以通过抓包获取的
// 用手机默认浏览器打开链接
Uri uri = Uri.parse(url);
Intent intent = new Intent(Intent.ACTION_VIEW, uri);
startActivity(intent);
}
});
Android P 以上 进入WebView可能 crash
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
boolean isMainProcess = getApplicationContext().getPackageName().equals(getCurrentProcessName());
if (!isMainProcess) {
WebView.setDataDirectorySuffix("any-folder-name");
}
}
Admob banner ad not loading in android P
Vivo 设备,Android 5.1报错
部分log
Caused by: android.content.res.Resources$NotFoundException: String resource ID #0x2040003
at android.content.res.Resources.getText(Resources.java:318)
at android.content.res.VivoResources.getText(VivoResources.java:123)
at android.content.res.Resources.getString(Resources.java:404)
fixed
public class LollipopFixedWebView extends WebView {
public LollipopFixedWebView(Context context) {
super(getFixedContext(context));
}
public LollipopFixedWebView(Context context, AttributeSet attrs) {
super(getFixedContext(context), attrs);
}
public LollipopFixedWebView(Context context, AttributeSet attrs, int defStyleAttr) {
super(getFixedContext(context), attrs, defStyleAttr);
}
@TargetApi(Build.VERSION_CODES.LOLLIPOP)
public LollipopFixedWebView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
super(getFixedContext(context), attrs, defStyleAttr, defStyleRes);
}
public LollipopFixedWebView(Context context, AttributeSet attrs, int defStyleAttr, boolean privateBrowsing) {
super(getFixedContext(context), attrs, defStyleAttr, privateBrowsing);
}
public static Context getFixedContext(Context context) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP && Build.VERSION.SDK_INT < Build.VERSION_CODES.M) // Android Lollipop 5.0 & 5.1
return context.createConfigurationContext(new Configuration());
return context;
}
}
Webview 无法打开谷歌文档短链接
Set WebViewClient to webview
mywebview.webViewClient = object : WebViewClient() {
override fun shouldOverrideUrlLoading(webView: WebView, url: String): Boolean {
if (url.startsWith("intent://")) {
val intent = Intent.parseUri(url, Intent.URI_INTENT_SCHEME)
if (intent != null) {
val fallbackUrl = intent.getStringExtra("browser_fallback_url")
return if (fallbackUrl != null) {
webView.loadUrl(fallbackUrl)
true
} else {
false
}
}
}
return false
}
}
参考:Google Forum short URL not working in Android app webview.