1. 简介
WebView是一个基于webkit引擎、展现web页面的控件。
Android的Webview在低版本和高版本采用了不同的webkit版本内核,4.4后直接使用了Chrome。
2. 作用
WebView控件功能强大,除了具有一般View的属性和设置外,还可以对url请求、页面加载、渲染、页面交互进行强大的处理。
3. 用法
1. 添加权限:AndroidManifest.xml中设置权限"android.permission.INTERNET",否则会出Web page not available错误。
2. 在要Activity中生成一个WebView组件:WebView webView = new WebView(this);或者可以在activity的layout文件里添加webview控件
3. 设置WebView基本信息:
mWebView = (WebView) findViewById(R.id.wb);
mWebView.getSettings().setJavaScriptEnabled(true);//支持javascript
mWebView.requestFocus();//触摸焦点起作用mWebView.setScrollBarStyle(WebView.SCROLLBARS_OUTSIDE_OVERLAY);//取消滚动条
mWebView.getSettings().setJavaScriptCanOpenWindowsAutomatically(true);//设置允许js弹出alert对话框
//load本地
mWebView.loadUrl("file:///android_asset/hellotest.html");
//load在线
//mWebView.loadUrl("http://www.google.com");
//js访问android,定义接口
mWebView.addJavascriptInterface(new JsInteration(), "control");
//设置了Alert才会弹出,重新onJsAlert()方法return true可以自定义处理信息
mWebView.setWebChromeClient(new WebChromeClient() {
@Override
public boolean onJsAlert(WebView view, String url, String message, JsResult result) {
//return super.onJsAlert(view, url, message,result);
Toast.makeText(MainActivity.this, message, Toast.LENGTH_LONG).show();
return true;
}});
4. 设置WebView要显示的网页:互联网用:webView.loadUrl("http://www.google.com");本地文件用:webView.loadUrl("file:///android_asset/XX.html");本地文件存放在:assets文件中
5. 如果希望点击链接由自己处理,而不是新开Android的系统browser中响应该链接。给WebView添加一个事件监听对象(WebViewClient)并重写其中的一些方法: shouldOverrideUrlLoading:对网页中超链接按钮的响应。当按下某个连接时WebViewClient会调用这个方法,并传递参数
mWebView.setWebViewClient(new WebViewClient(){
@Override
public booleanshouldOverrideUrlLoading(WebView view, String url) {
view.loadUrl(url);
return true;
}
});
6. 处理https请求
webView默认是不处理https请求的,页面显示空白,需要进行如下设置:
webView.setWebViewClient(new WebViewClient() {
@Override public void onReceivedSslError(WebView view,
SslErrorHandler handler, SslError error) {
handler.proceed();
// handler.cancel();
// handler.handleMessage(null); } });
onReceivedSslError为webView处理ssl证书设置
其中handler.proceed();表示等待证书响应
handler.cancel();表示挂起连接,为默认方式
handler.handleMessage(null);可做其他处理
另外还有其他一些可重写的方法
1,接收到Http请求的事件onReceivedHttpAuthRequest(WebView view,HttpAuthHandler handler, String host, String realm)
2,载入页面完成的事件public void onPageFinished(WebView view,String url){ }
同样道理,我们知道一个页面载入完成,于是我们可以关闭loading条,切换程序动作。
3,载入页面开始的事件public void onPageStarted(WebView view,String url, Bitmap favicon) { }
这个事件就是开始载入页面调用的,通常我们可以在这设定一个loading的页面,告诉用户程序在等待网络响应。通过这几个事件,我们可以很轻松的控制程序操作,一边用着浏览器显示内容,一边监控着用户操作实现我们需要的各种显示方式,同时可以防止用户产生误操作。
7. 如果用webview点链接看了很多页以后,如果不做任何处理,点击系统“Back”键,整个浏览器会调用finish()而结束自身,如果希望浏览的网页回退而不是退出浏览器,需要在当前Activity中处理并消费掉该Back事件。覆盖Activity类的onKeyDown(int keyCoder,KeyEvent event)方法。
@Overridepublicboolean onKeyDown(int keyCode, KeyEvent event) {
if (keyCode == KeyEvent.KEYCODE_BACK &&webView.canGoBack()) {
webView.goBack();// 返回前一个页面
return true;
}
returnsuper.onKeyDown(keyCode, event);
}
8. js与java代码互调用
java调js:调用webView.load("javascript:someFunction()");
这样可以调用webView里页面上的全局方法。这不是什么新鲜东西,你在网页中也可以这么做,试试在浏览器地址栏输入javascript:alert("427studio");也可以在浏览器地址栏里调用全局方法。
//调用js方法,第一个参数是js方法名,后面的参数是js方法的参数列表
void doJs(String function, Object... params){
StringBuilder result = newStringBuilder();
result.append("javascript:").append(function).append("(");
for(int i = 0; i
if(i
}
}
result.append(")");
String jsStr =result.toString();
webView.loadUrl(jsStr);
}
}
js调java:调用webView.addJavascriptInterface(somePOJO,"varName");
让一个java对象成为webview里面网页的window对象的varName属性,就好像执行了window.varName = somePOJO一样,因为window是全局上下文,js即可以用访问全局变量的方式访问这个java对象了,然后调用这个对象的函数即可,如果somePOJO这个对象有个public void doIt()方法,则可以这样调用它:someButton.οnclick=function(){varName.doIt();}
//要用来被js调用的java方法
@JavascriptInterface
public void javaDoIt(final String str){
Message msg = newMessage();
Bundle bundle = newBundle();
bundle.putString("str", str);
msg.setData(bundle);
handler.sendMessage(msg);
}
1)网页加载时机部分
publicboolean shouldOverrideUrlLoading(WebViewview, String url) {
view.loadUrl(url);
returntrue;
}
当加载的网页需要重定向的时候就会回调这个函数告知我们应用程序是否需要接管控制网页加载,如果应用程序接管,并且return true意味着主程序接管网页加载,如果返回false让webview自己处理。
参数说明:
@param view 接收WebViewClient的那个实例,前面看到webView.setWebViewClient(new MyAndroidWebViewClient()),即是这个webview。
@param url 即将要被加载的url
@return true 当前应用程序要自己处理这个url,返回false则不处理。
Tips
(1) 当请求的方式是"POST"方式时这个回调是不会通知的。
(2) 当我们访问的地址需要我们应用程序自己处理的时候,可以在这里截获,比如我们发现跳转到的是一个market的链接,那么我们可以直接跳转到应用市场,或者其他app。
1. public void onPageStarted(WebView view, String url, Bitmap favicon)
当内核开始加载访问的url时,会通知应用程序,对每个main frame这个函数只会被调用一次,页面包含iframe 或者framesets不会另外调用一次onPageStarted,当网页内内嵌的frame 发生改变时也不会调用onPageStarted。
参数说明:
@param view 接收WebViewClient的那个实例,前面看到webView.setWebViewClient(new MyAndroidWebViewClient()),即是这个webview。
@param url 即将要被加载的url
@param favicon 如果这个favicon已经存储在本地数据库中,则会返回这个网页的favicon,否则返回为null。
Tips:
(1) iframe 可能不少人不知道什么含义,这里我解释下,iframe 我们加载的一张,下面有很多链接,我们随便点击一个链接是即当前host的一个iframe.
(2) 有个问题可能是开发者困惑的,onPageStarted和shouldOverrideUrlLoading 在网页加载过程中这两个函数到底哪个先被调用。
当我们通过loadUrl的方式重新加载一个网址时候,这时候会先调用onPageStarted再调用shouldOverrideUrlLoading,当我们在打开的这个网址点击一个link,这时候会先调用shouldOverrideUrlLoading 再调用onPageStarted。不过shouldOverrideUrlLoading不一定每次都被调用,只有需要的时候才会被调用。
1. public void onPageFinished(WebView view, String url)
当内核加载完当前页面时会通知我们的应用程序,这个函数只有在main frame情况下才会被调用,当调用这个函数之后,渲染的图片不会被更新,如果需要获得新图片的通知可以使用@link WebView.PictureListener#onNewPicture。
参数说明:
@param view 接收WebViewClient的那个实例,前面看到webView.setWebViewClient(new MyAndroidWebViewClient()),即是这个webview。
@param url 即将要被加载的url
public void onLoadResource(WebView view, String url)
通知应用程序WebView即将加载url制定的资源
参数说明:
@param view 接收WebViewClient的那个实例,前面看到webView.setWebViewClient(new MyAndroidWebViewClient()),即是这个webview。
@param url 即将加载的url资源
1. public WebResourceResponse shouldInterceptRequest(WebView view,
2. String url)
通知应用程序内核即将加载url制定的资源,应用程序可以返回本地的资源提供给内核,若本地处理返回数据,内核不从网络上获取数据。
参数说明:
@param view 接收WebViewClient的那个实例,前面看到webView.setWebViewClient(new MyAndroidWebViewClient()),即是这个webview。
@param url raw url 制定的资源
@return 返回WebResourceResponse包含数据对象,或者返回null
Tips
这个回调并不一定在UI线程执行,所以我们需要注意在这里操作View或者私有数据相关的动作。
如果我们需要改变网页的背景,或者需要实现网页页面颜色定制化的需求,可以在这个回调时机处理。
1. public void onReceivedError(WebView view, int errorCode,
2. String description, String failingUrl)
当浏览器访问制定的网址发生错误时会通知我们应用程序,比如网络错误。
参数说明:
@param view 接收WebViewClient的那个实例,前面看到webView.setWebViewClient(new MyAndroidWebViewClient()),即是这个webview。
@param errorCode 错误号可以在WebViewClient.ERROR_*里面找到对应的错误名称。
@param description 描述错误的信息
@param failingUrl 当前访问失败的url,注意并不一定是我们主url
Tips
在onReceiveError我们可以自定义网页的错误页面。
1. public void onFormResubmission(WebView view, Message dontResend,
2. Message resend)
如果浏览器需要重新发送POST请求,可以通过这个时机来处理。默认是不重新发送数据。
参数说明:
@param view 接收WebViewClient的那个实例,前面看到webView.setWebViewClient(new MyAndroidWebViewClient()),即是这个webview。
@param dontResent 当浏览器不需要重新发送数据时,可以使用这个参数。
@param resent 当浏览器需要重新发送数据时,可以使用这个参数。
1. public void doUpdateVisitedHistory(WebView view, String url,
2. boolean isReload)
通知应用程序可以将当前的url存储在数据库中,意味着当前的访问url已经生效并被记录在内核当中。这个函数在网页加载过程中只会被调用一次。注意网页前进后退并不会回调这个函数。
参数说明:
@param view 接收WebViewClient的那个实例,前面看到webView.setWebViewClient(new MyAndroidWebViewClient()),即是这个webview。
@param url 当前正在访问的url
@ param isReload 如果是true这个是正在被reload的url
1. public void onReceivedSslError(WebView view, SslErrorHandler handler,
2. SslError error)
当网页加载资源过程中发现SSL错误会调用此方法。我们应用程序必须做出响应,是取消请求handler.cancel(),还是继续请求handler.proceed();内核的默认行为是handler.cancel();
参数说明:
@param view 接收WebViewClient的那个实例,前面看到webView.setWebViewClient(new MyAndroidWebViewClient()),即是这个webview。
@param handler 处理用户请求的对象。
@param error SSL错误对象
Tips
内核会记住本次选择,如果下次还有相同的错误,内核会直接执行之前选择的结果。
1. public void onReceivedHttpAuthRequest(WebView view,
2. HttpAuthHandler handler, String host, String realm)
通知应用程序WebView接收到了一个Http auth的请求,应用程序可以使用supplied 设置webview的响应请求。默认行为是cancel 本次请求。
参数说明:
@param view 接收WebViewClient的那个实例,前面看到webView.setWebViewClient(new MyAndroidWebViewClient()),即是这个webview。
@param handler 用来响应WebView请求的对象
@param host 请求认证的host
@param realm 认真请求所在的域
1. public boolean shouldOverrideKeyEvent(WebView view, KeyEvent event)
提供应用程序同步一个处理按键事件的机会,菜单快捷键需要被过滤掉。如果返回true,webview不处理该事件,如果返回false, webview会一直处理这个事件,因此在view 链上没有一个父类可以响应到这个事件。默认行为是return false;
参数说明:
@param view 接收WebViewClient的那个实例,前面看到webView.setWebViewClient(new MyAndroidWebViewClient()),即是这个webview。
@param event 键盘事件名
@return 如果返回true,应用程序处理该时间,返回false 交有webview处理。
1. public void onScaleChanged(WebView view, float oldScale, float newScale)
通知应用程序webview要被scale。应用程序可以处理改事件,比如调整适配屏幕。
1. public void onReceivedLoginRequest(WebView view, String realm,
2. String account, String args)
通知应用程序有个自动登录的帐号过程
参数说明:
@param view 请求登陆的webview
@param realm 账户的域名,用来查找账户。
@param account 一个可选的账户,如果是null 需要和本地的账户进行check,如果是一个可用的账户,则提供登录。
@param args 验证制定参数的登录用户
3.WebChromeClient 基本使用
1. WebView的内存泄露。
这个问题,很难清晰描述,你在谷歌里搜 webview leadmemory 能搜到很多结果甚至还有给谷歌提交的issue 哈哈,我也无法给出一个清晰的答案在什么时候什么版本那些手机上一定会出现内存泄露,
但是根据一些monkey结果来看,有时,webview内存泄露的情况还是很严重的,尤其是当你加载的页面比较庞大的时候。解决方案参考下微信和qq的做法,试了一下是目前效果最好的,
就是当你要用webview的时候,记得最好另外单独开一个进程去使用webview 并且当这个进程结束时,请手动调用System.exit(0)。
这是目前对于webview 内存泄露最好的解决方案。使用此方法所有因为webview引发的资源无法释放等问题全部可以解决。
3.4.1不在xml中定义 Webview ,而是在需要的时候在Activity中创建,并且Context使用 getApplicationgContext()
LinearLayout
.LayoutParamsparams = new LinearLayout
.LayoutParams(ViewGroup
.LayoutParams.MATCH_PARENT, ViewGroup
.LayoutParams.MATCH_PARENT)
;
mWebView = new WebView(getApplicationContext())
;
mWebView
.setLayoutParams(params)
;
mLayout
.addView(mWebView)
;
3.4.2 在 Activity 销毁( WebView )的时候,先让 WebView 加载null内容,然后移除 WebView,再销毁 WebView,最后置空。
@Override
protected
void
onDestroy
() {
if
(mWebView !=
null) {
mWebView.loadDataWithBaseURL(
null,
"",
"text/html",
"utf-8",
null);
mWebView.clearHistory();
((ViewGroup) mWebView.getParent()).removeView(mWebView);
mWebView.destroy();
mWebView =
null;
}
super
.onDestroy();
}
2. getSettings().setBuiltInZoomControls(true)引发的crush。
这个方法调用以后如果你触摸屏幕弹出那个提示框还没消失的时候你如果activity结束了就会报错了。3.0以上 4.4以下很多手机会出现这种情况
所以为了规避他,我们通常是在activity的onDestroy方法里手动的将webiew设置成setVisibility(View.GONE)
3.onPageFinished 函数到底有用没有?
多数开发者都是参考的http://stackoverflow.com/questions/3149216/how-to-listen-for-a-webview-finishing-loading-a-url-in-android 这个上面的高票答案。
但其实根据我自己观察,这个函数并没有什么卵用,有的时候是提前结束,有的时候就迟迟无法结束,你信这个函数还不如信上帝,甚至于onProgressChanged这个函数
都比onPageFinished 要准一些。如果你的产品经理坚持你一定要实现这种功能的话,我建议你提早结束他,否则卡在那用户迟迟动不了这种体验不好。
有空的同学可以跟一下源码,onPageFinished 在不同的内核里调用的时机都不一样。说实话我也很醉。。。这个问题有完美解决方案的请知会我一下。。。
4.后台无法释放js 导致耗电。
这个可能很少有人知道,你如果webview加载的html里有一些js 一直在执行比如动画之类的东西,如果此刻webview 挂在了后台
这些资源是不会被释放,用户也无法感知,导致一直占有cpu 耗电特别快,所以大家记住了,如果遇到这种情况请在onstop和onresume里分别把setJavaScriptEnabled();
给设置成false和true。
5.如果实在不想用开额外进程的方式解决webview 内存泄露的问题,那么下面的方法很大程度上可以避免这种情况
public
voidreleaseAllWebViewCallback() {
if
(android.os.Build.VERSION.SDK_INT <
16) {
try
{
Field field = WebView.class.getDeclaredField(
"mWebViewCore");
field = field.getType().getDeclaredField(
"mBrowserFrame");
field = field.getType().getDeclaredField(
"sConfigCallback");
field.setAccessible(
true);
field.set(
null,
null);
}
catch(NoSuchFieldException e) {
if
(BuildConfig.DEBUG) {
e.printStackTrace();
}
}
catch(IllegalAccessException e) {
if
(BuildConfig.DEBUG) {
e.printStackTrace();
}
}
}
else{
try
{
Field sConfigCallback = Class.forName(
"android.webkit.BrowserFrame").getDeclaredField(
"sConfigCallback");
if
(sConfigCallback !=
null) {
sConfigCallback.setAccessible(
true);
sConfigCallback.set(
null,
null);
}
}
catch(NoSuchFieldException e) {
if (BuildConfig.DEBUG) {
e.printStackTrace();
}
}
catch(ClassNotFoundException e) {
if
(BuildConfig.DEBUG) {
e.printStackTrace();
}
}
catch(IllegalAccessException e) {
if
(BuildConfig.DEBUG) {
e.printStackTrace();
}
}
}
}
在webview的 destroy方法里调用这个方法就行了。