Android WebView使用解析一

随着APP中HTML5的占比的增加,WebView这个控件的使用也越来越频发。开发中比较常见的例如新闻详情、课程详情、商品详情、商品展示页等,甚至有些APP中直接把登录注册也直接采用WebView内嵌网页的形式实现,这种开发模式就是所说的混合开发的APP(Hybrid APP)。

混合开发的APP(Hybrid APP)中H5虽然不能完全媲美Native APP中交互的流畅性,但是某些特殊情况却可以有效提升用户体验并且减少企业开发成本。举个简单示例,某购物APP,在一些特殊活动营销日,为了让自己的产品更具有节日气氛,可以直接在服务器端修改H5商品页为一个很炫酷的限时抢购的页面,这时候所有的终端设备不需要升级APP,因为商品页就是一个网页,用户直接打开APP,就可以直接融入到购物的气氛中了。

WebView简介

WebView可以说是一个简版的浏览器,它采用Webkit引擎渲染网页,可以进行类似浏览器的回退和前进操作,也可以进行网页的放大缩小;可以执行JavaScript脚步语言,也可以进行Cookie同步等等。数据缓存在本篇文章中暂不介绍,因为这会牵扯到H5的缓存操作,将来准备先介绍一下H5在本地的缓存,再介绍WebView的数据缓存。

WebView常用的功能基本上都离不开这三个类:WebSetting、WebViewClient、WebChromeClient,当然了如果想操作Cookie还要借助于另外两个操作类CookieSyncManager(API21标记为deprecated)和CookieManager。

WebView基本使用

先看一下WebView加载数据的基本操作,比较常用的就是下面两种:

//方式一
webview.loadUrl("https://m.baidu.com");

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

webview.loadUrl()方法在高版本上会弹出浏览器选择对话框,有时候我们可能想更大程度的限制用户在我们的应用内使用,这种情况很显然不是我们的目标,这时候就要借助于WebViewClient类了,下面会有详细的介绍。

WebView回退与前进与刷新

WebView前进用到的时候可能不多,但是回退这个功能相当有用。当加载网页的时候,我们点击了几层链接之后,点击返回键,突然间就退出应用或者直接销毁了当前组件,相信这种交互不是用户需要的,因为有可能用户只是想回退到上一层级页面。

//判断是否有可以回退,可以返回true
webView.canGoBack();
//返回上一层级
webView.goBack();

//判断是否可以前进,可以返回true
webView.canGoForward();
//进入上一层级
webView.goForward();

//刷新
webView.reload();

WebViewClient

WebViewClient最常用的方法如下:

public boolean shouldOverrideUrlLoading(WebView view, String url)
public void onPageStarted(WebView view, String url, Bitmap favicon)
public void onPageFinished(WebView view, String url)

public void onReceivedError(WebView view, int errorCode,String description, String failingUrl)
public void onReceivedError(WebView view, WebResourceRequest request, WebResourceError error)

shouldOverrideUrlLoading

shouldOverrideUrlLoading()方法基本上都是必需的,如果不使用该方法,在低版本如Android2.3上面会直接在WebView上面打开网页,但是在高版本上面会弹出显示各种浏览器选择对话框,让我们选择打开网页的方式。一般使用代码如下:

public boolean shouldOverrideUrlLoading(WebView view, String url) {
	view.loadUrl(url);
	return true;
}

如果我们想选择浏览器的方式打开网页,可以使用下面代码:

public boolean shouldOverrideUrlLoading(WebView view, String url) {
	Intent intent = new Intent(Intent.ACTION_VIEW);
	intent.setData(Uri.parse(url));
	startActivity(intent);
	return true;
}

shouldOverrideUrlLoading()方法返回的是一个boolean类型,如果返回false,则WebView处理链接url,如果返回true,WebView根据程序来执行url。

onPageStarted与onPageFinished

WebView加载网页开始和结束时调用,为了友好的交互效果,可以在onPageStarted()方法开始时使用loading图标,结束时onPageFinished()隐藏loading图标。

onReceivedError

WebView加载网页中错误处理机制就是通过onReceivedError()方法处理的。

/**
 * api23过时
 */
public void onReceivedError(WebView view, int errorCode, String description, String failingUrl) {
    super.onReceivedError(view, errorCode, description, failingUrl);
    ...
}

/**
 * api23新增
 */
public void onReceivedError(WebView view, WebResourceRequest request, WebResourceError error) {
    super.onReceivedError(view, request, error);
    ...
}

onReceivedError方法有两个,第二个是在Android6.0后新增加的,如果是在高版本上开发的,很可能我们复写的是第二个方法, 在低版本上测试时如果出现错误并不会进入onReceivedError方法,所以在开发中一定要注意使用的方法。

WebChromeClient

WebView借助于该类可以实现更丰富的处理效果,如与JavaScript交互、获取网页标题以及加载进度等等。比较常用的方法如下:

public void onReceivedTitle(WebView view, String title)
public void onProgressChanged(WebView view, int newProgress)
public boolean onJsAlert(WebView view, String url, String message,JsResult result)

在使用QQ或者一些其它应用时,有时候会看到头部会有一个加载的进度条,这种情况就说明我们在访问的是一个网页。

如果想使用带有进度条的Activity,请记住不要将Activity主题设置为NoTitleBar方式,同时还要在setContentView()方法之前设置getWindow().requestFeature(Window.FEATURE_PROGRESS)。即使这些都做了,估计最终还是不能达到UI的要求,因为Android自带的进度条在版本之间差异实在太大,而且很不好看。所以如真想使用进度条,最直接的方式就是自定义一个进度条放上去,然后在onProgressChanged()方法中动态更新进度。

onJsAlert也简单的说明一下,当网页中JavaScript触发alert事件时,Android本地程序就会触发该方法,但是弹框也不尽如人意。

一般情况下还是自定义一个弹框来处理,代码如下:

public boolean onJsAlert(WebView view, String url, String message, JsResult result) {
	AlertDialog.Builder builder=new AlertDialog.Builder(MainActivity3.this);
	builder.setTitle("提示");
	builder.setMessage(message);
	builder.setPositiveButton("确定",new DialogInterface.OnClickListener() {
		public void onClick(DialogInterface dialog, int which) {

		}
	});
	builder.create().show();
	//否则弹框就弹出一次
	result.confirm();
	return true;
}

WebSetting

WebView加载的网页是否支持缩放、JavaScript脚本语言、字体大小设置、数据缓存等都是借助该类实现的。该类不是通过new关键字来新建的,而是通过WebView实例直接获取到的。

WebSettings settings = webView.getSettings();

网页支持手势缩放

如果想要WebView加载进的网页而支持缩放,只需要同时设置下面两个属性就可以了。

settings.setBuiltInZoomControls(true);
settings.setSupportZoom(true);

但是并不是设置了上面两个属性就可以了,这还要看网页自身是否支持缩放了,如果网页设置了如下meta:

<meta name="viewport" content="width=device-width,initial-scale=1.0,user-scalable=no, minimum-scale=1.0, maximum-scale=1.0">

这种情况下我们是不能手动缩放网页的,因为网页自身已经设置很明确了,用户不可以手动缩放页面,页面宽度等于屏幕宽度,最大缩放比例和最小缩放比例都是1.0倍。很多自适应网页都会设置该属性,如果加载进来的网页已经设置了自动缩放,但是却不可以缩放,不要太奇怪了。

网页自动缩放自屏幕大小

有时候我们发现自动加载进来的网页会出现横向滚动条,可能这种交互式UI设计师所不远见到的。当然了如果是加载进来的自己预先定义好的自适应页面,不会出现这种问题,但是如果是像一些分享的第三方网页的页面,Webview也提供了处理这种问题的机制。

setting.setUseWideViewPort(true);
setting.setLoadWithOverviewMode(true);

下面是加载同一页面设置之前与设置之后的对比图:

与JavaScript交互

像一些新闻详情页面可以不用与Java代码进行数据交互,但是在商品列表页或课程列表页,如果点击某一个列表需要跳转到商品明细或者章节列表,而目标页面都是Java语言处理的,这时候需要网页代码与Java代码进行数据交互。

如果与JavaScript进行数据交互,第一步需要设置:

webView.getSettings().setJavaScriptEnabled(true);

先举一个简单的示例说明一下,一个登陆Html页面需要与Java代码交互。

login.html网页代码如下:

<div class="layout">
    <input class="input-text" type="text" id="username" placeholder="用户名" value=""/>
    <input class="input-text" type="password" id="password" placeholder="密码" style="margin-bottom:40px;" value=""/>

    <input class="input-text" type="text" id="java_text"/>

    <button class="btn" id="btn-submit">提交</button>
    <button class="btn" id="btn-get">获取</button>
</div>
<script type="text/javascript">
    function $(_id){
        return document.getElementById(_id);
    }

    $("btn-submit").onclick=function(){
        var username=$("username").value;
        var password=$("password").value;
        if(username==""||password==""){
            alert("用户名或密码为空!");
            return;
        }
        //jsObject就是一个Java代码对象定义的实例名称
        jsObject.getMessageFromJs(username,password);
    }

    $("btn-get").onclick=function(){
        $("java_text").value=jsObject.getMessageFromJava();
    }
    //Java代码使用loadUrl()方式传值
    function showMessageFromJava(msg){
        $("java_text").value=msg;
    }

</script>

Java代码如下:

webView.addJavascriptInterface(new JsObject(),"jsObject");

private class JsObject{
	@JavascriptInterface
	public void getMessageFromJs(String username,String password){
		System.out.println(username+password);
	}

    //方式一传值到Html页面
	@JavascriptInterface
	public String getMessageFromJava(){
		return "I'm from Java";
	}

}
//方式二传值到Html页面
webView.loadUrl("javascript:showMessageFromJava('hello javascript')");

通过定义一个类进行数据交互是其中的一种方式,上述示例当我们点击提交按钮,如果用户名和密码不为空,在Java代码中就可以获取到网页中提交的用户名和密码。当点击Html页面中的获取按钮时,我们也可以将Java代码中传递的值赋值到最下面一个文本框中。方式二,我们可主动提交Java代码中的值到Html页面,这种方式我们点击Android中的一个本地控件,可以直接将值赋值到最下面文本框中。

WebView同步Cookie免登录

WebView同步Cookie是通过CookieManager实现的,用例子进行说明,客户端请求一个网页,如果已经登录则直接进入Html的welcome页面,否则跳转到Html的login页面。 本示例采用的是okhttp网络请求框架,服务端就是最基础的Servlet。

客户端同步Cookie主要代码如下,服务端代码就不在这里贴出来:

client.newCall(request).enqueue(new Callback() {
	public void onFailure(Call call, IOException e) {
	}

	public void onResponse(Call call, Response response) throws IOException {
		Headers headers= response.headers();
		String cookie=response.header("Set-Cookie");
		Message msg=new Message();
		msg.obj=cookie;
		handler.sendMessage(msg);
	}
});

private Handler handler=new Handler(){
    public void handleMessage(Message msg) {
        String cookie=msg.obj.toString();
        url="http://192.168.0.107:8080/cookie/webview";
        synSetCookies(MainActivity.this,url,cookie);
        webView.loadUrl(url);
        webView.setWebViewClient(new WebViewClient() {
            public boolean shouldOverrideUrlLoading(WebView view, String url) {
                view.loadUrl(url);
                return true;
            }
        });
    }
};

/**
 * CookieSyncManager api21过期
 * @param context
 * @param url
 */
private void synSetCookies(Context context, String url,String cookie) {
	CookieSyncManager.createInstance(context);
	CookieManager cookieManager = CookieManager.getInstance();
	cookieManager.setAcceptCookie(true);
	cookieManager.setCookie(url, cookie);
	CookieSyncManager.getInstance().sync();
}

当我们客户端成功进入welcome页面后,在/data/data/package_name/databases/目录下(Android版本不同可能目录名称不同)可以看到Cookie已经保存进数据库文件webviewCookiesChromium.db。

本篇文章就写到这里了,有关WebView缓存以及离线阅读后面有机会再介绍。目前市场上有一些第三个库也可以简化我们混合APP的开发,如JsBridge、PhoneGap,国内的APICloud以及360的H5流应用等。感觉很久没有更新文章了,今天补上一篇。本文非经验之谈,而是一个学习过程的记录笔记,如有错误还请及时指正以求共同进步。

示例源代码下载

参考资料

Android学习之 WebView使用小结

关于android WebViewClient和WebChromeClient

Android WebView使用深入浅出

webview onReceivedError 接收不到404

评论

您确定要删除吗?删除之后不可恢复