Android中WebView的使用指南:

参考文献:
Carson_Ho的Android:这是一份全面 & 详细的Webview使用攻略

本文知识点:

  • WebView的介绍
  • WebView的基本使用
  • WebView的一些开发中常用的API
  • WebView中一些常见的案例分析及实现
这里盗用Carson的一张图片,如果觉得不妥,及时告知马上删除

1.WebView的介绍

相信做Android的基本上都用过WebView,不论是加载网页还是处理本地内容,基本上都或多或少的使用过WebView。那么WebView能为我们带来什么呢?其实就相当于一个内置的浏览器,我们可以使用它完成一下操作:

1.WebView可以加载网页
2.WebView可以加载html片段
3.WebView可以加载本地html
4.WebView与原生App进行交互
以上种种都是WebView能帮我们完成的内容,所以现在有很多App就采用混合开发。这样使得我们使用WebView的场景越来越多,所以这里总结一下关于WebView的一些内容。

2.WebView的基本使用

其实如果你只用WebView加载一个网页的话,还是很好上手的。基本上就三步:

  • 在AndroidManifest注册网络权限:这个是必须的,切记切记!!!
 <uses-permission android:name="android.permission.INTERNET" />
  • 加载相应的网页资源
webView.loadUrl(url);

但是这里虽然加载的是一个url,但是你会发现他会直接蹦到系统自带的浏览器中去。作为强大的开发人员,怎么处理呢?当然是有办法的了。。。

  • 设置相应的本APP内打开的方法;
        webView.setWebViewClient(new WebViewClient() {
            @RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)
            @Override
            public boolean shouldOverrideUrlLoading(WebView view, WebResourceRequest request) {
                view.loadUrl(String.valueOf(request.getUrl()));
                return true;
            }

            @Override
            public boolean shouldOverrideUrlLoading(WebView view, String url) {
                view.loadUrl(url);
                return true;
            }
        });

上面的代码有几点需要说明一下:

  • shouldOverrideUrlLoading(WebView view, WebResourceRequest request) 这个方法是在5.0以上的版本上使用的,而shouldOverrideUrlLoading(WebView view, String url)这个方法是在5.0以下使用的,有了这两个方法,就能在自己的APP中打开相应的网页链接了。。。

上面就是WebView的简单实用,基本上就是展示一个网页,没有相应的交互问题。

3.WebView一些开中常用的API

加载类:

  • loadUrl(string url) 加载url地址的方法,参数就是一个Url地址;这里有一个点需要注意一下,这个方法加载的地址可以是
    • 网址 如 -> http://www.baidu.com
    • 手机本地html地址 如-> file:///android_asset/test.html
    • app包中assect目录下的html文件 如 -> content://com.android.htmlfileprovider/sdcard/test.html
  • loadData(String data, String mimeType, String encoding) 加载html片段
    • 参数1:html片段
    • 参数2:类型
    • 参数3:编码格式

状态类

  • onResume() 当webView为活跃状态的时候回调此方法(获取焦点)
  • onPause() 切换到后台的时候回调此方法(失去焦点)

其实上面这两个方法是和生命周期匹配的两个方法

  • pauseTimers() 全局范围切换到后台降低cpu功耗时回调的方法
  • resumeTimers() 恢复正常状态的时候回调的方法

上面这两个方法,是为了提高自己APP的性能的方法,主要是在生命周期的方法中调用的,也就是说在Activity的onResume()和onPause()中调用的(个人感觉啊。。。)

  • destroy() 销毁WebView的方法(主要是在音视频的时候,在这里释放相应的WebView,这里也是对应相应生命周期的方法的,但是这里注意一点,在销毁前必须先移除WebView,因为WebView会持有相应Activity的上下文引用所以这里要使用其父布局调用相应的removeView(View view)的方法进行移除)

操作网页类

  • canGoBack() 是否可以后退
  • goBack() 回退
  • canGoForward() 是否可以前进
  • goForward() 前进网页
  • **goBackOrForward(intsteps) ** 前进或者后退指定的位置,正数为前进/负数为后退

这里面涉及到的主要内容就是操作网页的前进和后退,主要应用场景是什么呢?试想一下,有的WebView是嵌到APP中的,一般这样的网页都是没有标题的,标题的交互留给APP处理返回的问题,就会用到上面相应的API了,基本上都是处理相应的返回问题

    @Override
    public boolean onKeyDown(int keyCode, KeyEvent event) {
        if ((keyCode == KEYCODE_BACK) && mWb.canGoBack()) {
            mWb.goBack();
            return true;
        }
        return super.onKeyDown(keyCode, event);
    }
    
    @Override
    public void onBackPressed() {
        if (mWb.canGoBack()) {
            mWb.goBack();
        } else {
            super.onBackPressed();
        }
    }

上面两段代码是一样的,但是这里有一个很重要的问题需要注意一下,很重要啊:
有些网页存在重定向的时候,那么canGoBack()返回的会一直是true,我这里使用的是百度的链接,因为百度重定向了,所以一直返回的是true,如果这里要是自己公司开发的Url的话,应该不会出现这种情况!这里提供一种解决办法

    @Override
    public void goBack() {
        WebBackForwardList mWebBackForwardList = copyBackForwardList();
        // 判断当前历史列表是否最顶端,其实canGoBack已经判断过
        if (mWebBackForwardList.getCurrentIndex() > 0) {
            // 获取历史列表
            String historyUrl = mWebBackForwardList.getItemAtIndex(
                    mWebBackForwardList.getCurrentIndex() - 1).getUrl();
            if (historyUrl.equals("https://www.baidu.com/")) {
                //回到首页了
                Activity activity = (Activity) mContext;
                activity.finish();
            }
        }
        super.goBack();
    }

上面的代码就是拷贝出所有前进的页面,然后判断因为这里重定向了,所以你getUrl()获取的网址永远是重定向那个,所以这里我就想直接把网址写死的话就能处理了,但是我觉得我的解决办法比较笨,还请大神们指教!!!

缓存类

  • clearCache(true) 清除WebView产生的所有缓存
  • clearHistory() 清除当前WebView访问的历史数据
  • clearFormData() 清除表单数据

WebSettings类

这个类是对WebView进行管理和设置的

  • getSettings() 获取WebSettings的
  • setJavaScriptEnabled(boolean flag); 是否可以和js进行交互
  • setPluginState(PluginState state) 是否支持插件,这里面传递的是一个枚举对象
    • WebSettings.PluginState.OFF 不支持
    • WebSettings.PluginState.ON 支持
  • setUseWideViewPort(boolean use) 是否将图片调整到WebView的大小
  • setLoadWithOverviewMode(boolean overview) 是否将WebView调整到屏幕大小

上面两个API一起使用!切记,因为只有这样才能让界面看上去不那么丑!

  • setSupportZoom(boolean support)支持缩放,默认为true。是下面这个API的前提。

  • setBuiltInZoomControls(boolean enabled) 设置内置的缩放控件。若为false,则该WebView不可缩放

  • setDisplayZoomControls(boolean enabled) 隐藏原生的缩放控件,这里可以自己在页面中实现。

  • setCacheMode(@CacheMode int mode) 设置缓存的模式

    • WebSettings.LOAD_DEFAULT 默认的模式 根据cache-control决定是否从网络上取数据。
    • WebSettings.LOAD_CACHE_ELSE_NETWORK 只要本地有,无论是否过期,或者no-cache,都使用缓存中的数据
    • WebSettings.LOAD_NO_CACHE 不使用缓存
    • WebSettings.LOAD_CACHE_ONLY 不使用网络,只加载缓存
  • setAllowFileAccess(boolean allow) 设置可以访问app中assect中的文件

  • setJavaScriptCanOpenWindowsAutomatically(boolean flag) 支持通过JS打开新窗口

  • setLoadsImagesAutomatically(boolean flag) 支持自动加载图片

  • setDefaultTextEncodingName(String encoding) 设置编码格式

  • setRenderPriority(RenderPriority priority) 设置渲染的优先级,这里传入的是一个枚举

    • NORMAL 正常
    • HIGH 高
    • LOW 低

WebViewClient类

处理各种通知和请求事件的控制类

  • shouldOverrideUrlLoading(WebView view, WebResourceRequest request)
  • shouldOverrideUrlLoading(WebView view, String url)

下面这个方法已经过时了,最新的是上面的那个方法,对于相应的url可以通过WebResourceRequest进行获取。重写这两个方法自己处理的话,可以不使用相应的浏览器进行打开,在本APP中打开,这也是一开始的时候为什么要重写这个方法。

  • onPageStarted(WebView view, String url, Bitmap favicon) 加载WebView开始的时候回调的方法,一般都在这里添加相应的Loadding,与用户交互,告诉用户页面还没有加载完成;
  • onPageFinished(WebView view, String url) WebView加载完成的时候回调的方法和上面那个方法是相呼应的,可以在这里关闭相应的对话框!

这里有一个问题注意一下,还是重定向的问题。如果这个网页重定向了,那么上面这两个方法会被多次调用!

  • onLoadResource(WebView view, String url) 这个方法会在每次加载资源的时候回调,如果图片很多的话,会被调用多次。后面这个url代表的是相应加载的地址。

  • onReceivedError(WebView view, int errorCode,String description, String failingUrl)

  • onReceivedError(WebView view, WebResourceRequest request, WebResourceError error)

上面这个方法已经过时了,可以根据相应的code进行判断。后面这个方法主要是通过WebResourceError的错误编码进行相应的判断,处理相应的错误!这个方法的主要作用是加载错误的回调。

  • onReceivedError(WebView view, int errorCode,String description, String failingUrl)
  • onReceivedError(WebView view, WebResourceRequest request, WebResourceError error)

这两个方法是处理相应的https的网络请求的(webView默认是不处理https请求的,页面会显示空白,使用如上面的方法进行处理)

            @Override
            public void onReceivedSslError(WebView view, SslErrorHandler handler, SslError error) {
                super.onReceivedSslError(view, handler, error);
                handler.proceed();
            }

            // 特别注意:5.1以上默认禁止了https和http混用,以下方式是开启
            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
                mWebView.getSettings().setMixedContentMode(WebSettings.MIXED_CONTENT_ALWAYS_ALLOW);
            }

WebChromeClient类

如果你对控制类没有那么多要求的话,使用上面的控制类就可以了,但是如果你要求比较高的话,就要用到下面这个控制类了

  • onProgressChanged(WebView view, int newProgress) 这个回调会实时的返回网页加载的总进度,最大值是100,可以根据当前值显示进度。
  • onReceivedTitle(WebView view, String title) 获取当前加载网页的标题
  • onJsAlert(WebView view, String url, String message, JsResult result) 如果这里返回true,那么js中弹出的警告对话框就由客户端处理;
  • onJsAlert(WebView view, String url, String message, JsResult result) 如果这里返回true,那么js中弹出的确认对话框就由客户端处理;
  • **onJsPrompt(WebView view, String url, String message, String defaultValue, JsPromptResult result) ** 如果这里返回true,那么js中弹出的提示对话框将由客户端进行处理。

4.WebView中一些常见的案例分析及实现

WebView显示进度

由很多APP中由都嵌入WebView,不知道大家仔细看过没有,有的WebView在加载的时候顶部由一个进度条,提示用户加载的进度,其实很好实现,这里就带大家实现一下:

效果图
  • 页面搭建
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical"
    tools:context="com.example.demo.MainActivity">

    <ProgressBar
        android:id="@+id/pb_progress"
        style="@style/Widget.AppCompat.ProgressBar.Horizontal"
        android:layout_width="match_parent"
        android:layout_height="5dp"
        android:background="@drawable/progress_bg" />

    <WebView
        android:id="@+id/wb"
        android:layout_width="match_parent"
        android:layout_height="match_parent" />
</LinearLayout>

这里就不讲解ProgressBar实现水平进度条了,百度一大堆!!!

  • 代码实现
    private java.lang.String mUrl = "https://www.baidu.com/";
    private WebView mWb;
    private ProgressBar progressBar;

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

        mWb = findViewById(R.id.wb);

        progressBar = findViewById(R.id.pb_progress);
        progressBar.setMax(100);

        setWebView(mWb);
    }

    @SuppressLint("SetJavaScriptEnabled")
    private void setWebView(WebView wb) {
        WebSettings settings = wb.getSettings();
        settings.setJavaScriptEnabled(true);
        settings.setPluginState(WebSettings.PluginState.ON);
        settings.setUseWideViewPort(true);
        settings.setLoadWithOverviewMode(true);
        settings.setSupportZoom(true);

        wb.loadUrl(mUrl);
        wb.setWebViewClient(new WebViewClient() {
            @RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)
            @Override
            public boolean shouldOverrideUrlLoading(WebView view, WebResourceRequest request) {
                view.loadUrl(String.valueOf(request.getUrl()));
                return true;
            }

            @Override
            public boolean shouldOverrideUrlLoading(WebView view, String url) {
                view.loadUrl(url);
                return true;
            }
        });

        wb.setWebChromeClient(new WebChromeClient() {
            @Override
            public void onProgressChanged(WebView view, int newProgress) {
                super.onProgressChanged(view, newProgress);
                progressBar.setProgress(newProgress);
            }
        });
    }

主要代码就这么多就能,这里设置了一些其他的属性,上面都讲解了,这里就不去说了。。。

WebView加载错误显示缺省页面

private java.lang.String mUrl = "https://google.com/";
    private WebView mWb;
    private ProgressBar progressBar;
    private java.lang.String mErrorUrl = "file:///android_asset/test.html";

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

        mWb = findViewById(R.id.wb);

        progressBar = findViewById(R.id.pb_progress);
        progressBar.setMax(100);

        setWebView(mWb);
    }

    @SuppressLint("SetJavaScriptEnabled")
    private void setWebView(WebView wb) {
        WebSettings settings = wb.getSettings();
        settings.setJavaScriptEnabled(true);
        settings.setPluginState(WebSettings.PluginState.ON);
        settings.setUseWideViewPort(true);
        settings.setLoadWithOverviewMode(true);
        settings.setSupportZoom(true);

        wb.loadUrl(mUrl);
        wb.setWebViewClient(new WebViewClient() {
            @RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)
            @Override
            public boolean shouldOverrideUrlLoading(WebView view, WebResourceRequest request) {
                view.loadUrl(String.valueOf(request.getUrl()));
                return true;
            }

            @Override
            public boolean shouldOverrideUrlLoading(WebView view, String url) {
                view.loadUrl(url);
                return true;
            }

            @RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)
            @Override
            public void onReceivedHttpError(WebView view, WebResourceRequest request, WebResourceResponse errorResponse) {
                super.onReceivedHttpError(view, request, errorResponse);
                // 这个方法在6.0才出现
                int statusCode = errorResponse.getStatusCode();
                System.out.println("onReceivedHttpError code = " + statusCode);
                if (404 == statusCode || 500 == statusCode) {
                    view.loadUrl("about:blank");// 避免出现默认的错误界面
                    view.loadUrl(mErrorUrl);
                }
            }

            @RequiresApi(api = Build.VERSION_CODES.M)
            @Override
            public void onReceivedError(WebView view, WebResourceRequest request, WebResourceError error) {
                super.onReceivedError(view, request, error);
                // 断网或者网络连接超时
                if (error.getErrorCode() == ERROR_HOST_LOOKUP || error.getErrorCode() == ERROR_CONNECT || error.getErrorCode() == ERROR_TIMEOUT) {
                    view.loadUrl("about:blank"); // 避免出现默认的错误界面
                    view.loadUrl(mErrorUrl);
                }
            }

            @Override
            public void onReceivedError(WebView view, int errorCode, String description, String failingUrl) {
                super.onReceivedError(view, errorCode, description, failingUrl);
                // 断网或者网络连接超时
                if (errorCode == ERROR_HOST_LOOKUP || errorCode == ERROR_CONNECT || errorCode == ERROR_TIMEOUT) {
                    view.loadUrl("about:blank"); // 避免出现默认的错误界面
                    view.loadUrl(mErrorUrl);
                }
            }
        });

        wb.setWebChromeClient(new WebChromeClient() {
            @Override
            public void onProgressChanged(WebView view, int newProgress) {
                super.onProgressChanged(view, newProgress);
                Log.e("done", "onProgressChanged: " + newProgress);
                progressBar.setProgress(newProgress);
            }

            @Override
            public void onReceivedTitle(WebView view, String title) {
                super.onReceivedTitle(view, title);
                // android 6.0 以下通过title获取
                if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M) {
                    if (title.contains("404") || title.contains("500") || title.contains("Error")) {
                        view.loadUrl("about:blank");// 避免出现默认的错误界面
                        view.loadUrl(mErrorUrl);
                    }
                }
            }
        });
    }

因为WebView引起了google的重视,所以版本之间存在很多差异,所以这里区分了很多版本的问题,上面都有相应的注解!其实就是在断网或者网络重连的时候添加了相应的错误页面,防止用户看见那个难看的断网页面。增加用户的体验!

WebView与JS交互的实现

这里先贴一段测试的js代码,下面会用到:

<html>
<head>
    <meta charset="utf-8">
    <title>App调用js的代码</title>

    <script>
    // Android需要调用的方法
    function callJS(){
       console.log("你看我在哪出来~~~");
    }
    function callAndroid(){
       // 由于对象映射,所以调用test对象等于调用Android映射的对象
       test.hello("js调用了android中的hello方法");
    }
    </script>
</head>
<body>
<h1>假装这个一个页面</h1>
<button type="button" id="button1" onclick="callAndroid()"></button>
</body>
</html>

1. 原生代码调用js中的代码

这里可以有两种实现方案:
这里注意一点,无论使用那种方案调用,前提是一定要有下面的代码!!!

        WebSettings settings = mWb.getSettings();
        settings.setJavaScriptEnabled(true);//允许相应js代码
  • 通过loadUrl("javascript:callJS()")调用js中的代码,但是会刷新相应的页面(这里面callJS是上面function的方法名称)

  • 通过evaluateJavascript(String script, ValueCallback<String> resultCallback)的callBack进行调用相应的方法(这个处理效率高,但是这个方法是在API19才可以使用的,所以一般在使用的时候通过判断版本两个一起使用!!!)

整体的使用代码如下:

        if (Build.VERSION.SDK_INT > 18) {
            mWb.evaluateJavascript("javascript:callJS()", new ValueCallback<String>() {
                @Override
                public void onReceiveValue(String value) {
                    Log.e("done", "onReceiveValue: " + value);
                }
            });
        } else {
            mWb.loadUrl("javascript:callJS()");
        }

这样既解决了效率的问题也解决了版本的问题。

2. 原生代码调用js中的代码

这里最常用的方式就是通过注释@JavascriptInterface进行调用,但是看了Carson_Ho的文章说这里有漏洞,但是说4.2就没有这种漏洞了,所以没有去研究,感觉现在一般的手机都应该超过4.2了,如果以后需要适配的时候在仔细看一下!!!

  • 步骤1:
    创建一个类用于给JS提供相应的方法;
    class JSMothod {
        @JavascriptInterface
        public void hello(String msg) {
            System.out.println("JS调用了Android的hello方法");
        }
    }

这里有一点需要注意的,就是@JavascriptInterface这个注解一定要加上,否则对于4.0一下的手机可能会存在问题!!!切记。。。

  • 步骤2:
    添加相应的调用方法
addJavascriptInterface(Object object, String name);

这里面的两个参数分别是上面写的那个类对象和相应js中的方法名;具体的代码是这样的。。。

mWb.addJavascriptInterface(new JSMothod(),"test");

这样就能实现js调用Android的代码了,其实很简单。


基本上上面的内容就囊括了Android在使用WebView中的一些常见的使用场景,可能还有些内容没有想到,如果有什么不明白的,可以在下方给我留言,还希望这些内容可以帮到你!!!

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 205,236评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 87,867评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,715评论 0 340
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,899评论 1 278
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,895评论 5 368
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,733评论 1 283
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,085评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,722评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,025评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,696评论 2 323
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,816评论 1 333
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,447评论 4 322
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,057评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,009评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,254评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,204评论 2 352
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,561评论 2 343

推荐阅读更多精彩内容