最近项目里用到webview来呈现一个视频网站的内容。正好趁此机会研究一下WebView的使用。
1. WebSettings
用于管理WebView状态的设置。
//Webview的设置接口都在WebSettings类里面。
WebSettings webSettings = mWebView.getSettings();
webSettings.setJavaScriptEnabled(true);//支持javascript
webSettings.setUseWideViewPort(true); //设置使web页自适应屏幕
webSettings.setLoadWithOverviewMode(true);////缩放至屏幕的大小
webSettings.setSupportZoom(false); //设置支持缩放
webSettings.setBuiltInZoomControls(false); //在网页上添加放大缩小的控件
webSettings.setDisplayZoomControls(false); //在网页上显示放大缩小的控件
webSettings.setLoadsImagesAutomatically(true); //设置自动加载图片
webSettings.setBlockNetworkImage(true); //设置网页在加载的时候暂时不加载网络图片
webSettings.setAppCachePath(""); //设置缓存路径
webSettings.setCacheMode(WebSettings.LOAD_NO_CACHE); //设置缓存模式
//页面需要访问本地资源图片的时候,需要设置下面三个方法。
webSettings.setAllowUniversalAccessFromFileURLs(true);//设置是否允许通过 file url 加载的 Javascript 可以访问其他的源(包括http、https等源)
webSettings.setAllowFileAccess(true); //设置可以访问file:///类型文件
webSettings.setAllowFileAccessFromFileURLs(false); //设置是否允许通过 file url 加载的 Js代码读取其他的本地文件。安全问题。
//可以让WebView访问ContentPrivider存储的内容。 默认true
webSettings.setAllowContentAccess(boolean allow);
2. loadUrl(), loadData()和loadDataWithBaseUrl()
mWebview.loadUrl("file:///android_asset/index.html");
mWebview.loadUrl("https://www.csdn.net");
mWebView.loadData(htmlData, "text/html", "UTF-8");//API提供的标准用法,无法解决乱码问题
mWebView.loadData(htmlData, "text/html; charset=UTF-8", null);//这种写法可以解决乱码问题
webView.loadData(URLEncoder.encode(htmlData, "utf-8"), "text/html", "utf-8");//解决四个特殊字符问题
mWebView.loadDataWithBaseURL(null, mHtmlString, "text/html", "UTF-8", null);
3. WebViewClient
WebViewClient是WebView的callback方法,用来处理加载时的各种通知与事件,比如WebView中页面加载的开始、结束,以及错误等等。列举几个简单,必用的API。其他接口可能需要对html/js网页开发技术比较了解才能理解得了。由于本人不熟悉js,所以就不再列举其他方法了。
用来防止跳转出webview的,此方法在POST请求时不会调用。当返回false的时候就会把打开url的任务交给系统,返回true的时候就会用当前的webview来打开该url。说是每一次调用都会到这里来,但是不一定的,主url一样,只是参数变了,就不会进来。 另外,很多网站跳转的时候,URL不是以http或者https开头的, 这样会导致schema错误,这个方法也处理这个问题。
这个方法是在开始加载网页的时候开始调用的。大多数一调用loadURL()就开始调用onPageStart()了,但是注意,如果是那种404的网站,facebook等,会白屏很久,最后连接超时退出时才会依次调用onPageStart()-->onPageFinished()-->onReceivedError().也就是说这个方法是在socket连接上以后,从inputstream里开始读数据时才调用。
Bitmap favicon, 是网站的小图标,需要事先getFavicon()下载过来放到本地。如果这个favicon已经存储在本地数据库中,则会返回这个网页的favicon,否则返回为null。
页面加载(包括重定向)结束后或者加载错误后都会调用。
当网络加载出现异常时就会回调这个方法。下面是所有的errror code。加载错误不仅仅是网络的原因,还是比如不以http/https开头的schema原因。
public static final int ERROR_AUTHENTICATION = -4;
public static final int ERROR_BAD_URL = -12;
public static final int ERROR_CONNECT = -6;
public static final int ERROR_FAILED_SSL_HANDSHAKE = -11;
public static final int ERROR_FILE = -13;
public static final int ERROR_FILE_NOT_FOUND = -14;
public static final int ERROR_HOST_LOOKUP = -2;
public static final int ERROR_IO = -7;
public static final int ERROR_PROXY_AUTHENTICATION = -5;
public static final int ERROR_REDIRECT_LOOP = -9;
public static final int ERROR_TIMEOUT = -8;
public static final int ERROR_TOO_MANY_REQUESTS = -15;
public static final int ERROR_UNKNOWN = -1;
public static final int ERROR_UNSAFE_RESOURCE = -16;
public static final int ERROR_UNSUPPORTED_AUTH_SCHEME = -3;
public static final int ERROR_UNSUPPORTED_SCHEME = -10;
public static final int SAFE_BROWSING_THREAT_MALWARE = 1;
public static final int SAFE_BROWSING_THREAT_PHISHING = 2;
public static final int SAFE_BROWSING_THREAT_UNKNOWN = 0;
public static final int SAFE_BROWSING_THREAT_UNWANTED_SOFTWARE = 3;
mWebView.setWebViewClient(new WebViewClient() {
//防止跳转出webview的. 另外很多网站跳转时候URL不是以http或者https开头的, 这样会导致schema错误,这个方法也处理这个问题。
@Override
public boolean shouldOverrideUrlLoading(WebView view, WebResourceRequest request) {
String url = request.getUrl().toString();
try {
if (url.startsWith("http:") || url.startsWith("https:")) {
view.loadUrl(url);
} else {
Intent intent = new Intent(Intent.ACTION_VIEW, Uri.parse(url));
startActivity(intent);
}
} catch (Exception e) {
return false;
}
return true;
}
@Override
public void onPageStarted(WebView view, String url, Bitmap favicon) {
showProgress(false);
}
@Override
public void onPageFinished(WebView view, String url) {
}
@Override
public void onReceivedError(WebView view, int errorCode, String description, String failingUrl) {
//also will be schema error, not network error.
if (errorCode == WebViewClient.ERROR_TIMEOUT
|| errorCode == WebViewClient.ERROR_HOST_LOOKUP
|| errorCode == WebViewClient.ERROR_CONNECT
|| errorCode == WebViewClient.ERROR_PROXY_AUTHENTICATION) {
if (TextUtils.isEmpty(mHtml)) {
mHtml = Constants.WEB_VIEW_LOAD_ERROR_PAGE.replace("[title]", getString(R.string.Network_Problem)).replace("[body]", getString(R.string.check_network_problem));
}
mWebView.loadDataWithBaseURL(null, mHtml, "text/html", "UTF-8", null);
}
}
});
所有资源请求(包括css,js, image)出去之前,都会回调这个方法,所以可以做一些判断和拦截替换。这是非UI线程,替换的资源最好预先加载到内存,不然会出现错误。
接下的方法只是记录一下用处,笔者在项目中没有实际使用过,而且也不常用。不属于简单使用范畴。
通知应用程序有个自动登录的帐号过程
通知应用程序webview 要被scale。应用程序可以处理改事件,比如调整适配屏幕。应该要和webSettings.setSupportZoom(true)联用。
提供应用程序同步一个处理按键事件的机会,菜单快捷键需要被过滤掉。如果返回true,webview不处理该事件,如果返回false,webview会一直处理这个事件,因此在view 链上没有一个父类可以响应到这个事件。默认行为是return false;
通知应用程序WebView接收到了一个Http auth的请求,应用程序可以使用supplied 设置webview的响应请求。默认行为是cancel 本次请求。
当网页加载资源过程中发现SSL错误会调用此方法。我们应用程序必须做出响应,是取消请求handler.cancel(),还是继续请求handler. proceed();内核的默认行为是handler.cancel();
如果浏览器需要重新发送POST请求,可以通过这个时机来处理。默认是不重新发送数据。
通知应用程序可以将当前的url存储在数据库中,意味着当前的访问url已经生效并被记录在内核当中。这个函数在网页加载过程中只会被调用一次。注意网页前进后退并不会回调这个函数。
HTTP的body标签加载前调用,仅在主frame调用。
通知应用程序在加载资源时从服务器接收到HTTP错误。HTTP错误的状态代码>=400。此回调将调用任何资源(IFRAME、图像等),而不只是针对主页。因此,建议在该回调中执行最小所需的工作。注意,服务器响应的内容不能在errorCode参数中提供。
通知宿主应用程序已经退出给定的WebVIEW的渲染过程。 return true如果宿主应用程序处理进程已退出的情况,否则,如果渲染过程崩溃,应用程序将崩溃,或者如果渲染过程被系统杀死则会被杀死。
通知主机应用程序处理SSL客户端证书请求。如果需要,主机应用程序负责显示UI并提供密钥。有三种方式来响应:proceed(), cancel()或ignore()。WebVIEW将调用响应存储在内存中(对于应用程序的生命周期)如果proceed() 或 cancel()被调用并且个对同样的主机:端口不会再次调用onReceivedClientCertRequest()。如果调用ignore(),WebVIEW不会存储响应。要注意多层chromium网络栈可能会缓存相应,所以最好的行为就是忽略(ignore)。这个方法在UI线程上被调用。在回调期间,连接被挂起。
4. WebChromeClient
WebChromeClient主要辅助WebView处理Javascript的交互以及浏览器层面的事件
通知主程序当前加载页面进度。
通知主机应用程序Web内容请求访问指定资源的权限,并且当前未授予或拒绝权限。宿主应用程序必须调用PermissionRequest#grant(String[])或PermissionRequest#deny()。如果此方法未被重写,则拒绝该权限。
通知主机应用程序,来自指定来源的Web内容试图使用地理定位API,但目前没有为该源设置许可状态。宿主应用程序应该调用具有所需权限状态的指定回调。有关详细信息,请参阅GeolocationPermissions。
如果上面那个权限取消会调用这个,因为没有权限,所以相关ui应该被隐藏。
通知主程序权限请求被取消。相关ui应该被隐藏。
通知主程序document title变化。
通知主程序当前页面有新的favicon。
通知主程序的apple-touch-icon的url。apple-touch-icon用于给苹果设备生成桌面快捷方式.
通知主机应用程序当前页面已进入全屏模式。主应用程序必须在全屏幕模式下显示包含Web内容(视频或其他HTML内容)的自定义视图。也请参阅WebView里的“全屏支持”文档。
通知主程序当前页面已经退出全屏模式。主程序需要隐藏自定义view。
请求主机应用程序创建一个新窗口。如果宿主应用程序选择尊重这个请求,它应该从这个方法返回true,创建一个新的WebView来托管窗口,将它插入到视图系统中,并以新的WebView作为参数将提供的resultMsg消息发送到它的目标。如果宿主应用程序选择不履行请求,则应该从该方法返回false。此方法的默认实现不执行任何操作,因此返回false。
此WebVIEW请求显示和焦点。这可能是由于另一WebVIEW在这个WebVIEW中打开一个链接并请求显示这个WebView。
通知主机应用程序关闭给定的WebVIEW,并在必要时从视图系统中删除它。在这一点上,WebCore已经停止了这个窗口中的任何加载,并删除了JavaScript中的任何互相访问能力。
接收js三种对话框事件的,返回true则app处理,否则网页webview自己处理.
告诉客户端显示一个对话框以确认导航离开当前页面。这是预先卸载JavaScript事件的结果。如果客户端返回true,WebVIEW将假定客户端将处理确认对话框并调用适当的JSREST方法。如果客户端返回false,则true值将返回到JavaScript以接受当前页面的导航。默认行为是返回false。将JSRESULT设置为true将导航离开当前页面,false将取消导航。
向主机应用程序报告一个JavaScript控制台消息。ChromeClient 应该重写这个过程来处理日志消息,因为它们看起来合适。
当不播放时,视频元素由'poster' 图像表示。可以使用HTML中的视频标签的poster 属性来指定要使用的图像。如果属性不存在,则将使用默认poster 。此方法允许ChromeClient 提供默认图像
获取在全屏视频正在缓冲的同时显示的视图。宿主应用程序可以重写此方法,以提供包含旋转器或类似物的视图。
获取历史访问记录
告诉客户端显示一个文件选择器。这被调用来处理带有“文件”输入类型的HTML表单,以响应用户按下“选择文件”按钮。要取消请求,请调用filePathCallback.onReceiveValue(null)并返回true。
告诉客户端正在查看的页面有一个可自动填充的表单,用户希望设置一个配置文件。
5. pauseTimers(),pause(), resume()
当webview所在的activty退入后台或者呈现前台的时候,为了节省电源,在activity的onResume()和onPause()方法里会调用这三个方法。
6. 销毁WebView对象
这个没什么好说的,总之就是唯恐销毁不掉的感觉。可能怕延迟销毁。
@Override
protected void onDestroy() {
if (mWebView != null) {
mWebView.clearHistory();
((ViewGroup) mWebView.getParent()).removeView(mWebView);
mWebView.loadUrl("about:blank");
mWebView.stopLoading();
mWebView.setWebChromeClient(null);
mWebView.setWebViewClient(null);
mWebView.destroy();
mWebView = null;
}
super.onDestroy();
}
7. 白屏与错误页面处理
String WEB_VIEW_LOAD_ERROR_PAGE = "" +
"" +
"" +
" " +
" " +
" " +
"" +
"" +
"" +
" " +
" [title]
" +
" [body]
" +
" " +
"" +
"";
if (TextUtils.isEmpty(mHtml)) {
mHtml = Constants.WEB_VIEW_LOAD_ERROR_PAGE.replace("[title]", getString(R.string.Network_Problem)).replace("[body]", getString(R.string.check_network_problem));
}
mWebView.loadDataWithBaseURL(null, mHtml, "text/html", "UTF-8", null);
8. 上/下一个页面
private void goBack(){
if (mWebView.canGoBack()){
mWebView.goBack();
}
}
private void goForward() {
if (mWebView.canGoForward()) {
mWebView.goForward();
}
}
9. 下载
通过DownloadListener可以拿到下载相关的信息。
/×
Content-disposition 是 MIME 协议的扩展,MIME 协议指示 MIME 用户代理如何显示附加的文件。Content-disposition其实可以控制用户请求所得的内容存为一个文件的时候提供一个默认的文件名,文件直接在浏览器上显示或者在访问时弹出文件下载对话框。
格式说明: content-disposition = "Content-Disposition" ":" disposition-type *( ";" disposition-parm )
字段说明:Content-Disposition为属性名disposition-type是以什么方式下载,如attachment为以附件方式下载disposition-parm为默认保存时的文件名服务端向客户端游览器发送文件时,如果是浏览器支持的文件类型,一般会默认使用浏览器打开,比如txt、jpg等,会直接在浏览器中显示,如果需要提示用户保存,就要利用Content-Disposition进行一下处理,关键在于一定要加上attachment
×/
mWebView.setDownloadListener(new DownloadListener() {
@Override
public void onDownloadStart(String url, String userAgent, String contentDisposition, String mimetype,
long contentLength) {
}
});
10. 缓存策略
//优先使用缓存:
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 缓存目录
11. JS的交互
对于Android调用JS代码的方法有2种:
1. 通过WebView
的loadUrl().
2. 通过WebView
的evaluateJavascript(). 要求android4.4以上
对于JS调用Android代码的方法有3种:
1. 通过WebView
的addJavascriptInterface()
进行对象映射
2. 通过 WebViewClient
的shouldOverrideUrlLoading ()
方法回调拦截 url
3. 通过 WebChromeClient
的onJsAlert()
、onJsConfirm()
、onJsPrompt()
方法回调拦截JS对话框alert()
、confirm()
、prompt()
消息
4. 到了2020年了,市场上还有多少android 4.2,4.4的手机呢?! 所以JS--->JAVA就用addJavascriptInterface()这种方式吧, JAVA--->JS就用evaluateJavascript()吧。
另外,注意事项:
addJavascriptInterface这种方式,但是第4条已经说了现在的情况。
你不知道的 Android WebView 使用漏洞下面是对loadUrl方式的简单使用:
index.html 放在assets目录下面:
webview study
public class MainActivity extends AppCompatActivity {
private WebView mWebView;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
mWebView = (WebView) findViewById(R.id.webView);
mWebView.getSettings().setJavaScriptEnabled(true);
mWebView.getSettings().setDomStorageEnabled(true);
mWebView.addJavascriptInterface(new JsOperations(this), "client");
mWebView.loadUrl("file:///android_asset/index.html");
}
public void onClick(View view) {
runOnUiThread(new Runnable() {
@Override
public void run() {
mWebView.loadUrl("javascript:click()");
}
});
}
}
11. WebView的简单实用的完整代码如下:
public class WebViewActivity extends AppCompatActivity {
private WebView mWebView;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_test);
mWebView = (WebView) findViewById(R.id.webView);
//Webview的设置接口都在WebSettings类里面。
WebSettings webSettings = mWebView.getSettings();
webSettings.setJavaScriptEnabled(true);//支持javascript
webSettings.setUseWideViewPort(true); //设置使web页自适应屏幕
webSettings.setLoadWithOverviewMode(true);////缩放至屏幕的大小
webSettings.setSupportZoom(false); //设置支持缩放
webSettings.setBuiltInZoomControls(false); //在网页上添加放大缩小的控件
webSettings.setDisplayZoomControls(false); //在网页上显示放大缩小的控件
webSettings.setLoadsImagesAutomatically(true); //设置自动加载图片
webSettings.setBlockNetworkImage(true); //设置网页在加载的时候暂时不加载网络图片
webSettings.setAppCachePath(""); //设置缓存路径
webSettings.setCacheMode(WebSettings.LOAD_NO_CACHE); //设置缓存模式
webSettings.setAllowFileAccess(true); //设置可以访问file:///文件.有安全问题需要注意。
mWebView.loadUrl("https://www.csdn.net/");
showProgress(true);
mWebView.setWebViewClient(new WebViewClient() {
@Override
public boolean shouldOverrideUrlLoading(WebView view, WebResourceRequest request) {
String url = request.getUrl().toString();
try {
if (url.startsWith("http:") || url.startsWith("https:")) {
view.loadUrl(url);
} else {
Intent intent = new Intent(Intent.ACTION_VIEW, Uri.parse(url));
startActivity(intent);
}
} catch (Exception e) {
return false;
}
return true;
}
@Override
public void onPageStarted(WebView view, String url, Bitmap favicon) {
showProgress(false);
}
@Override
public void onPageFinished(WebView view, String url) {
}
@Override
public void onReceivedError(WebView view, int errorCode, String description, String failingUrl) {
//also will be schema error, not network error.
if (errorCode == WebViewClient.ERROR_TIMEOUT
|| errorCode == WebViewClient.ERROR_HOST_LOOKUP
|| errorCode == WebViewClient.ERROR_CONNECT
|| errorCode == WebViewClient.ERROR_PROXY_AUTHENTICATION) {
if (TextUtils.isEmpty(mHtml)) {
mHtml = Constants.WEB_VIEW_LOAD_ERROR_PAGE.replace("[title]", getString(R.string.Network_Problem)).replace("[body]", getString(R.string.check_network_problem));
}
mWebView.loadDataWithBaseURL(null, mHtml, "text/html", "UTF-8", null);
}
}
});
@Override
protected void onResume() {
super.onResume();
mIsResumed = true;
mWebView.onResume();
mWebView.resumeTimers();
}
@Override
protected void onPause() {
super.onPause();
mIsResumed = false;
mWebView.onPause();
mWebView.pauseTimers();//这方法慎重使用
}
@Override
protected void onDestroy() {
if (mWebView != null) {
mWebView.clearHistory();
((ViewGroup) mWebView.getParent()).removeView(mWebView);
mWebView.loadUrl("about:blank");
mWebView.stopLoading();
mWebView.setWebChromeClient(null);
mWebView.setWebViewClient(null);
mWebView.destroy();
mWebView = null;
}
super.onDestroy();
}
@Override
public void onBackPressed() {
if (mWebView.canGoBack()) {
mWebView.goBack();
return;
}
super.onBackPressed();
}
}
public class Constants {
public static String WEB_VIEW_LOAD_ERROR_PAGE = "" +
"" +
"" +
" " +
" " +
" " +
"" +
"" +
"" +
" " +
" [title]
" +
" [body]
" +
" " +
"" +
"";
}
WebView的简单介绍就到这里,倘若以后还有使用经历会继续补充。