参考文献:
Carson_Ho的Android:这是一份全面 & 详细的Webview使用攻略
本文知识点:
- WebView的介绍
- WebView的基本使用
- WebView的一些开发中常用的API
- WebView中一些常见的案例分析及实现
1.WebView的介绍
相信做Android的基本上都用过WebView,不论是加载网页还是处理本地内容,基本上都或多或少的使用过WebView。那么WebView能为我们带来什么呢?其实就相当于一个内置的浏览器,我们可以使用它完成一下操作:
1.WebView可以加载网页
2.WebView可以加载html片段
3.WebView可以加载本地html
4.WebView与原生App进行交互
以上种种都是WebView能帮我们完成的内容,所以现在有很多App就采用混合开发。这样使得我们使用WebView的场景越来越多,所以这里总结一下关于WebView的一些内容。
2.WebView的基本使用
其实如果你只用WebView加载一个网页的话,还是很好上手的。基本上就三步:
- 在AndroidManifest注册网络权限:这个是必须的,切记切记!!!
<uses-permission android:name="android.permission.INTERNET" />
- 加载相应的网页资源
webView.loadUrl(url);
但是这里虽然加载的是一个url,但是你会发现他会直接蹦到系统自带的浏览器中去。作为强大的开发人员,怎么处理呢?当然是有办法的了。。。
- 设置相应的本APP内打开的方法;
webView.setWebViewClient(new WebViewClient() {
@RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)
@Override
public boolean shouldOverrideUrlLoading(WebView view, WebResourceRequest request) {
view.loadUrl(String.valueOf(request.getUrl()));
return true;
}
@Override
public boolean shouldOverrideUrlLoading(WebView view, String url) {
view.loadUrl(url);
return true;
}
});
上面的代码有几点需要说明一下:
- shouldOverrideUrlLoading(WebView view, WebResourceRequest request) 这个方法是在5.0以上的版本上使用的,而shouldOverrideUrlLoading(WebView view, String url)这个方法是在5.0以下使用的,有了这两个方法,就能在自己的APP中打开相应的网页链接了。。。
上面就是WebView的简单实用,基本上就是展示一个网页,没有相应的交互问题。
3.WebView一些开中常用的API
加载类:
-
loadUrl(string url) 加载url地址的方法,参数就是一个Url地址;这里有一个点需要注意一下,这个方法加载的地址可以是
- 网址 如 ->
http://www.baidu.com
- 手机本地html地址 如->
file:///android_asset/test.html
- app包中assect目录下的html文件 如 ->
content://com.android.htmlfileprovider/sdcard/test.html
- 网址 如 ->
-
loadData(String data, String mimeType, String encoding) 加载html片段
- 参数1:html片段
- 参数2:类型
- 参数3:编码格式
状态类
- onResume() 当webView为活跃状态的时候回调此方法(获取焦点)
- onPause() 切换到后台的时候回调此方法(失去焦点)
其实上面这两个方法是和生命周期匹配的两个方法
- pauseTimers() 全局范围切换到后台降低cpu功耗时回调的方法
- resumeTimers() 恢复正常状态的时候回调的方法
上面这两个方法,是为了提高自己APP的性能的方法,主要是在生命周期的方法中调用的,也就是说在Activity的onResume()和onPause()中调用的(个人感觉啊。。。)
- destroy() 销毁WebView的方法(主要是在音视频的时候,在这里释放相应的WebView,这里也是对应相应生命周期的方法的,但是这里注意一点,在销毁前必须先移除WebView,因为WebView会持有相应Activity的上下文引用所以这里要使用其父布局调用相应的removeView(View view)的方法进行移除)
操作网页类
- canGoBack() 是否可以后退
- goBack() 回退
- canGoForward() 是否可以前进
- goForward() 前进网页
- **goBackOrForward(intsteps) ** 前进或者后退指定的位置,正数为前进/负数为后退
这里面涉及到的主要内容就是操作网页的前进和后退,主要应用场景是什么呢?试想一下,有的WebView是嵌到APP中的,一般这样的网页都是没有标题的,标题的交互留给APP处理返回的问题,就会用到上面相应的API了,基本上都是处理相应的返回问题
@Override
public boolean onKeyDown(int keyCode, KeyEvent event) {
if ((keyCode == KEYCODE_BACK) && mWb.canGoBack()) {
mWb.goBack();
return true;
}
return super.onKeyDown(keyCode, event);
}
@Override
public void onBackPressed() {
if (mWb.canGoBack()) {
mWb.goBack();
} else {
super.onBackPressed();
}
}
上面两段代码是一样的,但是这里有一个很重要的问题需要注意一下,很重要啊:
有些网页存在重定向的时候,那么canGoBack()返回的会一直是true,我这里使用的是百度的链接,因为百度重定向了,所以一直返回的是true,如果这里要是自己公司开发的Url的话,应该不会出现这种情况!这里提供一种解决办法
@Override
public void goBack() {
WebBackForwardList mWebBackForwardList = copyBackForwardList();
// 判断当前历史列表是否最顶端,其实canGoBack已经判断过
if (mWebBackForwardList.getCurrentIndex() > 0) {
// 获取历史列表
String historyUrl = mWebBackForwardList.getItemAtIndex(
mWebBackForwardList.getCurrentIndex() - 1).getUrl();
if (historyUrl.equals("https://www.baidu.com/")) {
//回到首页了
Activity activity = (Activity) mContext;
activity.finish();
}
}
super.goBack();
}
上面的代码就是拷贝出所有前进的页面,然后判断因为这里重定向了,所以你getUrl()获取的网址永远是重定向那个,所以这里我就想直接把网址写死的话就能处理了,但是我觉得我的解决办法比较笨,还请大神们指教!!!
缓存类
- clearCache(true) 清除WebView产生的所有缓存
- clearHistory() 清除当前WebView访问的历史数据
- clearFormData() 清除表单数据
WebSettings类
这个类是对WebView进行管理和设置的
- getSettings() 获取WebSettings的
- setJavaScriptEnabled(boolean flag); 是否可以和js进行交互
-
setPluginState(PluginState state) 是否支持插件,这里面传递的是一个枚举对象
- WebSettings.PluginState.OFF 不支持
- WebSettings.PluginState.ON 支持
- setUseWideViewPort(boolean use) 是否将图片调整到WebView的大小
- setLoadWithOverviewMode(boolean overview) 是否将WebView调整到屏幕大小
上面两个API一起使用!切记,因为只有这样才能让界面看上去不那么丑!
setSupportZoom(boolean support)支持缩放,默认为true。是下面这个API的前提。
setBuiltInZoomControls(boolean enabled) 设置内置的缩放控件。若为false,则该WebView不可缩放
setDisplayZoomControls(boolean enabled) 隐藏原生的缩放控件,这里可以自己在页面中实现。
-
setCacheMode(@CacheMode int mode) 设置缓存的模式
- WebSettings.LOAD_DEFAULT 默认的模式 根据cache-control决定是否从网络上取数据。
- WebSettings.LOAD_CACHE_ELSE_NETWORK 只要本地有,无论是否过期,或者no-cache,都使用缓存中的数据
- WebSettings.LOAD_NO_CACHE 不使用缓存
- WebSettings.LOAD_CACHE_ONLY 不使用网络,只加载缓存
setAllowFileAccess(boolean allow) 设置可以访问app中assect中的文件
setJavaScriptCanOpenWindowsAutomatically(boolean flag) 支持通过JS打开新窗口
setLoadsImagesAutomatically(boolean flag) 支持自动加载图片
setDefaultTextEncodingName(String encoding) 设置编码格式
-
setRenderPriority(RenderPriority priority) 设置渲染的优先级,这里传入的是一个枚举
- NORMAL 正常
- HIGH 高
- LOW 低
WebViewClient类
处理各种通知和请求事件的控制类
- shouldOverrideUrlLoading(WebView view, WebResourceRequest request)
- shouldOverrideUrlLoading(WebView view, String url)
下面这个方法已经过时了,最新的是上面的那个方法,对于相应的url可以通过WebResourceRequest进行获取。重写这两个方法自己处理的话,可以不使用相应的浏览器进行打开,在本APP中打开,这也是一开始的时候为什么要重写这个方法。
- onPageStarted(WebView view, String url, Bitmap favicon) 加载WebView开始的时候回调的方法,一般都在这里添加相应的Loadding,与用户交互,告诉用户页面还没有加载完成;
- onPageFinished(WebView view, String url) WebView加载完成的时候回调的方法和上面那个方法是相呼应的,可以在这里关闭相应的对话框!
这里有一个问题注意一下,还是重定向的问题。如果这个网页重定向了,那么上面这两个方法会被多次调用!
onLoadResource(WebView view, String url) 这个方法会在每次加载资源的时候回调,如果图片很多的话,会被调用多次。后面这个url代表的是相应加载的地址。
onReceivedError(WebView view, int errorCode,String description, String failingUrl)
onReceivedError(WebView view, WebResourceRequest request, WebResourceError error)
上面这个方法已经过时了,可以根据相应的code进行判断。后面这个方法主要是通过WebResourceError的错误编码进行相应的判断,处理相应的错误!这个方法的主要作用是加载错误的回调。
- onReceivedError(WebView view, int errorCode,String description, String failingUrl)
- onReceivedError(WebView view, WebResourceRequest request, WebResourceError error)
这两个方法是处理相应的https的网络请求的(webView默认是不处理https请求的,页面会显示空白,使用如上面的方法进行处理)
@Override
public void onReceivedSslError(WebView view, SslErrorHandler handler, SslError error) {
super.onReceivedSslError(view, handler, error);
handler.proceed();
}
// 特别注意:5.1以上默认禁止了https和http混用,以下方式是开启
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
mWebView.getSettings().setMixedContentMode(WebSettings.MIXED_CONTENT_ALWAYS_ALLOW);
}
WebChromeClient类
如果你对控制类没有那么多要求的话,使用上面的控制类就可以了,但是如果你要求比较高的话,就要用到下面这个控制类了
- onProgressChanged(WebView view, int newProgress) 这个回调会实时的返回网页加载的总进度,最大值是100,可以根据当前值显示进度。
- onReceivedTitle(WebView view, String title) 获取当前加载网页的标题
- onJsAlert(WebView view, String url, String message, JsResult result) 如果这里返回true,那么js中弹出的警告对话框就由客户端处理;
- onJsAlert(WebView view, String url, String message, JsResult result) 如果这里返回true,那么js中弹出的确认对话框就由客户端处理;
- **onJsPrompt(WebView view, String url, String message, String defaultValue, JsPromptResult result) ** 如果这里返回true,那么js中弹出的提示对话框将由客户端进行处理。
4.WebView中一些常见的案例分析及实现
WebView显示进度
由很多APP中由都嵌入WebView,不知道大家仔细看过没有,有的WebView在加载的时候顶部由一个进度条,提示用户加载的进度,其实很好实现,这里就带大家实现一下:
- 页面搭建
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
tools:context="com.example.demo.MainActivity">
<ProgressBar
android:id="@+id/pb_progress"
style="@style/Widget.AppCompat.ProgressBar.Horizontal"
android:layout_width="match_parent"
android:layout_height="5dp"
android:background="@drawable/progress_bg" />
<WebView
android:id="@+id/wb"
android:layout_width="match_parent"
android:layout_height="match_parent" />
</LinearLayout>
这里就不讲解ProgressBar实现水平进度条了,百度一大堆!!!
- 代码实现
private java.lang.String mUrl = "https://www.baidu.com/";
private WebView mWb;
private ProgressBar progressBar;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
mWb = findViewById(R.id.wb);
progressBar = findViewById(R.id.pb_progress);
progressBar.setMax(100);
setWebView(mWb);
}
@SuppressLint("SetJavaScriptEnabled")
private void setWebView(WebView wb) {
WebSettings settings = wb.getSettings();
settings.setJavaScriptEnabled(true);
settings.setPluginState(WebSettings.PluginState.ON);
settings.setUseWideViewPort(true);
settings.setLoadWithOverviewMode(true);
settings.setSupportZoom(true);
wb.loadUrl(mUrl);
wb.setWebViewClient(new WebViewClient() {
@RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)
@Override
public boolean shouldOverrideUrlLoading(WebView view, WebResourceRequest request) {
view.loadUrl(String.valueOf(request.getUrl()));
return true;
}
@Override
public boolean shouldOverrideUrlLoading(WebView view, String url) {
view.loadUrl(url);
return true;
}
});
wb.setWebChromeClient(new WebChromeClient() {
@Override
public void onProgressChanged(WebView view, int newProgress) {
super.onProgressChanged(view, newProgress);
progressBar.setProgress(newProgress);
}
});
}
主要代码就这么多就能,这里设置了一些其他的属性,上面都讲解了,这里就不去说了。。。
WebView加载错误显示缺省页面
private java.lang.String mUrl = "https://google.com/";
private WebView mWb;
private ProgressBar progressBar;
private java.lang.String mErrorUrl = "file:///android_asset/test.html";
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
mWb = findViewById(R.id.wb);
progressBar = findViewById(R.id.pb_progress);
progressBar.setMax(100);
setWebView(mWb);
}
@SuppressLint("SetJavaScriptEnabled")
private void setWebView(WebView wb) {
WebSettings settings = wb.getSettings();
settings.setJavaScriptEnabled(true);
settings.setPluginState(WebSettings.PluginState.ON);
settings.setUseWideViewPort(true);
settings.setLoadWithOverviewMode(true);
settings.setSupportZoom(true);
wb.loadUrl(mUrl);
wb.setWebViewClient(new WebViewClient() {
@RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)
@Override
public boolean shouldOverrideUrlLoading(WebView view, WebResourceRequest request) {
view.loadUrl(String.valueOf(request.getUrl()));
return true;
}
@Override
public boolean shouldOverrideUrlLoading(WebView view, String url) {
view.loadUrl(url);
return true;
}
@RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)
@Override
public void onReceivedHttpError(WebView view, WebResourceRequest request, WebResourceResponse errorResponse) {
super.onReceivedHttpError(view, request, errorResponse);
// 这个方法在6.0才出现
int statusCode = errorResponse.getStatusCode();
System.out.println("onReceivedHttpError code = " + statusCode);
if (404 == statusCode || 500 == statusCode) {
view.loadUrl("about:blank");// 避免出现默认的错误界面
view.loadUrl(mErrorUrl);
}
}
@RequiresApi(api = Build.VERSION_CODES.M)
@Override
public void onReceivedError(WebView view, WebResourceRequest request, WebResourceError error) {
super.onReceivedError(view, request, error);
// 断网或者网络连接超时
if (error.getErrorCode() == ERROR_HOST_LOOKUP || error.getErrorCode() == ERROR_CONNECT || error.getErrorCode() == ERROR_TIMEOUT) {
view.loadUrl("about:blank"); // 避免出现默认的错误界面
view.loadUrl(mErrorUrl);
}
}
@Override
public void onReceivedError(WebView view, int errorCode, String description, String failingUrl) {
super.onReceivedError(view, errorCode, description, failingUrl);
// 断网或者网络连接超时
if (errorCode == ERROR_HOST_LOOKUP || errorCode == ERROR_CONNECT || errorCode == ERROR_TIMEOUT) {
view.loadUrl("about:blank"); // 避免出现默认的错误界面
view.loadUrl(mErrorUrl);
}
}
});
wb.setWebChromeClient(new WebChromeClient() {
@Override
public void onProgressChanged(WebView view, int newProgress) {
super.onProgressChanged(view, newProgress);
Log.e("done", "onProgressChanged: " + newProgress);
progressBar.setProgress(newProgress);
}
@Override
public void onReceivedTitle(WebView view, String title) {
super.onReceivedTitle(view, title);
// android 6.0 以下通过title获取
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M) {
if (title.contains("404") || title.contains("500") || title.contains("Error")) {
view.loadUrl("about:blank");// 避免出现默认的错误界面
view.loadUrl(mErrorUrl);
}
}
}
});
}
因为WebView引起了google的重视,所以版本之间存在很多差异,所以这里区分了很多版本的问题,上面都有相应的注解!其实就是在断网或者网络重连的时候添加了相应的错误页面,防止用户看见那个难看的断网页面。增加用户的体验!
WebView与JS交互的实现
这里先贴一段测试的js代码,下面会用到:
<html>
<head>
<meta charset="utf-8">
<title>App调用js的代码</title>
<script>
// Android需要调用的方法
function callJS(){
console.log("你看我在哪出来~~~");
}
function callAndroid(){
// 由于对象映射,所以调用test对象等于调用Android映射的对象
test.hello("js调用了android中的hello方法");
}
</script>
</head>
<body>
<h1>假装这个一个页面</h1>
<button type="button" id="button1" onclick="callAndroid()"></button>
</body>
</html>
1. 原生代码调用js中的代码
这里可以有两种实现方案:
这里注意一点,无论使用那种方案调用,前提是一定要有下面的代码!!!
WebSettings settings = mWb.getSettings();
settings.setJavaScriptEnabled(true);//允许相应js代码
通过loadUrl("javascript:callJS()")调用js中的代码,但是会刷新相应的页面(这里面callJS是上面function的方法名称)
通过evaluateJavascript(String script, ValueCallback<String> resultCallback)的callBack进行调用相应的方法(这个处理效率高,但是这个方法是在API19才可以使用的,所以一般在使用的时候通过判断版本两个一起使用!!!)
整体的使用代码如下:
if (Build.VERSION.SDK_INT > 18) {
mWb.evaluateJavascript("javascript:callJS()", new ValueCallback<String>() {
@Override
public void onReceiveValue(String value) {
Log.e("done", "onReceiveValue: " + value);
}
});
} else {
mWb.loadUrl("javascript:callJS()");
}
这样既解决了效率的问题也解决了版本的问题。
2. 原生代码调用js中的代码
这里最常用的方式就是通过注释@JavascriptInterface进行调用,但是看了Carson_Ho的文章说这里有漏洞,但是说4.2就没有这种漏洞了,所以没有去研究,感觉现在一般的手机都应该超过4.2了,如果以后需要适配的时候在仔细看一下!!!
- 步骤1:
创建一个类用于给JS提供相应的方法;
class JSMothod {
@JavascriptInterface
public void hello(String msg) {
System.out.println("JS调用了Android的hello方法");
}
}
这里有一点需要注意的,就是@JavascriptInterface这个注解一定要加上,否则对于4.0一下的手机可能会存在问题!!!切记。。。
- 步骤2:
添加相应的调用方法
addJavascriptInterface(Object object, String name);
这里面的两个参数分别是上面写的那个类对象和相应js中的方法名;具体的代码是这样的。。。
mWb.addJavascriptInterface(new JSMothod(),"test");
这样就能实现js调用Android的代码了,其实很简单。
基本上上面的内容就囊括了Android在使用WebView中的一些常见的使用场景,可能还有些内容没有想到,如果有什么不明白的,可以在下方给我留言,还希望这些内容可以帮到你!!!