1.> 转载请标明出处
本文出自[HCY的微博]
移动web开发相对原生开发有以下好处:
所以对于频繁更新的业务,比如商城。就比较适合用web进行开发。采用web开发,在App端必然离不开WebView这个组件。本文将从以下几个方面阐述WebView的相关知识及Web开发中的常用技巧。
WebView的内容加载主要分为以下三种方式。
比如加载百度
wvTest.loadUrl("http://www.baidu.com");
比如加载assets/error.html
wvTest.loadUrl("file:///android_asset/error.html");
String summary = "<html><body>You scored <b>192</b> points.</body></html>";
wvTest.loadData(summary, "text/html", null);
WebView与服务端的交互方式主要有两种:
假设有一个名为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()");
}
});
1.首先要重写WebViewClient的shouldOverrideUrlLoading方法
@Override
public boolean shouldOverrideUrlLoading(WebView view, String url) {
if (TextUtils.isEmpty(url)) {
return true;
}
//继续做URL解析,执行特定的业务逻辑处理
return doOverrideUrlLoading(view, url);
}
2.解析Url,做特定的业务逻辑处理。
比如有http://www.xxx.com/?act=test&p={此处为json数据}
就可以通过act来区分不同的业务,p后面的json数据可以作为参数,传递给业务处理方法。
可以在页面开始加载时调用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();
}
JavaScript可以通过发射技术,调用注入对象的父类的public方法,执行一些不安全代码。
解决方案
1.在被调用的方法前面加上@JavascriptInterface注解,在4.1以上的系统只有加了此注解的方法才能被JavaScript调用。
2.通过shouldOverrideUrlLoading方法屏蔽拦截不属于自己的网站的url请求。
启动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
重写WebViewClient的onReceivedSslError方法
@Override
public void onReceivedSslError(WebView view, SslErrorHandler handler, SslError error) {
//当发生SSL错误时,继续信任SSL证书
handler.proceed();
}