Android之webview详解
文章大纲
一、webview基本介绍
1.什么是webview
2.为什么要使用webview
3.webview基本操作
二、webview高级使用
1.webview状态
2.资源加载
3.webview加载优化
4.数据缓存
5.android 和 javascript 交互
6.网页前进与后退
7.内存管理
8.cookie操作
9.页面监听与拦截
10.定位设置
11.常见问题处理
三、参考文章
webview基本介绍
1.什么是webview
webview是android中的原生ui控件,主要用于在app应用中方便地访问远程网页或本地html资源。同时,webview也在android中充当java代码和js代码之间交互的桥梁。实际上,也可以将webview看做一个功能最小化的浏览器。
2.为什么要使用webview
目前很多公司的 app 使用一个 webview 作为网页加载, app 中的所有网页内容使用 html5 进行展示,这样只需要写一次 html5 代码,就可以在 android 和 ios 平台上运行,这就是所谓的跨平台 。随着 html5 的普及,很多 app 都会内嵌 webview 来加载 html5 页面,即 原生和html5 共存,这就是当下最流行的「 混合开发 」。html5 最大的优势是迭代方便, 只需要修改服务端的 html5 页面,app 会同步更新,无论是做活动推广 app 还是及时修复 bug 都带来的极大的便利。不过 html5 劣势也很明显,当网速不尽如人意时候,加载速度会很慢(不知道5g出现后结果会带来什么变革),也就是html5 加载受限于网络,没有原生控件流畅,用户体验相对较差, 所以目前完全使用 html5 开发 app 并没有成为主流。
3.webview基本操作
添加网络权限
在androidmanifest.xml中添加以下内容
<uses-permission android:name="android.permission.internet" />
新建webview_simple.xml布局
<?xml version="1.0" encoding="utf-8"?> <linearlayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent"> <webview android:id="@+id/webview" android:layout_width="match_parent" android:layout_height="match_parent" /> </linearlayout>
在androidmanifest.xml中注册webviewactivity
<activity android:name=".webviewactivity"></activity>
webviewactivity中代码如下:
import android.app.activity; import android.os.bundle; import android.support.v7.app.appcompatactivity; import android.webkit.webview; public class webviewactivity extends appcompatactivity { webview webview; @override protected void oncreate(bundle savedinstancestate) { super.oncreate(savedinstancestate); setcontentview(r.layout.webview_simple); webview = (webview) findviewbyid(r.id.webview); //加载网页链接 webview.loadurl("https://www.baidu.com"); } }
主页面跳转代码如下:
import android.content.intent; import android.os.bundle; import android.support.v7.app.appcompatactivity; import android.view.view; import android.widget.button; import butterknife.bindview; import butterknife.butterknife; import butterknife.onclick; public class mainactivity extends appcompatactivity { @bindview(r.id.button) button button; @override protected void oncreate(bundle savedinstancestate) { super.oncreate(savedinstancestate); setcontentview(r.layout.activity_main); butterknife.bind(this); } @onclick({r.id.button, r.id.button2}) public void onviewclicked(view view) { switch (view.getid()) { //简单webview加载 case r.id.button: startactivity(new intent(mainactivity.this, webviewactivity.class)); break; } } }
运行截图如下所示:
网页弹窗
//message:alert弹出窗口中的提示信息(提示或警告信息对话框,仅一个确认按钮) //result:向网页中的javascript代码反馈本次操作结果(result.confirm代表点击了确定按钮,result.cancel代表点击了取消按钮) public boolean onjsalert(webview view, string url, string message,jsresult result) ///message:confirm弹出窗口中的提示信息(确认对话框,有确认、取消两个按钮) //result:向网页中的javascript代码反馈本次操作结果(result.confirm代表点击了确定按钮,result.cancel代表点击了取消按钮) public boolean onjsconfirm(webview view, string url, string message,jsresult result) //message:prompt弹出窗口中的提示信息(输入信息对话框,有一个输入框,还有确认、取消两个按钮) //defaultvalue:输入框中的默认信息 //result:向网页中的javascript代码反馈本次操作结果(result.confirm代表点击了确定按钮,result.cancel代表点击了取消按钮) public boolean onjsprompt(webview view, string url, string message,string defaultvalue, jspromptresult result)
下面给出一个简单的处理方案,可以作为参考:
@override public boolean onjsalert(webview view, string url, string message, final jsresult result) { new alertdialog.builder(mainactivity.this) .settitle("jsalert") .setmessage(message) .setpositivebutton("确定", new dialoginterface.onclicklistener() { @override public void onclick(dialoginterface dialog, int which) { result.confirm(); } }) .setcancelable(false) .show(); return true; } @override public boolean onjsconfirm(webview view, string url, string message, final jsresult result) { new alertdialog.builder(mainactivity.this) .settitle("jsconfirm") .setmessage(message) .setpositivebutton("确定", new dialoginterface.onclicklistener() { @override public void onclick(dialoginterface dialog, int which) { result.confirm(); } }) .setnegativebutton("取消", new dialoginterface.onclicklistener() { @override public void onclick(dialoginterface dialog, int which) { result.cancel(); } }) .setcancelable(false) .show(); return true; } @override public boolean onjsprompt(webview view, string url, string message, string defaultvalue, final jspromptresult result) { final edittext edittext=new edittext(mainactivity.this); edittext.settext("默认数据");//设置默认数据 new alertdialog.builder(mainactivity.this) .settitle("jspromt") .setview(edittext)//为弹出窗口设置输入框 .setpositivebutton("确定", new dialoginterface.onclicklistener() { @override public void onclick(dialoginterface dialog, int which) { result.confirm(edittext.gettext().tostring());//向javascript传递输入值 } }) .setnegativebutton("取消", new dialoginterface.onclicklistener() { @override public void onclick(dialoginterface dialog, int which) { result.cancel(); } }) .setcancelable(false) .show(); return true; }
二、webview高级使用
1.webview状态
//激活webview为活跃状态,能正常执行网页的响应 webview.onresume() ; //当页面被失去焦点被切换到后台不可见状态,需要执行onpause //通过onpause动作通知内核暂停所有的动作,比如dom的解析、plugin的执行、javascript执行。 webview.onpause(); //当应用程序(存在webview)被切换到后台时,这个方法不仅仅针对当前的webview而是全局的全应用程序的webview //它会暂停所有webview的layout,parsing,javascripttimer。降低cpu功耗。 webview.pausetimers() //恢复pausetimers状态 webview.resumetimers(); //销毁webview //在关闭了activity时,如果webview的音乐或视频,还在播放。就必须销毁webview //但是注意:webview调用destory时,webview仍绑定在activity上 //这是由于自定义webview构建时传入了该activity的context对象 //因此需要先从父容器中移除webview,然后再销毁webview: rootlayout.removeview(webview); webview.destroy();
2.资源加载
webview可以加载多种资源,包括本地资源和远程资源,同时也有多种用于加载资源的方法。
加载assets中的资源
新建assets文件夹,再新建.html文件
在activity中添加以下代码:
webview.loadurl("file:///android_asset/test.html");//加载本地assets文件夹下的资源
加载res中的资源
webview.loadurl(``"[file:///android_res/mipmap/ic_launcher.png](file:///android_res/mipmap/ic_launcher.png)"``);//加载本地res文件夹下的图片 webview.loadurl(``"[file:///android_res/raw/ic_launcher.png](file:///android_res/raw/ic_launcher.png)"``);//加载本地res文件夹下raw文件夹下的图片 webview.loadurl(``"[file:///android_res/raw/test.html](file:///android_res/raw/test.html)"``);//加载本地res文件夹下raw文件夹下的html文件
加载sdcard中的资源
webview.loadurl(``"file:/sdcard/test.html");//加载本地sdcard下的资源 webview.loadurl(``"[file:///sdcard/test.html](file:///sdcard/test.html)");//加载本地sdcard下的资源 webview.loadurl(``"[content://com.android.htmlfileprovider/sdcard/test.html
请求远程网页方式
loaddata(以字符串形式加载html片段)
//data:html片段 //mimetype:数据类型,如"text/html" //encoding:数据编码,有两种可选值("base64"和其他任何值),分别代表base64编码和url编码 public void loaddata(string data, string mimetype, string encoding)
loadurlwithbaseurl(以字符串形式加载html片段)
//baseurl:基础url,传入null相当于传入了"about:blank" //data:html片段 //mimetype:数据类型,如"text/html" //encoding:数据编码,有两种可选值("base64"和其他任何值),分别代表base64编码和url编码 //historyurl:历史url public void loaddatawithbaseurl(string baseurl, string data,string mimetype, string encoding, string historyurl)
posturl(以post请求的形式访问url)
//postdata:本次post请求携带的数据,必须是application/x-www-form-urlencoded编码 //如果传入的url不是一个远程网页地址,那么最终将通过loadurl方法加载这个url,同时postdata参数会被忽略。 public void posturl(string url, byte[] postdata)
3.webview加载优化
设置页面自适应屏幕
websettings.setusewideviewport(true); websettings.setloadwithoverviewmode(true);
缩放功能
websettings.setsupportzoom(true);//启用缩放功能 websettings.setbuiltinzoomcontrols(true);//使用webview内置的缩放功能 websettings.setdisplayzoomcontrols(false);//隐藏屏幕中的虚拟缩放按钮
温馨提示:setdisplayzoomcontrols(true)在某些系统版本中可能会导致应用出现意外崩溃。
若加载的 html 里有js 在执行动画等操作,会造成资源浪费(cpu、电量)
在 onstop 和 onresume 里分别把 setjavascriptenabled() 给设置成 false 和 true 即可
页面加载过程优化
处理各种通知 & 请求事件
//步骤1. 定义webview组件 webview webview = (webview) findviewbyid(r.id.webview1); //步骤2. 选择加载方式 //方式1. 加载一个网页: webview.loadurl("http://www.google.com/"); //步骤3. 复写shouldoverrideurlloading()方法,使得打开网页时不调用系统浏览器, 而是在本webview中显示 webview.setwebviewclient(new webviewclient(){ @override public boolean shouldoverrideurlloading(webview view, string url) { view.loadurl(url); return true; } });
onpagestarted()
作用:开始载入页面调用的,我们可以设定一个loading的页面,告诉用户程序在等待网络响应。
webview.setwebviewclient(new webviewclient(){ @override public void onpagestarted(webview view, string url, bitmap favicon) { //设定加载开始的操作 } });
onpagefinished()
作用:在页面加载结束时调用。我们可以关闭loading 条,切换程序动作。
webview.setwebviewclient(new webviewclient(){ @override public void onpagefinished(webview view, string url) { //设定加载结束的操作 } });
onloadresource()
作用:在加载页面资源时会调用,每一个资源(比如图片)的加载都会调用一次。
webview.setwebviewclient(new webviewclient(){ @override public boolean onloadresource(webview view, string url) { //设定加载资源的操作 }});
onreceivederror()
作用:加载页面的服务器出现错误时(如404)调用。app里面使用webview控件的时候遇到了诸如404这类的错误的时候,若也显示浏览器里面的那种错误提示页面就显得很丑陋了,那么这个时候我们的app就需要加载一个本地的错误提示页面,即webview如何加载一个本地的页面
//步骤1:写一个html文件(error_handle.html),用于出错时展示给用户看的提示页面 //步骤2:将该html文件放置到代码根目录的assets文件夹下 //步骤3:复写webviewclient的onrecievederror方法 //该方法传回了错误码,根据错误类型可以进行不同的错误分类处理 webview.setwebviewclient(new webviewclient(){ @override public void onreceivederror(webview view, int errorcode, string description, string failingurl){ switch(errorcode) { case httpstatus.sc_not_found: view.loadurl("file:///android_assets/error_handle.html"); break; } } });
onreceivedsslerror()
作用:处理https请求,webview默认是不处理https请求的,页面显示空白,需要进行如下设置:
webview.setwebviewclient(new webviewclient() { @override public void onreceivedsslerror(webview view, sslerrorhandler handler, sslerror error) { handler.proceed(); //表示等待证书响应 // handler.cancel(); //表示挂起连接,为默认方式 // handler.handlemessage(null); //可做其他处理 } });
4.数据缓存
当加载 html 页面时,webview会在/data/data/包名目录下生成 database 与 cache 两个文件夹
请求的 url记录保存在 webviewcache.db,而 url的内容是保存在 webviewcache 文件夹下,设置是否启动缓存方法如下:
webview.getsettings().setcachemode(websettings.load_cache_else_network); 缓存模式如下: // load_cache_only: 不使用网络,只读取本地缓存数据 // load_default: (默认)根据cache-control决定是否从网络上取数据。 // load_no_cache: 不使用缓存,只从网络获取数据. // load_cache_else_network,只要本地有,无论是否过期,或者no-cache,都使用缓存中的数据。 //webview.getsettings().setcachemode(websettings.load_no_cache);不使用缓存
结合使用(离线加载)
if (netstatusutil.isconnected(getapplicationcontext())) { websettings.setcachemode(websettings.load_default);//根据cache-control决定是否从网络上取数据。 } else { websettings.setcachemode(websettings.load_cache_else_network);//没网,则从本地获取,即离线加载 } websettings.setdomstorageenabled(true); // 开启 dom storage api 功能 websettings.setdatabaseenabled(true); //开启 database storage api 功能 websettings.setappcacheenabled(true);//开启 application caches 功能 string cachedirpath = getfilesdir().getabsolutepath() + app_cacahe_dirname; websettings.setappcachepath(cachedirpath); //设置 application caches 缓存目录 //注意: 每个 application 只调用一次 websettings.setappcachepath(),websettings.setappcachemaxsize()
清理缓存
//清除网页访问留下的缓存 //由于内核缓存是全局的因此这个方法不仅仅针对webview而是针对整个应用程序. webview.clearcache(true); //清除当前webview访问的历史记录 //只会webview访问历史记录里的所有记录除了当前访问记录 webview.clearhistory(); //这个api仅仅清除自动完成填充的表单数据,并不会清除webview存储到本地的数据 webview.clearformdata(); 另外一种方式: //删除缓存文件夹 file file = cachemanager.getcachefilebasedir(); if (file != null && file.exists() && file.isdirectory()) { for (file item : file.listfiles()) { item.delete(); } file.delete(); } //删除缓存数据库 context.deletedatabase("webview.db"); context.deletedatabase("webviewcache.db");
5.android 和 javascript 交互
安卓代码
websettings settings = mwebview.getsettings(); settings.setjavascriptenabled(true);//开启javascript mwebview.loadurl("file:///android_asset/keithxiaoy.html");//加载本地网页 mwebview.setwebchromeclient(new webchromeclient());//此行代码可以保证javascript的alert弹窗正常弹出 //核心方法, 用于处理javascript被执行后的回调 mwebview.addjavascriptinterface(new jscallback() { @javascriptinterface//注意:此处一定要加该注解,否则在4.1+系统上运行失败 @override public void onjscallback() { system.out.println("javascript调用android啦"); } }, "keithxiaoy");//参1是回调接口的实现;参2是javascript回调对象的名称 //定义回调接口 public interface jscallback { public void onjscallback(); }
android 调用 javascript
//直接使用webview加载js就可以了 mwebview.loadurl("javascript:wave()");
html代码如下
<head> <meta http-equiv="content-type" content="text/html; charset=utf-8" /> </head> <script language="javascript"> /* this function is invoked by the activity */ function wave() { alert("android调用javascript"); } </script> <body> <!-- javascript调用android代码 --> <a onclick="window.demo.onjscallback()"><div style="width:80px; margin:0px auto; padding:10px; text-align:center; border:2px solid #202020;" > ![](android_normal.png)<br> click me! </div></a> </body> </html>
注意:
javascript 回调的方法的书写格式: onclick="window.demo.onjscallback()格式是: windows.js 回调对象的名称(要和 java 代码中设置的一致)。回调方法名称(要和 java 代码中设置的一致)
javascript 调用 android 的方式具有版本兼容问题。经测试,在 2.2、4.0+ 系统上运行稳定,可以正常调用,但是在 2.3 系统上运行时出现崩溃。原因是底层进行 jni 调用时,把一个 java 中的 string 对象当数组来访问了,最终导致虚拟机崩溃。基本算是一个比较严重的 bug,没办法解决。所以如果说用 webview 组件想在 javascript 和 java 互调就没办法适配所有机型。
6.网页前进与后退
在安卓手机中,back 键控制网页后退,会关闭整个webview,解决方法是在当前 activity 中处理获取back键,改造成网络的返回上一步
webview.goback();//跳到上个页面 webview.goforward();//跳到下个页面 webview.cangoback();//是否可以跳到上一页(如果返回false,说明已经是第一页) webview.cangoforward();//是否可以跳到下一页(如果返回false,说明已经是最后一页)
7.内存管理
直接 new webview 并传入 application context 代替在 xml 里面声明以防止 activity 引用被滥用,能解决90+%的 webview 内存泄漏。
vweb = new webview(getcontext().getapplicationcontext()); container.addview(vweb);
销毁 webview
if (vweb != null) { vweb.setwebviewclient(null); vweb.setwebchromeclient(null); vweb.loaddatawithbaseurl(null, "", "text/html", "utf-8", null); vweb.clearhistory(); ((viewgroup) vweb.getparent()).removeview(vweb); vweb.destroy(); vweb = null; }
8.cookie操作
cookie 设置
cookiesyncmanager.createinstance(this); cookiemanager cookiemanager = cookiemanager.getinstance(); cookiemanager.setacceptcookie(true); string cookie = "name=xxx;age=18"; cookiemanager.setcookie(url, cookie); cookiesyncmanager.getinstance().sync();
获取 cookie
cookiemanager cookiemanager = cookiemanager.getinstance(); string cookie = cookiemanager.getcookie(url);
清除 cookie
cookiesyncmanager.createinstance(context); cookiemanager cookiemanager = cookiemanager.getinstance(); cookiemanager.removeallcookie(); cookiesyncmanager.getinstance().sync();
9.页面监听与拦截
监听网页加载进度
public void onprogresschanged(webview view, int newprogress)
onprogresschanged会在网页加载过程中多次触发。当newprogress的值为100时,可以认为当前网页已经加载完毕。因此,通过这个方法判断页面是否加载完成比使用上文提到的onpagefinished方法更准确。同时,由于这个方法在回调中会不断获得最新的加载进度,因此我们可以借助这个方法实现自定义的加载进度条。
这里给出一个简单的思路:在webview的上方添加一个progressbar控件,并默认隐藏。在onpagestarted方法中显示progressbar,并在onprogresschanged方法回调时更新progressbar的进度值。当onprogresschanged方法中的newprogress达到100时,就隐藏这个progressbar。需要注意,为了在页面加载出错时也能正确隐藏进度条,也应该在onreceivederror方法中隐藏progressbar。
10.定位设置
websettings.setgeolocationenabled(true);//允许网页执行定位操作
如果要禁用网页的定位功能,传入false作为参数即可。需要注意,这个方法只是允许网页执行定位操作,但是最终定位操作的实现还是会委托给android应用处理。因此,为了保证定位功能正常执行,需要满足以下两点:
android应用需要获取定位权限。需要在androidmanifest文件中声明android.manifest.permission.access_coarse_location和android.manifest.permission.access_fine_location两个权限。 需要为webview设置webchromeclient,并重写webchromeclient的ongeolocationpermissionsshowprompt方法。这个方法会在网页中的javascript代码执行定位操作时触发。需要注意,android6.0及以上引入了运行时权限的概念。定位属于危险权限,需要在使用时手动获取。因此我们可以在这个回调方法中弹出一个请求定位的提示对话框(alertdialog),在用户选择确定后获得相应权限。
11.常见问题处理
loaddata加载中文数据出现乱码
问题描述:使用loaddata方法加载含有中文的数据时,中文显示为乱码。
解决方案:使用loaddatawithbaseurl方法代替loaddata加载数据,不会出现乱码问题。 为loaddata的mimetype参数传入“text/html;charset=utf-8”,也可以解决乱码问题。
密码明文存储问题
问题描述:在android 4.3(api 18)以前,用户在webview加载的网页中输入密码后,系统会弹出对话框询问用户是否需要保存密码。如果用户选择保存,那么密码将会以明文的形式保存在本地,显然这是一个巨大的安全隐患。
解决方案:webview是否保存密码是由websettings的setsavepassword方法决定的。因此,我们只要调用这个方法并传入false,就可以避免明文储存的安全问题了。在android 4.3及以上的版本,setsavepassword方法已经被弃用,webview也不会默认保存密码,因此不再需要进行修复。
weview出现oom影响主进程
问题描述:由于webview默认运行在应用进程中,如果webview加载的数据过大(例如加载大图片),就可能导致oom问题,从而影响应用主进程。
解决方案:为了避免webview影响主进程,可以尝试将webview所在的activity运行在独立进程中。这样即使webview出现了oom问题,应用主进程也不会受到影响。具体做法也很简单,只要在androidmanifest文件中为相应的activity设置process属性即可。
webview后台耗电问题
问题描述:在某些情况下,即使activity已经退出,webview依旧占据着内存空间,这会导致设备耗电量增加。
解决方案:在上文提到过将webview运行在独立进程中,然后只要在activity的ondestroy方法中调用system.exit(0)退出虚拟机,就可以避免webview继续占据内存空间.
视频或音频在退出activity后继续播放的问题
问题描述:在webview加载的网页中播放音乐或视频,然而当前应用进入后台后音乐或视频还在继续播放。
解决方案:在activity的onpause方法中暂停webview,然后在onresume方法中恢复webview。
protected void onpause() { if(webview!=null){//暂停webview webview.onpause(); webview.pausetimers(); } super.onpause(); } @override protected void onresume() { if(webview!=null){//恢复webview webview.onresume(); webview.resumetimers(); } super.onresume(); }   
开启硬件加速导致的闪烁问题
问题描述:在应用开启硬件加速后,webview可能在加载过程出现闪烁现象。
解决方案:为webview关闭硬件加速功能。
webview.setlayertype(view.layer_type_software,null);
https请求失败的解决方案
问题描述:在使用webview加载https协议的网页或资源时,如果该网站的安全证书不被android认可,就会出现无法成功加载的问题。
解决方案:重写webviewclient的onreceivedsslerror方法,设置其接受所有网站的安全证书。
public void onreceivedsslerror(webview view, sslerrorhandler handler, sslerror error) { handler.proceed();//接受所有网站证书 }
webview中http和https混合使用的问题
问题描述:在android 5.0及以上,webview可能在加载混合使用http和https的网页时出现异常。比如在一个https的安全网页中加载使用http协议的资源将会失败。
解决方案:在android 5.0后利用websettings设置webview支持http和https混合内容模式。
//方式1 websettings.setmixedcontentmode(websettings.mixed_content_compatibility_mode); //方式2 websettings.setmixedcontentmode(websettings.mixed_content_always_allow);
需要注意,mixed_content_always_allow这个模式是不安全的,建议先使用mixed_content_compatibility_mode模式。这个模式会尝试以安全的方式加载部分http资源,另一部分http资源则不会被加载。资源是否能被加载的判断依据可能会随着版本的不同而改变,因此需要根据实际情况决定是否采用这一模式。
setdisplayzoomcontrols引起的崩溃问题
问题描述:我们知道,setdisplayzoomcontrols(true)方法会允许显示系统缩放按钮,这个缩放按钮会在每次出现后的几秒内逐渐消失。但是在部分系统版本中,如果在缩放按钮消失前退出了activity,就可能引起应用崩溃。
解决方案:调用setdisplayzoomcontrols(false)方法不显示系统缩放按钮,反正使用手势捏合动作就可以实现网页的缩放功能了。如果确实需要使用缩放按钮,就需要在activity的ondestroy方法中隐藏webview。
webview.setvisibility(view.gone);
三、参考文章
上一篇: 王国军:未来数据库应迎合云和大数据
下一篇: 大数据的核心是大量数据的分析能力