WebView使用全攻略

时间:2021-06-27 19:00:49

1.> 转载请标明出处

本文出自[HCY的微博]

一、概述

移动web开发相对原生开发有以下好处:

  • 开发成本低,可以适配多种平台的设备
  • 迭代更新成本低,可以快速的实现更新的内容全覆盖

所以对于频繁更新的业务,比如商城。就比较适合用web进行开发。采用web开发,在App端必然离不开WebView这个组件。本文将从以下几个方面阐述WebView的相关知识及Web开发中的常用技巧。

  • 内容加载
  • 与服务端的交互
  • 性能优化
  • 安全性
  • Web开发常用技巧

二、内容加载

WebView的内容加载主要分为以下三种方式。

  • 加载远端页面
  • 加载本地页面
  • 加载html代码

加载远端代码

比如加载百度

wvTest.loadUrl("http://www.baidu.com");

加载本地代码

比如加载assets/error.html

wvTest.loadUrl("file:///android_asset/error.html");

加载html代码

    String summary = "<html><body>You scored <b>192</b> points.</body></html>";
wvTest.loadData(summary, "text/html", null);

三、与服务端的交互

WebView与服务端的交互方式主要有两种:

  • JavaScript的交互方式
  • 拦截Url请求的交互方式

JavaScript的交互方式

假设有一个名为error.html的网页文件在assets目录中,代码如下:

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<title>页面无法显示</title>
<style type="text/css">
html,body,div,img,p,ul,li{ margin:0; padding:0;}
html,body,img {width:100%; height:100%;}
.wrapper{ position:relative; margin:0 auto;width:100%; height:100%; min-height:445px; overflow:hidden; font-size:14px; line-height:180%;}
.wrapper img { width:100%; height:100%;}
.wrapper p{ margin-left:8px; padding-right:15px; position:absolute;top:50px;}
a.refresh{ float:right; -webkit-border-radius:5px; background:#ccdeff; display:block; width:60px; height:20px; text-align:center; line-height:20px; font-size:12px; text-decoration:none; color:#201f1f; -webkit-tap-highlight-color:#a7c7ff;}
.wrapper ul{ margin-left:35px; margin-top:5px; position:absolute;top:100px;}
</style>
</head>

<body>
<script language="javascript">
//提供给App端调用测试的代码
function testForClient() {
window.error.refresh();

}
</script>
<div class="wrapper">
//调用App端名为error的对象的refresh方法
<p><a href="javascript:window.error.refresh()" class="refresh">刷新</a>该页面无法显示,您可以尝试:</p>
<ul>
<li>检查您的网络连接是否正常</li>
<li>点击右侧刷新按钮重新连接</li>
<li>可能是服务器故障,请稍后再试</li>
</ul>
</div>
</body>
</html>

JavaScript调用App端的方法
1.开启JS功能,这一步很重要

wvTest.getSettings().setJavaScriptEnabled(true);

2.使用WebView的addJavascriptInterface(Object object, String name)方法将App端的对象注入到WebView中,其中object为注入到JS上下文中的对象,其中的name就是JS中使用的对象名
例如将要被使用的对象的类代码如下

public class JavascriptSimple {
String url = null;
WebView view = null;

public JavascriptSimple(WebView view, String url) {
this.view = view;
this.url = url;
}
//1.为了防止JS反射攻击,4.1以上的系统必须添加此注解,否则将无法被JS调用
//2.当然,只有public方法才能被调用
//3.在JS中被调用的方法都在JavaBridge线程里面,而WebView所有的方法都只能在主线程中调用。
@JavascriptInterface
public void refresh() {
if (view == null || TextUtils.isEmpty(url)) {
return;
}
// 所有JS方法都在JavaBridge线程中回调,所以对于UI操作要回调到主线程
Handler mainHandler = new Handler(Looper.getMainLooper());
mainHandler.post(new Runnable() {

@Override
public void run() {
view.loadUrl(url);
}
});
}
}

注入到JS中的代码为

wvTest.addJavascriptInterface(new JavascriptSimple(wvTest, ""), "error");

3.在被调用的方法前面加上@JavascriptInterface注解,若不添加,4.1以上的系统中JS将无法成功调用App端的方法。

4.在JS中被调用的方法都在JavaBridge线程里面,而WebView所有的方法都只能在主线程中调用。。否则会出现警告信息A WebView method was called on thread ‘JavaBridge’. All WebView methods must be called on the same thread

APP调用JavaScript的方法

1.首先必须要加载JavaScript方法所在的Url,否则App端无法调用成功。

wvTest.loadUrl("file:///android_asset/error.html");

2.调用JavaScript方法

    btDemo.setOnClickListener(new OnClickListener() {

@Override
public void onClick(View v) {
wvTest.loadUrl("javascript:testForClient()");
}
});

拦截Url请求的交互方式

1.首先要重写WebViewClient的shouldOverrideUrlLoading方法

    @Override
public boolean shouldOverrideUrlLoading(WebView view, String url) {
if (TextUtils.isEmpty(url)) {
return true;
}
//继续做URL解析,执行特定的业务逻辑处理
return doOverrideUrlLoading(view, url);
}

拦截请求响应实现离线缓存

        @Override
public WebResourceResponse shouldInterceptRequest(WebView view, WebResourceRequest request) {
//return null表示不拦截响应请求
return cacheEnabled ? interceptRequest(request.getUrl().toString()) : null;
}

private WebResourceResponse interceptRequest(String url) {
if (TextUtils.isEmpty(url)) {
return null;
}
byte[] binary = aCache.getAsBinary(url);
if (binary == null || isUpdate) {
byte[] bytes = cacheRequest(url);
binary = bytes;
}
if (binary == null || !isUseCache) {
return null;
} else {
LogUtils.i(String.format("read %s cache", url));
InputStream inputStream = new ByteArrayInputStream(binary);
return new WebResourceResponse("", "UTF-8", inputStream);
}
}

private byte[] cacheRequest(String url) {
try {
OkHttpClient client = new OkHttpClient();
Request request = new Request.Builder()
.url(url)
.build();
Response response = client.newCall(request).execute();
if (!response.isSuccessful()) {
log(String.format("cache %s failed", url));
return null;
} else {
log(String.format("cache %s succeeded", url));
byte[] bytes = response.body().bytes();
aCache.put(url, bytes);
return bytes;
}

} catch (Exception e) {
e.printStackTrace();
log(String.format("cache %s failed", url));
return null;
}
}

2.解析Url,做特定的业务逻辑处理。
比如有http://www.xxx.com/?act=test&p={此处为json数据}
就可以通过act来区分不同的业务,p后面的json数据可以作为参数,传递给业务处理方法。

四、WebView的性能优化

加快渲染速度

可以在页面开始加载时调用setBlockNetworkImage(true);方法来阻止图片的加载,先渲染除图片以外的东西。当加载完成之后调用setBlockNetworkImage(false);方法来开启图片的加载,经过测试加载速度快了许多。

public abstract class BaseWebViewClient extends WebViewClient {
@Override
public void onPageStarted(WebView view, String url, Bitmap favicon) {
// 为了加快加载速度,先阻止图片加载,待数据加载完毕在onPageFinished回调中开启图片加载功能
view.getSettings().setBlockNetworkImage(true);

}

@Override
public void onPageFinished(WebView view, String url) {
// 网页加载完毕,开启加载图片
view.getSettings().setBlockNetworkImage(false);

}

@Override
public boolean shouldOverrideUrlLoading(WebView view, String url) {
if (TextUtils.isEmpty(url)) {
return true;
}
//继续做URL解析,执行特定的业务逻辑处理
return doOverrideUrlLoading(view, url);
}

/**
* 子类中继续处理URL拦截操作
*
* @param view
* @param url
* @return
*/

protected abstract boolean doOverrideUrlLoading(WebView view, String url);

}

防止内存泄漏

在界面退出时要及时调用WebView的相关释放方法,以免内存泄漏。

    @Override
protected void onDestroy() {
super.onDestroy();
wvTest.stopLoading();
wvTest.removeAllViews();
wvTest.destroy();
}

五、WebView的安全性

1.JavaScript反射攻击

JavaScript可以通过反射技术,调用注入对象的父类的public方法,执行一些不安全代码。

解决方案
1.在被调用的方法前面加上@JavascriptInterface注解,在4.1以上的系统只有加了此注解的方法才能被JavaScript调用。

2.通过shouldOverrideUrlLoading方法屏蔽拦截不属于自己的网站的url请求。

六、Web开发常用技巧

1.从网页中启动App端的Activity

启动App端的Activity,可以配置URL Scheme采用隐式意图的方式启动。
Scheme介绍见http://developer.android.com/guide/topics/manifest/data-element.html

1.配置Scheme

       <activity android:name="com.hcy.test.SchemeAty" >

<!-- android:exported="false" 含有intent-filter的组件默认是导出的,即可以被其它应用调用,若加上了这句代码就变成不可导出,第三方应用就没有权限调用这个组件,不管你下面怎么配置都是无法调用的 -->
<intent-filter>

<!-- 指定向用户显示数据的动作 -->
<action android:name="android.intent.action.VIEW" />
<!-- 隐式意图必须要的category,若没有这个无法启动 -->
<category android:name="android.intent.category.DEFAULT" />
<!-- 指定该Activity能被浏览器安全调用 -->
<category android:name="android.intent.category.BROWSABLE" />
<!-- 1.若scheme相同则继续匹配第2点,若scheme不相同则不符合调用要求 -->
<!-- 2.若host相同则继续第3点,若host不相同则不符合调用要求 -->
<!-- 3.若port相同,则符合调用要求,若port不相同则不符合调用要求 -->
<!-- 4.scheme、host、port都是大小写敏感的,注意大小写区别 -->
<data
android:host="test"
android:port="8990"
android:scheme="hcy" />

</intent-filter>
</activity>

2.启动这个Activity

在其它应用中启动

    Intent intent = new Intent();
intent.setData(Uri.parse("hcy://test:8990/fc?p1=12&p2=1"));
startActivity(intent);

在html中启动

<a href="hcy://test:8990/fc?p1=12&p2=1">launch activity by scheme</a>

3.解析意图内容

Intent intent = getIntent();
StringBuilder sb = new StringBuilder();
if (intent != null) {
// 获取整个Scheme串
sb.append("dataString=" + intent.getDataString() + ",");
// 获取scheme
sb.append("scheme=" + intent.getScheme() + ",");
Uri uri = intent.getData();
if (uri != null) {
// 获取host
sb.append("host=" + uri.getHost() + ",");
// 获取port
sb.append("port=" + uri.getPort() + ",");
// 获取path
sb.append("path=" + uri.getPath() + ",");
// 获取请求内容
sb.append("queryString=" + uri.getQuery() + ",");
// 获取请求参数值
sb.append("p1=" + uri.getQueryParameter("p1"));
}
}

结果为:

dataString=hcy://test:8990/fc?p1=12&p2=1,scheme=hcy,host=test,port=8990,path=/fc,queryString=p1=12&p2=1,p1=12

2.让WebView支持Https网页链接

重写WebViewClient的onReceivedSslError方法

@Override
public void onReceivedSslError(WebView view, SslErrorHandler handler, SslError error) {
//当发生SSL错误时,继续信任SSL证书
handler.proceed();
}