• 首页 首页 icon
  • 工具库 工具库 icon
    • IP查询 IP查询 icon
  • 内容库 内容库 icon
    • 快讯库 快讯库 icon
    • 精品库 精品库 icon
    • 问答库 问答库 icon
  • 更多 更多 icon
    • 服务条款 服务条款 icon

【Android】 WebView 常用功能及解决方案集合

武飞扬头像
juejin
帮助235

1. 加载空白页

重写 onReceivedSslError() 方法

比如处理 https 请求时,webView 默认时不处理 https 请求的,页面显示空白。

webView.setWebViewClient(new WebViewClient() {    
        @Override    
        public void onReceivedSslError(WebView view, SslErrorHandler handler, SslError error) {    
            handler.proceed();    //表示等待证书响应
        // handler.cancel();      //表示挂起连接,为默认方式
        // handler.handleMessage(null);    //可做其他处理
        }    
    });

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;
}

参考方案:Android中WebView和父控件滑动冲突

3. WebView 注入 Js 代码,实现功能

在直接加载 html 内容的时,有时候缺少一些交互,那么可以通过 WebView 注入 JS 代码的方式实现功能。以下举了几个例子。

3.1 实现点击图片滑动查看

在信息流当中经常穿插着一些网页界面,提供更丰富的内容,如何能让 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);
    }
}

3.2 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 加载网页时,有时候会出现警告:您要访问的网站包含有害应用。

image.png

这是 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);// 是否开启安全模式

此方法是一种全局的策略,也就是要么开启、要么关闭。

相关参考:

8. 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);
    }
});

9. Android P 以上 进入WebView可能 crash

在 Android P 上应用 WebView 可能会出现如下错误

Caused by: java.lang.RuntimeException: Using WebView from more than one process at once with the same data directory is not supported

因为Android P 行为变更,多进程 webView 不能使用同一个目录,需要为不同进程 webView 设置不同目录。为不同进程设置不同的目录即可解决问题

 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

这篇好文章是转载于:学新通技术网

  • 版权申明: 本站部分内容来自互联网,仅供学习及演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系,请提供相关证据及您的身份证明,我们将在收到邮件后48小时内删除。
  • 本站站名: 学新通技术网
  • 本文地址: /boutique/detail/tanejic
系列文章
更多 icon
同类精品
更多 icon
继续加载