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>192b> points.body>html>";
wvTest.loadData(summary, "text/html", null);
WebView与服务端的交互方式主要有两种:
假设有一个名为error.html的网页文件在assets目录中,代码如下:
<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);
}
@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数据可以作为参数,传递给业务处理方法。
可以在页面开始加载时调用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" >
<intent-filter>
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" />
<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 schemea>
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();
}