Android-WebView
Android应用层开发有两种方式:客户端开发和HTML5移动端开发,所谓的HTML5开发就是用HTML5+CSS+JS来构建一个网页版的应用,这中间的媒介就是webview,而web和网页端可以通过js来进行交互。相比普通客户端开发,HTML5移动端有一个优势,可以用百分比来布局,而且如果HTML5端由什么大的改变,不用像客户端那样去重新下载一个APP,只要修改下网页即可,当然HTML5也有一个缺点,就是性能问题,数据积累,耗电问题,还有闪屏等。
webview:android内置webkit内核的高性能浏览器,而Webview则是在这个基础上进行封装后的一个控件,可以简单理解为一个嵌套到界面上的浏览器控件。
webview的常用方法
webview.onResume()
激活WebView为活跃状态,能正常执行网页的相应
webview.onPause()
当页面被失去焦点切换到后台为不可见状态时,执行此方法,通过onPause动作通知内核暂停所有的动作,比如DOM的解析、plugin的执行、javascript的执行
webview.pauseTimes()
当应用程序被切换到后台时这个方法不仅仅针对当前的webview而是全局的全应用程序的webview,它会暂停所有webview的layout,parsing,javascripttimer,降低cpu功耗。
webview.resumeTimers()
恢复pauseTimers状态
rootLayout.removeView(webview);
webview.destroy();
销毁webview,在关闭了Activity时,如果webview的音乐或视频还在播放,就必须销毁webview,但是注意:webview调用destroy时,webview仍绑定在Activity上,这是由于自定义webview构建时传入了该Activity的context对象,因此需要先从父容器中移除webview,然后再销毁webview。
webview.canGoBack()
是否可以后退
webview.goBack()
后退网页
webview.canGoForward()
是否可以前进
webview.goForward()
前进网页
webview.goBackOrForward(int steps)
以当前的index为起始点前进或者后退到历史记录中指定的steps,如果steps为负数则为后退,正数则为前进
常见用法:Back键控制网页后退
在不做任何处理的前提下,浏览网页时点击系统的back键,整个浏览器会调用finish()而结束自身,如果想要实现网页的回退而不是退出浏览器,那么要重写onKeyDown()方法。
image.png
webview.clearCache(true)
清除网页留下的缓存,由于内核缓存时全局的因此这个方法不仅仅针对webview而是针对整个应用程序。
webview.clearHistory()
清除当前webview访问的历史记录,只会webview访问历史记录里的所有记录,除了当前访问的记录
webview.clearFormData()
这个方法仅仅清除自动完成填充的表单数据,并不会清除webview存储到本地的数据。
WebSettings类
作用:对webview进行配置和管理
配置步骤:
1. 添加访问网络的权限
<uses-permission android:name="android.permission.INTERNET"/>
2. 生成一个webview组件
webView = (WebView) findViewById(R.id.webview);
3. 利用webSettings的子类进行配置
webSettings = webView.getSettings();
//如果访问的页面中要与Javascript交互,则webview必须设置支持Javascript
webSettings.setJavaScriptEnabled(true);
//设置自适应屏幕,两者合用
webSettings.setUseWideViewPort(true); //将图片调整到适合webview的大小
webSettings.setLoadWithOverviewMode(true); // 缩放至屏幕的大小
//缩放操作
webSettings.setSupportZoom(true); //支持缩放,默认为true。是下面那个的前提。
webSettings.setBuiltInZoomControls(true); //设置内置的缩放控件。若为false,则该WebView不可缩放
webSettings.setDisplayZoomControls(false); //隐藏原生的缩放控件
//其他细节操作
webSettings.setCacheMode(WebSettings.LOAD_CACHE_ELSE_NETWORK); //关闭webview中缓存
webSettings.setAllowFileAccess(true); //设置可以访问文件
webSettings.setJavaScriptCanOpenWindowsAutomatically(true); //支持通过JS打开新窗口
webSettings.setLoadsImagesAutomatically(true); //支持自动加载图片
webSettings.setDefaultTextEncodingName("utf-8");//设置编码格式
WebViewClient类
作用:处理各种通知和请求事件
常用方法:
1. shouldOverrideUrlLoading()
打开网页时不调用系统浏览器,而是在本webview中显示,在网页上的所有加载都经过这个方法。
image.png
2. onPageStarted()
开始载入页面时调用,可以设定一个loading的页面,告诉用户程序正在等待网络响应。
image.png
3. onPageFinished()
在页面加载结束时调用,可以关闭loading条,切换程序动作。
image.png
4. onReceivedError()
加载页面的服务器出现错误时调用,App里面使用webview控件的时候遇到了诸如404这类的错误的时候,若也显示浏览器里面的那种错误提示页面就会显得很丑陋,那么这个时候我们的app就需要加载一个本地的错误提示页面,即webview如何加载一个本地的页面。
image.png
WebChromeClient类
作用:辅助webview处理javascript的对话框、网站图标、网站标题等。
常用方法:
1. onProgressChanged()
获得网页的加载进度并显示
image.png2. onReceivedTitle()
获取web页中的标题
image.png
避免webview内存泄露
不在xml中定义webview,而是在需要的时候在Activity中创建,并且Context使用getApplicationContext(),在Activity销毁webview的时候,先让webview加载null内容,然后移除webview,再销毁webview,最后置空。
Android通过webview与JS进行交互
两种方式:
方式一:通过webview的loadUrl()
方式二:通过webview的evaluateJavascript()
需要将调用的本地JS代码以.html的格式放到src/main/assets文件夹下面,在实际情况中更多的是调用远程js代码,将加载的js代码路径改成url即可。例如我们的本地js代码如下,JS中的calljs()方法弹出一个弹框,并返回一个值111,我们看下在android中怎么调用:
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>jsweb</title>
<script>
function calljs() {
alert("android调用了js的alert");
return 111;
}
</script>
</head>
<body>
<div style="background:#f00">我是一个块</div>
</body>
</html>
方式一:通过webview的loadUrl()
设置一个Button,当点击按钮时,即调用js代码,在此之前要对webview进行一些配置:
//如果访问的页面中要与Javascript交互,则webview必须设置支持Javascript
webSettings.setJavaScriptEnabled(true);
//支持通过JS打开新窗口
webSettings.setJavaScriptCanOpenWindowsAutomatically(true);
载入JS代码:
webView.loadUrl("file:///android_asset/jsweb.html");
重写button的onClick方法
bt.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
webView.post(new Runnable() {
@Override
public void run() {
//同步
webView.loadUrl("javascript:calljs()");
}
});
}
});
Runnable是一个接口,不是一个线程,一般线程会实现Runnable,所以如果我们使用匿名内部类是运行在UI线程的,如果我们使用实现这个Runnable接口的线程类,则是运行在对应线程的。
个人的理解:webview.post()的作用很明确,就是从其他线程访问主线程,比如在onCreat()方法中获取某个view的宽高,而直接view.getWidth获取到的值为0,为啥呢,因为view显示到界面上需要经历onMeasure、onLayout、onDraw三个过程,而view的宽高是在onLayout阶段才确定的,在onCreate中并不能保证view已经执行到了onLayout方法,也就是说Activity的生命周期与view的绘制流程并不是一一绑定的,为啥调用post方法就能起作用呢,首先MessageQueue是按顺序处理消息的,而在setContentView()后队列中会包含一条询问是否完成布局的消息,view.post()方法把action添加到队列尾部,保证了在onLayout结束后才执行。在post(Runnable action)方法里,View获得当前线程(即UI线程)的Handler,然后将action对象post到Handler里,在Handler里,它将传递过来的action对象包装成一个Message,然后将其投入UI线程的消息循环中,在Handler再次处理该Message时,有一条分支就是为它所设,直接调用Runnable的run方法,而此时,已经路由到UI线程里,此时可以毫无顾忌的更新UI,在这种情况下,由于不是在新的线程中使用,所以不要做复杂的计算逻辑。
方式二:通过webview的evaluateJavascript()方法
优点:更高效、使用更简洁。该方法的执行不会使页面刷新。
两个问题:(已解决)
1. loadUrl()是同步的?evaluateJavaScript()是异步的?
(好像是的)
2. webview.post(new Runnable())到底开没开子线程?
(没有开子线程,而是为了防止在子线程中使用webview.loadUrl(),而使用post方法调回主线程)
bt.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
webView.post(new Runnable() {
@Override
public void run() {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
webView.evaluateJavascript("calljs()", new ValueCallback<String>() {
@Override
public void onReceiveValue(String s) {
Log.e("eee",s);
}
});
}
}
});
}
});
两种方法对比:
image.png
JS通过webview与Android进行交互
三种方式:
方式一:通过webview的addJavascriptInterface()方法进行对象映射
方式二:通过webview的shouldOverrideUrlLoading()方法回调拦截Url
方式三:通过webChromeClient的onJsAlert()、onJsConfirm()、onJsPrompt()方法回调拦截JS对话框
方式一:通过webview的addJavascriptInterface()
首先,要定义一个与JS对象有映射关系的Android类,继承Object类,并加入@JavaScriptInterface注解
public class AndroidJs extends Object {
@JavascriptInterface
public void hello(String msg){
Log.e("eee",msg);
}
}
nbsp;nbsp;nbsp;nbsp;同样将需要调用的JS代码以.html格式存放到src/main/assets文件夹里,这里我们定义一个button,在点击事件中,androidcall对象是js中的对象,而在activity代码中通过addJavascriptInterface()将java对象映射到js对象上。
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>jsweb</title>
<script>
function calljs() {
alert("android调用了js的alert");
return 111;
}
function callandroid(){
androidcall.hello("js调用了android的方法");
}
</script>
</head>
<body>
<div style="background:#f00">我是一个块</div>
<button style="height:200px;width:200px" onclick="callandroid()">js调用android</button>
</body>
</html>
在activity代码中,通过webview设置Android与JS之间的映射。
webView.addJavascriptInterface(new AndroidJs(),"androidcall");
在AndroidJs对象中写hello()方法,执行相应的操作。
优点:使用简单,仅将Android对象和JS对象映射即可
缺点:存在严重的漏洞问题。
方式二:通过WebViewClient()中的shouleOverrideUrlLoading()方法回调拦截url
原理:
Android通过webviewClient的回调方法shouldOverrideUrlLoading()拦截url;解析该url的协议;如果检测到是预先约定好的协议,就调用相应的方法。
首先,在js中预定所需要的url协议
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>jsweb</title>
<script>
function calljs() {
alert("android调用了js的alert");
return 111;
}
function callandroid1(){
androidcall.hello("js调用了android的方法");
}
function callandroid2(){
document.location="js://webview?arg1=123&arg2=456";
}
</script>
</head>
<body>
<div style="background:#f00">我是一个块</div>
<button style="height:200px;width:200px" onclick="callandroid1()">js调用android</button>
<button style="height:200px;width:200px" onclick="callandroid2()">js调用android</button>
</body>
</html>
重写WebViewClient()的shouleOverrideUrlLoading()方法,根据协议的参数,判断是否是所需要的url,一般根据scheme(协议格式)和authority(协议名)这两个参数判断,约定好的url格式是:”js://webview?arg1=123&arg2=456”,下面的代码分别对协议格式和协议名进行解析。
webView.setWebViewClient(new WebViewClient() {
@Override
public boolean shouldOverrideUrlLoading(WebView view, String Url) {
Uri uri = Uri.parse(Url);
if(uri.getScheme().equals("js")){
if(uri.getAuthority().equals("webview")){
String aa = uri.getQueryParameter("arg1");
Log.e("eee",aa);
}
return true;
}
return super.shouldOverrideUrlLoading(view, Url);
}
优点:不存在方式一的漏洞
缺点:JS获取Android方法的返回值比较复杂(怎么返回呢,看看好不好),参数bb为android返回给js的参数。
webView.evaluateJavascript("result("+bb+")", new ValueCallback<String>() {
@Override
public void onReceiveValue(String s) {
Log.e("eee",s);
}
});
JS代码如下:
function result(data) {
document.getElementById("div2").innerHTML=data;
}
方式三:通过WebClient的onJsAlert()、onJsConfirm()、onJsPrompt()方法回调拦截JS对话框alert()、confirm()、prompt()消息
原理:
Android通过WebChromeClient的onJsAlert()、onJsConfirm()、onJsPrompt()方法回调分别拦截JS对话框,得到他们的消息内容,然后解析即可。常用的拦截是JS的输入框(prompt),因为只有prompt()可以返回任意类型的值,操作最全面方便,更加灵活,而alert()对话框没有返回值,confirm()对话框只能返回两种状态(确定/取消)两个值。
同样,在JS代码中定义规则
function callandroid3(){
var msg = prompt("js://prompt?msg1=222&msg2=555");
document.getElementById("div2").innerHTML=msg;
}
在Activity中当使用webview.loadUrl()加载了JS代码后,就会触发回调onJsPrompt()方法,重写此方法如下:
webView.setWebChromeClient(new WebChromeClient(){
@Override
public boolean onJsPrompt(WebView view, String url, String message, String defaultValue, JsPromptResult result) {
Uri uri = Uri.parse(message);
if(uri.getScheme().equals("js")){
if(uri.getAuthority().equals("prompt")){
Log.e("eee",uri.getQueryParameter("msg1"));
Log.e("eee",uri.getQueryParameter("msg2"));
result.confirm("哈哈哈哈哈哈");
}
return true;
}
return super.onJsPrompt(view, url, message, defaultValue, result);
}
并在代码中通过result.confirm()给JS返回值,在上述JS代码中,msg的值就是得到的返回值(我就是觉得溜溜溜!)
实现的结果是这样的:黄色div原来的文字内容是“巴拉巴拉巴拉”,点击第三个按钮之后,文字内容变为“溜溜溜溜溜溜”。
image.png
'
image.png
给出三种方式的运用场景
image.png