WebView 使用和方法(持更)

正式项目经常是Android 和 iOS 同时开发的,使用H5进行部分开发,可以减少开发成本,又便于更新,所以在实际项目中会用 WebView 做很多事情。
在开发当中也遇到很多的问题和深坑,WebView 虽然主要类不多,但是方法甚多。再此做知识总结,也为有同样需求和问题的同仁们提供微薄的帮助,其中也借鉴了很多公开的解决方案,会附上相应传送门。
如果内容有帮助到你,请给个大拇哥❤

先看下导图(由于内容较多,分开更新)
Part 1 传送门 WebView 使用和方法
Part 2 传送门 常见功能 & 问题

1. WebView 基本使用

1.1 基本使用

在 Android 应用开发中会经常要嵌套H5来进行混合式开发,WebView是不可或缺的容器。

使用 WebView 加载一个网页很容易

  1. AndroidManifest.xml 中添加网络权限
<uses-permission android:name="android.permission.INTERNET"/>
  1. 在布局中添加 WebView
<WebView
    android:id="@+id/webView"
    android:layout_width="match_parent"
    android:layout_height="match_parent" 
/>
  1. ActivityFragment 中获取控件
WebView mWebview = findViewById(R.id.webView);
// 也可以通过 new 的形式创建 WebView
  1. 加载目标地址
webView.loadUrl("https://developer.android.google.cn/");

so easy~
WebView 有很丰富的功能,继续学习

1.2 加载页面

//加载一个远程网页
webView.loadUrl("https://developer.android.google.cn/");

// 加载assets中资源
webView.loadUrl("file:///android_asset/test.html");

//加载sdcard中子源
webView.loadUrl("content://com.android.htmlfileprovider/sdcard/test.html");

使用 loadUrl,不过需要注意,这里因为是使用本地数据,所以传入的url需要做些处理,例如:
  a 如果html文件存于assets:则加前缀:file:///android_asset/
  b 如果html文件存于sdcard:则加前缀:content://com.android.htmlfileprovider/sdcard/
  注意:content 前缀可能导致异常,也可使用file:///sdcard/ 或者 file:/sdcard 做前缀。

2. 加载设置

2.1 页面自适应屏幕

settings.setUseWideViewPort(true); // 将图片调整到适合webview的大小
settings.setLoadWithOverviewMode(true);  // 缩放至屏幕的大小

2.2 缩放

settings.setSupportZoom(true);//启用缩放功能
settings.setBuiltInZoomControls(true);//使用WebView内置的缩放功能
settings.setDisplayZoomControls(false);//隐藏屏幕中的虚拟缩放按钮

2.3 个性化设置

settings.setTextZoom(100);//字体百分比,替代原API:setTextSize

安卓端上可能因为设置了系统字体大小导致 h5 页面布局异常
该方法可设置 webview 内部字体的缩放比例。而字体单位是 px,它其实设置的是 px 的缩放比例。通过强制设置为100%,来使得用户的外部设置无法内部 webview 的字体大小呈现。

settings.setMediaPlaybackRequiresUserGesture(false);//SDK>18 是否支持手势控制网页媒体,比如视频的全屏
String ua = webview.getSettings().getUserAgentString();
webview.getSettings().setUserAgentString(ua+"; 自定义标记");

有时和H5交互的时候,H5需要一些设备信息,可以通过自定义 agent 处理,里面本身也包含一些内置浏览内核的信息
Mozilla/5.0 (Linux; Android 6.0; HTC D10w Build/MRA58K; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/48.0.2564.106 Mobile Safari/537.36

2.4 Https 加载 Http 混合模式

当 WebView 加载 https 的地址中有 http 的地址时(比如 https 地址含有 http 的图片) WebView 无法加载 http 的资源

原因是 Android 5.0 (Lollipop)开始,WebView 默认不支持同时加载 Https 和 Http 混合模式。此时可以通过 setMixedContentMode() 方法设置混合模式

if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
        webSettings.setMixedContentMode(WebSettings.MIXED_CONTENT_ALWAYS_ALLOW);
}
  • MIXED_CONTENT_NEVER_ALLOW:(Android 5.0 以上默认值)
    Webview不允许一个安全的站点(https)去加载非安全的站点内容(http),比如,https网页内容的图片是 http 链接。强烈建议App使用这种模式,因为这样更安全。

  • MIXED_CONTENT_ALWAYS_ALLOW:(Android 5.0 以下默认值)
    在这种模式下,WebView是可以在一个安全的站点(Https)里加载非安全的站点内容(Http),这是WebView最不安全的操作模式,尽可能地不要使用这种模式。

  • MIXED_CONTENT_COMPATIBILITY_MODE
    在这种模式下,当涉及到混合式内容时,WebView会尝试去兼容最新Web浏览器的风格。一些不安全的内容(Http)能被加载到一个安全的站点上(Https),而其他类型的内容将会被阻塞。这些内容的类型是被允许加载还是被阻塞可能会随着版本的不同而改变,并没有明确的定义。这种模式主要用于在App里面不能控制内容的渲染,但是又希望在一个安全的环境下运行。

    参考:Android5.0 WebView中Http和Https混合问题

3. 页面监听与拦截

3.1 WebViewClient

帮助 WebView 处理各种通知、请求事件、记录页面加载过程的。其中就包括URL地址,我们可以通过它来监控到地址的调用过程

// 设置 WebViewClient
mWebView.setWebViewClient(mWebViewClient); 

shouldOverrideUrlLoading

用户可选择是否拦截加载 URL

如果返回值为 true,拦截 WebView 加载 url,false 允许 WebView 加载 url

If a WebViewClient is provided, returning true causes the current WebView to abort loading the URL, while returning false causes the WebView to continue loading the URL as usual.

boolean shouldOverrideUrlLoading(WebView view, WebResourceRequest request)
boolean shouldOverrideUrlLoading(WebView view, String url) (API>21)

可以在这个方法里做什么呢,比如点击到已经定义好的 url 协议 电话号码 tel:// 时,那么可以在这里做拦截,跳转到系统拨号界面。

@Override
public boolean shouldOverrideUrlLoading(WebView view, String url) {
    if (url.startsWith("tel:")) {
        Intent intent = new Intent(Intent.ACTION_VIEW, Uri.parse(url));
        startActivity(intent);
        return true;
    }
    super.shouldOverrideUrlLoading(view, url);
}

所以在实际项目中,可以在这里处理自定义的一些跳转协议。

onPageStarted() 开始载入页面调用

开始载入页面调用的,我们可以设定一个 loading 的页面,告诉用户程序在等待网络响应。

onPageFinished() 页面加载结束时调用。

页面加载结束时调用 onPageFinished()

onLoadResource()

在加载页面资源时会调用,每一个资源(比如图片)的加载都会调用一次。

onReceivedError()

加载页面的服务器出现错误时调用。

App 里面使用 webview 控件的时候遇到了诸如 404 这类的错误的时候,若也显示浏览器里面的那种错误提示页面就显得很丑陋了,那么这个时候我们的 app 就可以加载一个本地的错误提示页面,比如:

@Override
public void onReceivedError(WebView view, WebResourceRequest request, WebResourceError error) {
    super.onReceivedError(view, request, error);
    view.loadUrl("file:///android_assets/error_handle.html");
}

有一些常见的 error
-2:net::ERR_NAME_NOT_RESOLVED 服务器不存在
-8:net::ERR_CONNECTION_TIMED_OUT 连接超时,可能被墙掉了

onReceivedSslError()

SSL 证书验证错误

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

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

onReceivedHttpError()

响应服务器返回的 Http 错误,当一个 http 正常响应时,状态码会是 200,当状态码异常时可以用该方法监听。

这里可以和页面做一些约定,比如页面没有登录,就返回 402,当 errorcode = 402 时,就跳转到登录页面。

@Override
public void onReceivedHttpError(WebView view, WebResourceRequest request, WebResourceResponse errorResponse) {
    super.onReceivedHttpError(view, request, errorResponse);
    int code = errorResponse.getStatusCode();      
    if (code == 402){
        // toLogin();
    }
}

3.2 WebChromeClient

和 webView 的网页内容管理有关,它的成员方法帮助 WebView 处理 JS 的弹框、网站图标、网站title,加载进度等等。

  // 设置 WebChromeClient
 mWebView.setWebChromeClient(mWebChromeClient);

页面加载进度 onProgressChange()

这个方法会在网页加载过程中多次触发,当 newProgress = 100 时,可以认为网页加载完成。这个方法比 onPageFinished 更为准确,一般用来实现自定义进度条加载。

/**
 * 页面提示框
 * @param view
 * @param newProgress 加载进度
 */
public void onProgressChanged(WebView view, int newProgress) {
    super.onProgressChanged(view, newProgress);
}

显示页面标题 onReceivedTitle()

通常会使用 Activity 或 Fragment 加载网页,我们想让页面标题随着网页内容变化的话,就可以用这个方法获取当前页面的 title。

public void onReceivedTitle(WebView view, String title) {
     //过滤掉服务器返回的连接地址作为标题 过滤掉 http 开头
     if (!title.startsWith("http")) {
         mTvTitle.setText(title);
     }
}

页面提示框 onJsAlert()

html 可以用下文 Android 与 JS 交互中的html 代码测试


/**
 * 页面提示框
 * @param message alert 弹出窗口中的提示信息(提示或警告信息对话框,仅一个确认按钮)
 * @param result 向网页中的 Javascript 代码反馈本次操作结果(result.confirm 代表点击了确定按钮,result.cancel 代表点击了取消按钮)
 * @return boolean {@code true} if the request is handled or ignored.{@code false} if WebView needs to show the default dialog. 
 */
public boolean onJsAlert(WebView view, String url, String message, final JsResult result) {
//return super.onJsAlert(view, url, message, result);
    new AlertDialog.Builder(mContext)
            .setTitle("JsAlert")
            .setMessage(message)
            .setPositiveButton("确定", new DialogInterface.OnClickListener() {
                @Override
                public void onClick(DialogInterface dialog, int which) {
                    result.confirm();
                }
            })
            .setCancelable(false)
            .show();
    return true;
}

页面选择框 onJsConfirm()


/**
 * 页面选择框
 * @param message confirm 弹出窗口中的提示信息(确认对话框,有确认、取消两个按钮)
 * @param result 向网页中的 Javascript 代码反馈本次操作结果(result.confirm 代表点击了确定按钮,result.cancel 代表点击了取消按钮)
 */
public boolean onJsConfirm(WebView view, String url, String message, final JsResult result) {
}

页面选择框 onJsPrompt()


/**
 * 页面选择框
 * @param message prompt 弹出窗口中的提示信息(输入信息对话框,有一个输入框,还有确认、取消两个按钮)
 * @param result 向网页中的 Javascript 代码反馈本次操作结果
 */
public boolean onJsPrompt(WebView view, String url, String message, String defaultValue, JsPromptResult result) {
    final EditText editText = new EditText(MainActivity.this);
    editText.setText("默认数据");//设置默认数据
    new AlertDialog.Builder(MainActivity.this)
            .setTitle("JsPromt")
            .setView(editText)//为弹出窗口设置输入框
            .setPositiveButton("确定", new DialogInterface.OnClickListener() {
                @Override
                public void onClick(DialogInterface dialog, int which) {
                    result.confirm(editText.getText().toString());//向Javascript传递输入值
                }
            })
            .setNegativeButton("取消", new DialogInterface.OnClickListener() {
                @Override
                public void onClick(DialogInterface dialog, int which) {
                    result.cancel();
                }
            })
            .setCancelable(false)
            .show();
    return true;
}

4. 网页的前进与后退

在 Android 中 如果在 WebView 触发物理返回键会直接关闭整个 WebView,所以需要监听事件,用 WebView 的返回处理。

webView.goBack();//跳到上个页面
webView.goForward();//跳到下个页面
webView.canGoBack();//是否可以跳到上一页(如果返回false,说明已经是第一页)
webView.canGoForward();//是否可以跳到下一页(如果返回false,说明已经是最后一页)

实际项目中可以可以监听物理键的返回键,也可以重写 onBackPressed()方法,以下代码段仅做参考:

@Override
public boolean onKeyUp(int keyCode, KeyEvent event) {
    if (KeyEvent.KEYCODE_BACK == keyCode) {
        if (mWebView != null && mWebView.canGoBack()) {
            mWebView.goBack();
        } else {
            finish();
        }
        return true;
    }
    return super.onKeyUp(keyCode, event);
}

5. 缓存

加载 html 界面时:

  • 当加载 html 页面时,WebView 会在/data/data/包名目录下生成 database 与 cache 两个文件夹。
  • 请求的 URL记录保存在 WebViewCache.db,而 URL的内容是保存在 WebViewCache 文件夹下。

缓存方式

settings.setCacheMode(WebSettings.LOAD_NO_CACHE);
  • LOAD_CACHE_ONLY: 不使用网络,只读取本地缓存数据
  • LOAD_DEFAULT: (默认)根据cache-control决定是否从网络上取数据
  • LOAD_NO_CACHE: 不使用缓存,只从网络获取数据
  • LOAD_CACHE_ELSE_NETWORK:只要本地有,无论是否过期,或者no-cache,都使用缓存中的数据

PS:这里还有其他和缓存相关的,由于还没有搞的很清楚,暂时不总结了

清除缓存

//清除网页访问留下的缓存
//由于内核缓存是全局的因此这个方法不仅仅针对webview而是针对整个应用程序.
webView.clearCache(true);

//清除当前webview访问的历史记录
//只会webview访问历史记录里的所有记录除了当前访问记录
webView.clearHistory();

//这个api仅仅清除自动完成填充的表单数据,并不会清除WebView存储到本地的数据
webView.clearFormData();

6. Android 和 JS 的交互

允许 WebView 与 Js 交互,先设置权限

settings.setJavaScriptEnabled(true); //使 WebView 支持 JS
settings.setJavaScriptCanOpenWindowsAutomatically(true); //支持通过JS打开新窗口
  • setJavaScriptEnabled 设置支持 JS,比如下面的代码段 放到 assets 文件下,使用loadUrl("file:///android_asset/index.html") 打开网页,如果改属性为 false 点击就不能弹出 alert

  • setJavaScriptCanOpenWindowsAutomatically 这个方法目前没测试出到底有什么用途。无论 true 还是 false 都能代开网页。不过以防万一,还是设置上吧

index.html 文件(可放入asset 文件夹下测试)

<!DOCTYPE html>
<head>
    <meta charset=utf-8>
    <meta http-equiv=X-UA-Compatible content="IE=edge">
    <meta name=viewport content="width=device-width,initial-scale=1,maximum-scale=1,user-scalable=no,viewport-fit=cover">
    <title></title>
</head>

<body>
    <button id="app">点击</button>
</body>

<script>
  document.getElementById("app").onclick = function () {
     alert('Tina')
     //window.open("http://www.baidu.com")
  }
</script>
</html>

6.1 Android 调用 JS

在 html 有如下 javascript 方法(可放进上面 script 中验证)

function jsFunc(){   
    alert('调用 JS 方法')
}

Android 主动调用 JavaScript 的函数,使用如下方法

mWebView.loadUrl("javascript:jsFunc()");

加参也一样,比如调用下面方法

function jsFunc(msg){   
    alert(msg)
}

WebView 操作:

mWebView.loadUrl("javascript:jsFunc(\'jsFunc:Tina\')");
  • 注意字符串要加引号

6.2 JS 调用 Android

Android 使用如下方法和 Js 建立联系

addJavascriptInterface(Object obj, String interfaceName); 

第一个参数为类的对象,第二是自定义别名,Js 通过这个别名来调用 Java 的方法

前面的 Html 中,点击 button 触发事件,通过 Android 别名调用 Android 的 callJava 方法

  document.getElementById("app").onclick = function () {
     window.android.callJava()
  }

设置 WebView

 mWebView.addJavascriptInterface(new JSRelation(mContext), "android");

在定义的类中添加相关方法 方法需添加@JavascriptInterface注解

public class JSRelation {
    private Context mContext;
    public JSRelation(Context context) {
        this.mContext = context;
    }

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