WebView 是一个用来显示 Web 网页的控件,它就是一个微型浏览器,包含一个浏览器该有的基本功能,例如:滚动、缩放、前进、后退下一页、搜索、执行 Js等功能。在Android4.4之前使用的是WebKit的内核,之后使用的是Chrome内核。
void loadUrl(String url):加载网络链接 url
boolean canGoBack():判断 WebView 当前是否可以返回上一页
goBack():回退到上一页
boolean canGoForward():判断 WebView 当前是否可以向前一页
goForward():回退到前一页
onPause():类似 Activity 生命周期,页面进入后台不可见状态
pauseTimers():该方法面向全局整个应用程序的webview,它会暂停所有webview的layout,parsing,JavaScript Timer。当程序进入后台时,该方法的调用可以降低CPU功耗。
onResume():在调用 onPause()后,可以调用该方法来恢复 WebView 的运行。
resumeTimers():恢复pauseTimers时的所有操作。(注:pauseTimers和resumeTimers 方法必须一起使用,否则再使用其它场景下的 WebView 会有问题)
destroy():销毁 WebView
clearHistory():清除当前 WebView 访问的历史记录。
clearCache(boolean includeDiskFiles):清空网页访问留下的缓存数据。需要注意的时,由于缓存是全局的,所以只要是WebView用到的缓存都会被清空,即便其他地方也会使用到。该方法接受一个参数,从命名即可看出作用。若设为false,则只清空内存里的资源缓存,而不清空磁盘里的。
reload():重新加载当前请求
setLayerType(int layerType, Paint paint):设置硬件加速、软件加速
removeAllViews():清除子view。
clearSslPreferences():清除ssl信息。
clearMatches():清除网页查找的高亮匹配字符。
removeJavascriptInterface(String interfaceName):删除interfaceName 对应的注入对象
addJavascriptInterface(Object object,String interfaceName):注入 java 对象。
setVerticalScrollBarEnabled(boolean verticalScrollBarEnabled):设置垂直方向滚动条。
setHorizontalScrollBarEnabled(boolean horizontalScrollBarEnabled):设置横向滚动条。
loadUrl(String url, MapadditionalHttpHeaders):加载制定url并携带http header数据。
evaluateJavascript(String script, ValueCallback resultCallback):Api 19 之后可以采用此方法之行 Js。
stopLoading():停止 WebView 当前加载。
clearView():在Android 4.3及其以上系统这个api被丢弃了, 并且这个api大多数情况下会有bug,经常不能清除掉之前的渲染数据。官方建议通过loadUrl(“about:blank”)来实现这个功能,阴雨需要重新加载一个页面自然时间会收到影响。
freeMemory():释放内存,不过貌似不好用。
clearFormData():清除自动完成填充的表单数据。需要注意的是,该方法仅仅清除当前表单域自动完成填充的表单数据,并不会清除WebView存储到本地的数据。
基本上是和WebSettings和WebClient、WebChromeClient搭配使用,还有和JS一些交互等
<uses-permission android:name="android.permission.INTERNET"/>
<?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=".MainActivity">
<com.seven.webview.SevenWebView
android:layout_weight="9"
android:id="@+id/seven_web"
android:layout_width="match_parent"
android:layout_height="wrap_content"/>
<Button
android:text="调用js方法"
android:layout_weight="1"
android:id="@+id/call_js"
android:layout_width="match_parent"
android:layout_height="wrap_content" />
</LinearLayout>
这里的WebView是单独封装的,配合WebSetting给WebView设置一些参数,比如支持js等
package com.seven.webview;
import android.content.Context;
import android.util.AttributeSet;
import android.webkit.WebSettings;
import android.webkit.WebView;
/**
* Time:2020/3/13
*
* Author:seven
*
* Description:
*/
public class SevenWebView extends WebView {
public SevenWebView(Context context) {
super(context);
init(context);
}
public SevenWebView(Context context, AttributeSet attrs) {
super(context, attrs);
init(context);
}
/**
* 初始化设置,和WebSettings搭配使用
* @param context
*/
private void init(Context context) {
WebSettings webSettings = getSettings();
if (webSettings == null) return;
// 支持 Js 使用
webSettings.setJavaScriptEnabled(true);
// 开启DOM缓存,默认状态下是不支持LocalStorage的
webSettings.setDomStorageEnabled(true);
// 开启数据库缓存
webSettings.setDatabaseEnabled(true);
// 设置 WebView 的缓存模式
webSettings.setCacheMode(WebSettings.LOAD_DEFAULT);
// 支持缩放
webSettings.setSupportZoom(true);
// 设置 UserAgent 属性
webSettings.setUserAgentString("");
// 允许加载本地 html 文件/false
webSettings.setAllowFileAccess(true);
// 允许通过 file url 加载的 Javascript 读取其他的本地文件,Android 4.1 之前默认是true,在 Android 4.1 及以后默认是false,也就是禁止
webSettings.setAllowFileAccessFromFileURLs(false);
// 允许通过 file url 加载的 Javascript 可以访问其他的源,包括其他的文件和 http,https 等其他的源,
// Android 4.1 之前默认是true,在 Android 4.1 及以后默认是false,也就是禁止
// 如果此设置是允许,则 setAllowFileAccessFromFileURLs 不起做用
webSettings.setAllowUniversalAccessFromFileURLs(false);
//设置背景
setBackgroundColor(getResources().getColor(android.R.color.transparent));
}
}
注意重写shouldOverrideUrlLoading这个方法,需要调用webView去加载网页,否则,会有系统浏览器去加载的,理解这句话的意思第一次加载还是用webView,但是网页上如果有链接,就不会拦截了。
package com.seven.webview;
import android.graphics.Bitmap;
import android.net.http.SslError;
import android.util.Log;
import android.webkit.SslErrorHandler;
import android.webkit.WebResourceError;
import android.webkit.WebResourceRequest;
import android.webkit.WebResourceResponse;
import android.webkit.WebView;
import android.webkit.WebViewClient;
/**
* Time:2020/3/13
*
* Author:seven
*
* Description:
*/
public class SevenWebClient extends WebViewClient {
private String TAG=getClass().getSimpleName();
/**
* 当WebView得页面Scale值发生改变时回调
*/
@Override
public void onScaleChanged(WebView view, float oldScale, float newScale) {
super.onScaleChanged(view, oldScale, newScale);
Log.d(TAG, "onScaleChanged:oldScale="+oldScale+",newScale="+newScale);
}
/**
* 系统默认会通过手机浏览器打开网页,
* 为了能够直接通过WebView显示网页,则必须设置
* 是否在 WebView 内加载页面
*
* @param view
* @param url
* @return
*/
@Override
public boolean shouldOverrideUrlLoading(WebView view, String url) {
Log.d(TAG, "shouldOverrideUrlLoading: "+url);
view.loadUrl(url);
return true;
}
/**
* WebView 开始加载页面时回调,一次Frame加载对应一次回调
*
* @param view
* @param url
* @param favicon
*/
@Override
public void onPageStarted(WebView view, String url, Bitmap favicon) {
super.onPageStarted(view, url, favicon);
Log.d(TAG, "onPageStarted: "+url);
}
/**
* WebView 完成加载页面时回调,一次Frame加载对应一次回调
*
* @param view
* @param url
*/
@Override
public void onPageFinished(WebView view, String url) {
super.onPageFinished(view, url);
Log.d(TAG, "onPageFinished: "+url);
}
/**
* WebView 加载页面资源时会回调,每一个资源产生的一次网络加载,除非本地有当前 url 对应有缓存,否则就会加载。
*
* @param view WebView
* @param url url
*/
@Override
public void onLoadResource(WebView view, String url) {
super.onLoadResource(view, url);
Log.d(TAG, "onLoadResource: "+url);
}
/**
* WebView 可以拦截某一次的 request 来返回我们自己加载的数据,这个方法在后面缓存会有很大作用。
*
* @param view WebView
* @param request 当前产生 request 请求
* @return WebResourceResponse
*/
@Override
public WebResourceResponse shouldInterceptRequest(WebView view, WebResourceRequest request) {
return super.shouldInterceptRequest(view, request);
}
/**
* WebView 访问 url 出错
*
* @param view
* @param request
* @param error
*/
@Override
public void onReceivedError(WebView view, WebResourceRequest request, WebResourceError error) {
super.onReceivedError(view, request, error);
Log.d(TAG, "onReceivedError: "+error);
}
/**
* WebView ssl 访问证书出错,handler.cancel()取消加载,handler.proceed()对然错误也继续加载
*
* @param view
* @param handler
* @param error
*/
@Override
public void onReceivedSslError(WebView view, SslErrorHandler handler, SslError error) {
super.onReceivedSslError(view, handler, error);
Log.d(TAG, "onReceivedSslError: "+error);
}
}
onProgressChanged这个方法会回调网页加载的进度,可以做一些进度条什么的,显示友好效果。
package com.seven.webview;
import android.content.Context;
import android.graphics.Bitmap;
import android.util.Log;
import android.webkit.ConsoleMessage;
import android.webkit.JsPromptResult;
import android.webkit.JsResult;
import android.webkit.WebChromeClient;
import android.webkit.WebView;
/**
* Time:2020/3/13
*
* Author:seven
*
* Description:
*/
public class SevenWebChromeClient extends WebChromeClient {
private String TAG=getClass().getSimpleName();
private Context mContenxt;
public SevenWebChromeClient(Context context) {
this.mContenxt = context;
}
/**
* Js的日志打印
* @param consoleMessage
* @return
*/
@Override
public boolean onConsoleMessage(ConsoleMessage consoleMessage) {
Log.d(TAG, "onConsoleMessage: "+consoleMessage.message());
return super.onConsoleMessage(consoleMessage);
}
/**
* 当前 WebView 加载网页进度
*
* @param view
* @param newProgress
*/
@Override
public void onProgressChanged(WebView view, int newProgress) {
super.onProgressChanged(view, newProgress);
Log.d(TAG, "onProgressChanged: "+newProgress);
}
/**
* Js 中调用 alert() 函数,产生的对话框
*
* @param view
* @param url
* @param message
* @param result
* @return
*/
@Override
public boolean onJsAlert(WebView view, String url, String message, JsResult result) {
Log.d(TAG, "onJsAlert: "+message);
return super.onJsAlert(view, url, message, result);
}
/**
* 处理 Js 中的 Confirm 对话框
*
* @param view
* @param url
* @param message
* @param result
* @return
*/
@Override
public boolean onJsConfirm(WebView view, String url, String message, JsResult result) {
Log.d(TAG, "onJsConfirm: "+message);
return super.onJsConfirm(view, url, message, result);
}
/**
* 处理 JS 中的 Prompt对话框
*
* @param view
* @param url
* @param message
* @param defaultValue
* @param result
* @return
*/
@Override
public boolean onJsPrompt(WebView view, String url, String message, String defaultValue, JsPromptResult result) {
Log.d(TAG, "onJsPrompt: "+message);
return super.onJsPrompt(view, url, message, defaultValue, result);
}
/**
* 接收web页面的icon
*
* @param view
* @param icon
*/
@Override
public void onReceivedIcon(WebView view, Bitmap icon) {
super.onReceivedIcon(view, icon);
}
/**
* 接收web页面的 Title
*
* @param view
* @param title
*/
@Override
public void onReceivedTitle(WebView view, String title) {
super.onReceivedTitle(view, title);
Log.d(TAG, "onReceivedTitle: "+title);
}
}
在我自定义的WebView的时候,就已经设置过支持javascript了,所以这里只要实例化一个java对象,并通过addJavascriptInterface这个方法将java对象注入进去。
实力恶化一个java对象,被js调用的方法上需要加上@JavascriptInterface这个注解
package com.seven.webview;
import android.content.Context;
import android.webkit.JavascriptInterface;
import android.widget.Toast;
/**
* Time:2020/3/13
*
* Author:seven
*
* Description:
*/
public class NativeJsInterface {
private Context mContext;
public NativeJsInterface(Context context) {
mContext = context;
}
@JavascriptInterface
public void helloAndroid() {
Toast.makeText(mContext, "Hello Android!", Toast.LENGTH_SHORT).show();
}
@JavascriptInterface
public String getAndroid() {
Toast.makeText(mContext, "getAndroid", Toast.LENGTH_SHORT).show();
return "Android data";
}
}
package com.seven.webview;
import android.os.Build;
import android.os.Bundle;
import android.support.v7.app.AppCompatActivity;
import android.util.Log;
import android.webkit.ValueCallback;
import android.webkit.WebChromeClient;
import android.webkit.WebView;
import android.webkit.WebViewClient;
import butterknife.BindView;
import butterknife.ButterKnife;
import butterknife.OnClick;
public class MainActivity extends AppCompatActivity {
private String TAG=getClass().getSimpleName();
@BindView(R.id.seven_web)
WebView webView;
private WebViewClient webViewClient;
private WebChromeClient webChromeClient;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
ButterKnife.bind(this);
loadUrl();
supportJs();
}
/**
* 加载网页
*/
private void loadUrl() {
webViewClient = new SevenWebClient();
webChromeClient = new SevenWebChromeClient(this);
webView.setWebViewClient(webViewClient);
webView.setWebChromeClient(webChromeClient);
webView.loadUrl("file:///android_asset/html/ts.html");
}
/**
* 扩展JS对象,页面可直接使用NativeObject对象调用Android提供的方法
*/
private void supportJs() {
NativeJsInterface nativeJsInterface = new NativeJsInterface(this);
webView.addJavascriptInterface(nativeJsInterface, "NativeObject");
}
/**
* 调用JS的方法
*/
@OnClick(R.id.call_js)
public void onViewClicked() {
String jsMethod="javascript:androidCallJs()";
if(Build.VERSION.SDK_INT< Build.VERSION_CODES.KITKAT){
webView.loadUrl(jsMethod);
}else {
webView.evaluateJavascript(jsMethod, new ValueCallback<String>() {
@Override
public void onReceiveValue(String value) {
Log.d(TAG, "onReceiveValue: "+value);
}
});
}
}
}
6.写一个简单的网页
</html>
<html>
<head>
<meta charset="utf-8">
<title>测试页</title>
<script>
function callAndroid(){
NativeObject.helloAndroid();
}
function getAndroid(){
NativeObject.getAndroid();
}
function androidCallJs(){
alert('js方法被执行!!!')
}
function jumpApp(){
}
</script>
</head>
<body background="images/ts.png">
<center>
<button type="button" id="button" onclick="callAndroid()">helloAndroid</button>
</center>
<center>
<button type="button" id="button2" onclick="getAndroid()">getAndroid</button>
</center>
</center> <a href="scheme://host/path?query">重新打开自己</a></center>
</body>
</html>
通过以上6步就可以完成基本的加载网页,支持java和js互相调用的功能了。
android中的scheme是一种页面内跳转协议,是一种非常好的实现机制,通过定义自己的scheme协议,可以非常方便跳转app中的各个页面;通过scheme协议,服务器可以定制化告诉App跳转那个页面,可以通过通知栏消息定制化跳转页面,可以通过H5页面跳转页面等。schema也是【隐示启动】中的一种,在data属性下的,其余内容查看Android Intent的隐示启动(启动其余APP界面并传递数据)
Scheme的格式:客户端自定义的 URL 作为从一个应用调用另一个的基础,遵循 RFC 1808 (Relative Uniform Resource Locators) 标准。这跟我们常见的网页内容 URL 格式一样。一个普通的 URL 分为几个部分,scheme、host、relativePath、query。
1)首先,你自定义一个scheme,我在上面的页面代码中已经写过了。
2)如果在webViewclient拦截了,在webView中使用shouldOverrideUrlLoading中去拦截
public boolean shouldOverrideUrlLoading(WebView view, String url) {
Log.d(TAG, "shouldOverrideUrlLoading: "+url);
if (url.startsWith("scheme")) {
Log.d(TAG, "shouldOverrideUrlLoading: 处理自定义scheme-->" + url);
try {
// 以下固定写法
final Intent intent = new Intent(Intent.ACTION_VIEW,
Uri.parse(url));
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK
| Intent.FLAG_ACTIVITY_SINGLE_TOP);
mContext.startActivity(intent);
} catch (Exception e) {
// 防止没有安装的情况
e.printStackTrace();
Toast.makeText(mContext,"您所打开的第三方App未安装!",Toast.LENGTH_SHORT).show();
}
return true;
}
view.loadUrl(url);
return true;
}
3)如果没有拦截,准确的来说是没有设置过webViewClient,可以直接用页面的方法直接打开。Android清单文件如下:
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.seven.webview">
<uses-permission android:name="android.permission.INTERNET"/>
<application
android:allowBackup="true"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true"
android:usesCleartextTraffic="true"
android:theme="@style/AppTheme">
<activity android:name=".MainActivity">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
<intent-filter>
<action android:name="android.intent.action.VIEW"/>
<category android:name="android.intent.category.DEFAULT"/>
<category android:name="android.intent.category.BROWSABLE"/>
<data android:scheme="scheme"/>
</intent-filter>
</activity>
</application>
</manifest>
一般的做法都是拦截,在拦截出去启动app。不过也可以单独提供native方法给页面使用,让页面去跳转app。实现的方法可以有多种。
1.onPause() 尽力尝试暂停可以暂停的任何处理,如动画和地理位置。 不会暂停JavaScript。 要全局暂停JavaScript,可使用pauseTimers。onResume() 恢复onPause() 停掉的操作;pauseTimers() 暂停所有WebView的布局,解析和JavaScript定时器。 这个是一个全局请求,不仅限于这个WebView。resumeTimers() 恢复所有WebView的所有布局,解析和JavaScript计时器,将恢复调度所有计时器.另外注意 JS 端setTimeout()、setInterval() 方法使用,当不使用 pauseTimers() 和pauseTimers() ,从 Activity 返回上一个包含WebView 的Activity时,页面里的 setTimeout() 是不执行的,setInterval() 是可以恢复执行的。在适当的生命周期使用 pauseTimers() 和 pauseTimers() 既可以恢复setTimeout() 执行。
2.扩展的java方法是在子线程中执行的,更新UI需要切换至主线程,否则会报错!
3.跨域的小坑,记得设置setAllowFileAccessFromFileURLs(true)这个方法
4.页面和native保存账号的坑
原生和页面嵌套时,比如登录时,如何同步账号的问题,一般的解法都是使用cookieManage这个来保存原生登录时的cookie,以确保在网页操作时不至于需要重复登录。
5.文件选择,重写WebChromeClient的这几个方法
// For Android < 3.0
public void openFileChooser(ValueCallback<Uri> valueCallback) {
if(null!=valueCall){
valueCall.onValueSelect(valueCallback);
}
openImageChooserActivity();
}
// For Android >= 3.0
public void openFileChooser(ValueCallback valueCallback, String acceptType) {
if(null!=valueCall){
valueCall.onValueSelect(valueCallback);
}
openImageChooserActivity();
}
//For Android >= 4.1
public void openFileChooser(ValueCallback<Uri> valueCallback, String acceptType, String capture) {
// uploadMessage = valueCallback;
if(null!=valueCall){
valueCall.onValueSelect(valueCallback);
}
openImageChooserActivity();
}
// For Android >= 5.0
@Override
public boolean onShowFileChooser(WebView webView, ValueCallback<Uri[]> filePathCallback, WebChromeClient.FileChooserParams fileChooserParams) {
if(null!=valueCall){
valueCall.onValuesSelectAboveL(filePathCallback);
}
openImageChooserActivity();
return true;
}
private void openImageChooserActivity() {
Intent i = new Intent(Intent.ACTION_GET_CONTENT);
i.addCategory(Intent.CATEGORY_OPENABLE);
i.setType("image/*");
mContenxt.startActivityForResult(Intent.createChooser(i, "Image Chooser"), FILE_CHOOSER_RESULT_CODE);
}
安全漏洞·:Android 4.2 以下不要在使用 JavascriptInterface方式,4.2 以上需要添加注解 @JavascriptInterface 才能调用。(这部分和JsBrige 有关,更详细的内容后面会介绍)
当系统辅助功能服务被开启时,在 Android 4.4 以下的系统中,由系统提供的 WebView 组件都默认导出 ”accessibility” 和 ”accessibilityTraversal” 这两个接口,这两个接口同样存在远程任意代码执行的威胁,同样的需要通过 removeJavascriptInterface 方法将这两个对象删除。
super.removeJavascriptInterface("searchBoxJavaBridge_");
super.removeJavascriptInterface("accessibility");
super.removeJavascriptInterface("accessibilityTraversal");
webView缓存问题
参考:WebView缓存原理分析和应用
如何优化加载速度
参考:WebView性能、体验分析与优化
本文整理了一些webView常见用法、和js交互以及一些小坑。希望能对你有所帮助!如果觉得对你有用,我也觉得很高兴,毕竟技术是拿来分享的。