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