一、WebView
1.概述
WebView是View的子类,用于在布局中部分展现网页。常用的地方,比如购物app的商品详情页面,就可以使用WebView。
2.使用
布局:
<?xml version="1.0" encoding="utf-8"?>
<WebView xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/webview"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
/>
加载:
WebView mWebView = (WebView) findViewById(R.id.webview);
mWebView.loadUrl("https://www.baidu.com/");
注意需要网络权限。
3.使用JavaScript
JavaScript默认是不可用的,需要通过WebView的WebSettings调用setJavaScriptEnabled()方法来设置。
WebSettings webSettings = myWebView.getSettings();
webSettings.setJavaScriptEnabled(true);
之后可以通过addJavaScriptInterface()定义JavaScript访问Android的接口,比如启动拨号,使用Dialog等等。
public class WebAppInterface {
Context mContext;
WebAppInterface(Context c) {
mContext = c;
}
/** 显示toast */
@JavascriptInterface
public void showToast(String toast) {
Toast.makeText(mContext, toast, Toast.LENGTH_SHORT).show();
}
}
注意:如果targetSdkVersion 为17或以上,必须使用注解@JavascriptInterface表明该接口可以被JavaScript获取。不提供就无法访问。这块主要是出于对安全的考虑。之后使用addJavaScriptInterface()添加该接口,并指定其名称。
WebView webView = (WebView) findViewById(R.id.webview);
webView.addJavascriptInterface(new WebAppInterface(this), "android");
之后在html中使用:
<input type="button" value="Say hello" onClick="showAndroidToast('Hello Android!')" />
<script type="text/javascript">
function showAndroidToast(toast) {
Android.showToast(toast);
}
</script>
WebView会自动将接口载入。
注意:由于addJavascriptInterface会让js来控制应用,因此,安全问题很重要。所以,不要轻易链接到不安全的地址,一般不要轻易的addJavaScriptInterface()。
4.导航
WebView中的url链接,点击之后会默认的启动浏览器来打开,而不是在webView中。可以通过设置WebViewClient使其仍在WebView中打开。
WebView myWebView = (WebView) findViewById(R.id.webview);
myWebView.setWebViewClient(new MyWebViewClient());
重写WebViewClient:
private class MyWebViewClient extends WebViewClient {
@Override
public boolean shouldOverrideUrlLoading(WebView view, String url) {
if (Uri.parse(url).getHost().equals("www.example.com")) {
// 让WebView加载页面
return false;
}
// 否则,启动别的浏览器应用
Intent intent = new Intent(Intent.ACTION_VIEW, Uri.parse(url));
startActivity(intent);
return true;
}
}
点击链接,系统调用shouldOverrideUrlLoading()方法,检测是是否要处理,返回false,则WebView处理,true,则选择其他应用处理。
5.导航页面历史
当webView加载多个页面时,其会保存浏览记录。通过goBack()以及goForward()进行后退和前进操作。
@Override
public boolean onKeyDown(int keyCode, KeyEvent event) {
// 点击了返回键且WebView有历史记录,则可以返回
if ((keyCode == KeyEvent.KEYCODE_BACK) && myWebView.canGoBack()) {
myWebView.goBack();
return true;
}
return super.onKeyDown(keyCode, event);
}
二、Android4.4中WebView变化
API19之后,新引入的WebView是基于Chromium浏览器的,使其能有更好的性能,更好的支持h5,css3,以及JavaScript。
1.用户代理
用户代理,简单说,就是浏览器给服务器提供的关于自身的信息,包括操作系统信息,浏览器信息等。如果服务内容是基于用户代理的,则用户代理字符串应为:
Mozilla/5.0 (Linux; Android 4.4; Nexus 4 Build/KRT16H) AppleWebKit/537.36
(KHTML, like Gecko) Version/4.0 Chrome/30.0.0.0 Mobile Safari/537.36
如果不需要存储UserAgent或不需要实例化WebView,可以用静态方法getDefaultUserAgent()获取用户代理,如果要重写WebView的用户代理字符串,使用getUserAgentString()获取。
2.多线程以及线程阻塞
如果在非UI线程调用WebView的方法,使用runOnUiThread()方法。同时,不要去阻塞线程,比如等待js的回调。
// This code is BAD and will block the UI thread
webView.loadUrl("javascript:fn()");
while(result == null) {
Thread.sleep(100);
}
可以使用evaluateJavascript()异步运行js。
3.自定义URL处理
新WebView对使用自定义协议的url增加了限制。只有合法的url才会触发shouldOverrideUrlLoading()和shouldInterceptRequest()方法。如果一定要使用自定义URL或者base URL,需要确保请求的URL符合RFC 3968标准。例如:<a href="showProfile">Show Profile</a>将不会调用回调。
如果加载的页面调用loadData()或loadDataWidthBaseUrl()使用不合法或空base URL,将不会回调shouldOverrideUrlLoading()。如果一定要使用不合法或空base URL,需要指定绝对路径。
如果用loadUrl()或loadDataWithBaseURL()使用合法的Base URL加载页面,将可以收到shouldOverrideUrlLoading()回调,且获得的URL相对于当前页面是绝对路径。例如,会收到“http://www.example.com/showProfile”而不是showProfile。可以使用自定义协议来代替简单字符串,例如上面的可以改为:<a href="example-app:showProfile">Show Profile</a>。这样shouldOverrideUrlLoading()将会被回调:
// 该URL协议应该是非层次的(没有后面的斜杠/)
private static final String APP_SCHEME = "example-app:";
@Override
public boolean shouldOverrideUrlLoading(WebView view, String url) {
if (url.startsWith(APP_SCHEME)) {
urlData = URLDecoder.decode(url.substring(APP_SCHEME.length()), "UTF-8");
respondToData(urlData);
return true;
}
return false;
}
如果不能修改HTML,可以使用loadDataWithBaseUrl()并且设置base URL,其由自定义协议,和一个有效主机组成,形如:“example-app://host/”。
例:
webView.loadDataWithBaseURL("example-app://example.co.uk/", HTML_DATA,
null, "UTF-8", null);
有效主机名应该符合RFC 3986标准,并且最后包括尾部的/,否则可能会丢弃来自加载的页面的任何请求。
4.Viewport 变化
1)不支持target-densitydpi,建议使用图像或者css;
2)以前版本的WebView,如果Viewport宽度设置为小于或等于“320”的值,会被设为“device-width”,如果Viewport高度为小于或等于WebView高度的值将设为“device-height”。 但是,当在新的WebView中运行时,WebView会放大以填充屏幕宽度。
3)默认缩放弃用;
5.样式变化
1)background会覆盖background-size属性,重置为默认字体大小。只需要先定义background,后定义background-size即可;
2)尺寸为Css像素,而不是屏幕像素;
3)不支持NARROW_COLUMNS和SINGLE_COLUMN;
三、在JavaScript中处理TouchEvents
如果网页直接处理WebView中的TouchEvents,要确保也处理了touchcancel事件。有以下两种可能:
1.元素被触摸(touchstart和touchmove被调用),页面滚动,touchcancel被抛出;
2.元素被触摸(touchstart被调用),但没调用event.preventDefault(),从而足够早就触发了事件(WebView会假设你不想消耗触摸事件)。
四、最佳实践
1.重定向到手机专用页面;
一般是根据用户代理中确定移动页面,其可以识别是否移动端,甚至是Android版本。
2.使用适合移动端的有效标记DOCTYPE;
官方解释是,最适合的标记语言是XHTML Basic,确保移动设备有效的展现页面,比如哪些HTML框架不适用于移动端。务必给文档声明适当的字符编码,如utf-8.如:
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML Basic 1.1//EN"
"http://www.w3.org/TR/xhtml-basic/xhtml-basic11.dtd">
3.使用Viewport元数据正确调整网页大小;
在文档的head中提供元数据,指定浏览器的Viewport如何呈现网页,例如宽高,初始网页缩放,目标屏幕密度。如:
<meta name="viewport" content="width=device-width, initial-scale=1.0, user-scalable=no">
4.避免多个文件请求;
由于移动端连接速度远低于PC,所以应尽可能快地加载网页。其中一种方法就是避免在head中加载额外的文件,如css和js文件。而应该在head中直接提供样式和js,或者在body末尾提供(对于加载完之前不需要执行的脚本)。还可以使用工具来压缩文件,优化其大小和加载速度。
5.使用垂直布局;
避免浏览的网页左右滑动,要倾向于手机的用户体验。
关于移动端网页,官方推荐的几个学习资料:W3C的移动web最佳实践;其他指南和加速相关,YAhoo的指南Exceptional Performance以及谷歌的速度指南,Let's make the web faster(要翻墙).