WebView在Android应用开发中非常有用,在需要展示网页内容或者与网页交互的场景中。例如,在微信或微博等应用程序中,WebView常用于打开应用程序内的共享超链接。通过WebView在应用中直接展示网页内容,提供了更为丰富的用户体验。
WebView的生命周期:
onResume()
:当WebView处于活跃状态时,会回调此方法。WebView可以正常执行网页的响应,包括加载网页内容、执行JavaScript等。onPause()
:当WebView被切换到后台或失去焦点时,会回调此方法。WebView会暂停所有进行中的动作,如DOM的解析、CSS和JavaScript的执行等,以降低CPU功耗。destroy()
:当WebView需要被销毁以释放资源时,会调用此方法。在这个阶段,应确保所有与WebView相关的资源都被正确清理,以避免内存泄漏。
为了正确管理WebView的生命周期,应跟随Activity的生命周期方法来调用WebView的生命周期方法。例如,当Activity进入onResume状态时,应调用WebView的onResume方法;当Activity进入onPause状态时,应调用WebView的onPause方法;当Activity被销毁时,应确保WebView也被正确销毁。
@Override
protected void onResume() {
super.onResume();
//恢复webview的状态(不靠谱)
webView.resumeTimers();
//激活webView的状态,能正常加载网页
webView.onResume();
}
@Override
protected void onPause() {
super.onPause();
//当页面被失去焦点被切换到后台不可见状态,需要执行onPause
//通过onPause动作通知内核暂停所有的动作,比如DOM的解析、plugin的执行、JavaScript执行。
webView.onPause();
//当应用程序(存在webview)被切换到后台时,这个方法不仅仅针对当前的webview而是全局的全应用程序的webview
//它会暂停所有webview的layout,parsing,javascripttimer。降低CPU功耗。(不靠谱)
webView.pauseTimers();
}
@Override
protected void onDestroy() {
super.onDestroy();
//在关闭了Activity时,如果Webview的音乐或视频,还在播放。就必须销毁Webview
//但是注意:webview调用destory时,webview仍绑定在Activity上
//这是由于自定义webview构建时传入了该Activity的context对象
//因此需要先从父容器中移除webview,然后再销毁webview:
ViewGroup parent = findViewById(R.id.container);
parent.removeView(webView);
webView.destroy();
}
WebView使用
添加网络权限
- 布局文件添加WebView控件
- 初始化WebView
WebView webView = (WebView) findViewById(R.id.webview);
- 设置WebSettings 通过WebSettings类来配置WebView的一些设置项,比如是否支持JavaScript,是否允许缩放等。
//声明WebSettings子类
WebSettings webSettings = webView.getSettings();
//如果访问的页面中要与Javascript交互,则webview必须设置支持Javascript
webSettings.setJavaScriptEnabled(true);
//支持插件
webSettings.setPluginsEnabled(true);
//设置自适应屏幕,两者合用
webSettings.setUseWideViewPort(true); //将图片调整到适合webview的大小
webSettings.setLoadWithOverviewMode(true); // 缩放至屏幕的大小
//缩放操作
webSettings.setSupportZoom(true); //支持缩放,默认为true。是下面那个的前提。
webSettings.setBuiltInZoomControls(true); //设置内置的缩放控件。若为false,则该WebView不可缩放
webSettings.setDisplayZoomControls(false); //隐藏原生的缩放控件
//其他细节操作
webSettings.setCacheMode(WebSettings.LOAD_CACHE_ELSE_NETWORK); //关闭webview中缓存
webSettings.setAllowFileAccess(true); //设置可以访问文件
webSettings.setJavaScriptCanOpenWindowsAutomatically(true); //支持通过JS打开新窗口
webSettings.setLoadsImagesAutomatically(true); //支持自动加载图片
webSettings.setDefaultTextEncodingName("utf-8");//设置编码格式
- 加载网页内容 WebView可以加载远程网页或本地HTML资源。使用
loadUrl
方法加载一个网页的URL,或者使用loadData
方法加载一段HTML数据。
webView.loadUrl("https://www.baidu.com"); // 加载远程网页
加载本地的HTML文件:
webView.loadUrl("file:///android_asset/index.html"); // 加载本地HTML文件
加载HTML数据:
String goods_content="我的第一个段落。
";
webView.loadDataWithBaseURL(null, WebUtil.getHtmlData(goods_content), "text/html", "utf-8", null);
public static String getHtmlData(String bodyHTML) {
String head = "" +
" " +
"" +
"";
return "" + head + "" + bodyHTML + "";
}
- 处理网页加载事件 常规用法,复写shouldOverrideUrlLoading()方法,使打开网页时不调用系统浏览器, 而是在WebView中显示。
webView.setWebViewClient(new WebViewClient(){
@Override
public boolean shouldOverrideUrlLoading(WebView view, String url) {
view.loadUrl(url);
return true;
}
});
通过WebViewClient或WebChromeClient类来处理网页加载过程中的一些事件,比如页面开始加载、页面加载完成、出现错误等。
WebViewClient webViewClient = new WebViewClient() {
@Override
public boolean shouldOverrideUrlLoading(WebView view, String url) {
if (Uri.parse(url).getHost().equals("www.baidu.com")) {
Intent intent = new Intent(Intent.ACTION_VIEW, Uri.parse(url));
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
context.startActivity(intent);
return true;
}
return false;
}
@Override
public void onPageStarted(WebView view, String url, Bitmap favicon) {
// TODO Auto-generated method stub
super.onPageStarted(view, url, favicon);
Log.i(TAG, "onPageStarted:页面开始加载");
}
@Override
public void onPageFinished(WebView view, String url) {
// TODO Auto-generated method stub
super.onPageFinished(view, url);
Log.i(TAG, "onPageStarted:页面加载结束");
}
@Override
public void onLoadResource(WebView view, String url) {
// TODO Auto-generated method stub
super.onLoadResource(view, url);
Log.i(TAG, "onLoadResource:加载资源指定的网址");
}
@Override
public WebResourceResponse shouldInterceptRequest(WebView view, String url) {
// TODO Auto-generated method stub
Log.i(TAG, "shouldInterceptRequest");
return super.shouldInterceptRequest(view, url);
}
@Override
public void onReceivedError(WebView view, int errorCode,
String description, String failingUrl) {
// TODO Auto-generated method stub
super.onReceivedError(view, errorCode, description, failingUrl);
view.loadUrl("file:///android_asset/error.html");
Log.i(TAG, "onReceivedError");
}
@Override
public void onFormResubmission(WebView view, Message dontResend,
Message resend) {
// TODO Auto-generated method stub
super.onFormResubmission(view, dontResend, resend);
Log.i(TAG, "onFormResubmission");
}
@Override
public void doUpdateVisitedHistory(WebView view, String url,
boolean isReload) {
// TODO Auto-generated method stub
super.doUpdateVisitedHistory(view, url, isReload);
Log.i(TAG, "doUpdateVisitedHistory");
}
@Override
public void onReceivedSslError(WebView view, SslErrorHandler handler,
SslError error) {
// view.loadUrl("file:///android_asset/error.html");
// TODO Auto-generated method stub
super.onReceivedSslError(view, handler, error);
Log.i(TAG, "onReceivedSslError");
}
@Override
public void onReceivedHttpAuthRequest(WebView view,
HttpAuthHandler handler, String host, String realm) {
// TODO Auto-generated method stub
super.onReceivedHttpAuthRequest(view, handler, host, realm);
Log.i(TAG, "onReceivedHttpAuthRequest");
}
@Override
public boolean shouldOverrideKeyEvent(WebView view, KeyEvent event) {
Log.i(TAG, "shouldOverrideKeyEvent");
// TODO Auto-generated method stub
return super.shouldOverrideKeyEvent(view, event);
}
@Override
public void onScaleChanged(WebView view, float oldScale, float newScale) {
// TODO Auto-generated method stub
super.onScaleChanged(view, oldScale, newScale);
Log.i(TAG, "onScaleChanged");
}
@Override
public void onReceivedLoginRequest(WebView view, String realm,
String account, String args) {
// TODO Auto-generated method stub
super.onReceivedLoginRequest(view, realm, account, args);
Log.i(TAG, "onReceivedLoginRequest");
}
});
WebChromeClient辅助WebVlew处理Javascrlpt的对话框,网站图标,网站tltle,加载进度等。
webView.setWebChromeClient(new WebChromeClient() {
@Override
public void onProgressChanged(WebView view, int newProgress) {
// TODO Auto-generated method stub
super.onProgressChanged(view, newProgress);
if (newProgress <= 100) {
Log.i(TAG, newProgress + "===onProgressChanged===");
}
}
@Override
public void onReceivedTitle(WebView view, String title) {
// TODO Auto-generated method stub
super.onReceivedTitle(view, title);
Message message = new Message();
message.what = 100;
message.obj = title;
handler.sendMessage(message);
}
@Override
public void onReceivedIcon(WebView view, Bitmap icon) {
// TODO Auto-generated method stub
super.onReceivedIcon(view, icon);
Message message = new Message();
message.what = 200;
message.obj = icon;
handler.sendMessage(message);
}
@Override
public void onReceivedTouchIconUrl(WebView view, String url,
boolean precomposed) {
// TODO Auto-generated method stub
super.onReceivedTouchIconUrl(view, url, precomposed);
Log.i(TAG, "====onReceivedTouchIconUrl====");
}
@Override
public void onRequestFocus(WebView view) {
// TODO Auto-generated method stub
super.onRequestFocus(view);
Log.i(TAG, "====onRequestFocus====");
}
@Override
public boolean onJsAlert(final WebView view, String url, String message,
JsResult result) {
final AlertDialog.Builder builder = new AlertDialog.Builder(
view.getContext());
builder.setTitle("对话框").setMessage(message)
.setPositiveButton("确定", null);
builder.setOnKeyListener(new OnKeyListener() {
public boolean onKey(DialogInterface dialog, int keyCode,
KeyEvent event) {
Log.v("onJsAlert", "keyCode==" + keyCode + "event=" + event);
return true;
}
});
// 禁止响应按back键的事件
builder.setCancelable(false);
AlertDialog dialog = builder.create();
dialog.show();
result.confirm();// 因为没有绑定事件,需要强行confirm,否则页面会变黑显示不了内容。
return true;
// return super.onJsAlert(view, url, message, result);
}
@Override
public boolean onJsConfirm(final WebView view, String url, String message,
final JsResult result) {
final AlertDialog.Builder builder = new AlertDialog.Builder(
view.getContext());
builder.setTitle("对话框").setMessage(message)
.setPositiveButton("确定", new OnClickListener() {
public void onClick(DialogInterface dialog, int which) {
result.confirm();
}
}).setNeutralButton("取消", new OnClickListener() {
public void onClick(DialogInterface dialog, int which) {
result.cancel();
}
});
builder.setOnCancelListener(new OnCancelListener() {
@Override
public void onCancel(DialogInterface dialog) {
result.cancel();
}
});
// 屏蔽keycode等于84之类的按键,避免按键后导致对话框消息而页面无法再弹出对话框的问题
builder.setOnKeyListener(new OnKeyListener() {
@Override
public boolean onKey(DialogInterface dialog, int keyCode,
KeyEvent event) {
Log.v("onJsConfirm", "keyCode==" + keyCode + "event=" + event);
return true;
}
});
// 禁止响应按back键的事件
// builder.setCancelable(false);
AlertDialog dialog = builder.create();
dialog.show();
return true;
}
@Override
public boolean onJsPrompt(WebView view, String url, String message,
String defaultValue, final JsPromptResult result) {
final AlertDialog.Builder builder = new AlertDialog.Builder(
view.getContext());
builder.setTitle("对话框").setMessage(message);
final EditText et = new EditText(view.getContext());
et.setSingleLine();
et.setText(defaultValue);
builder.setView(et).setPositiveButton("确定", new OnClickListener() {
public void onClick(DialogInterface dialog, int which) {
result.confirm(et.getText().toString());
}
}).setNeutralButton("取消", new OnClickListener() {
public void onClick(DialogInterface dialog, int which) {
result.cancel();
}
});
// 屏蔽keycode等于84之类的按键,避免按键后导致对话框消息而页面无法再弹出对话框的问题
builder.setOnKeyListener(new OnKeyListener() {
public boolean onKey(DialogInterface dialog, int keyCode,
KeyEvent event) {
Log.v("onJsPrompt", "keyCode==" + keyCode + "event=" + event);
return true;
}
});
// 禁止响应按back键的事件
// builder.setCancelable(false);
AlertDialog dialog = builder.create();
dialog.show();
return true;
// return super.onJsPrompt(view, url, message, defaultValue,
// result);
}
});
- 处理JavaScript与Android代码的交互 如果网页中包含JavaScript,并且需要与Android代码进行交互,可以使用WebView的addJavascriptInterface方法来实现。在Android代码中定义一个对象,并在JavaScript中调用这个对象的方法。
编写html文件,放到assets文件里面:
function say(value){
callJS(value);
}
Android调用js:
public class MainActivity extends AppCompatActivity {
private WebView webview;
private TextView tvAndroid;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
webview = (WebView) findViewById(R.id.webview);
tvAndroid = (TextView) findViewById(R.id.tv_android);
tvAndroid.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
//Android调用js方法
//Android 4.4以下使用loadUrl,Android 4.4以上evaluateJavascript
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.JELLY_BEAN_MR2) {
webview.loadUrl("javascript:callJS('aaa')");
} else {
webview.evaluateJavascript("javascript:callJS('aaa')", new ValueCallback() {
@Override
public void onReceiveValue(String value) {
//此处为 js 返回的结果
Toast.makeText(MainActivity.this,value,Toast.LENGTH_SHORT).show();
}
});
}
}
});
initWebView();
}
public void initWebView() {
//启用JS脚本
webview.getSettings().setJavaScriptEnabled(true);
// 设置允许JS弹窗
webview.getSettings().setJavaScriptCanOpenWindowsAutomatically(true);
//加载网页
webview.loadUrl("file:///android_asset/index.html");
// 由于设置了弹窗检验调用结果,所以需要支持js对话框
// webview只是载体,内容的渲染需要使用webviewChromClient类去实现
// 通过设置WebChromeClient对象处理JavaScript的对话框
//设置响应js 的Alert()函数
webview.setWebChromeClient(new WebChromeClient(){
@Override
public boolean onJsAlert(WebView view, String url, String message, JsResult jsResult) {
new AlertDialog.Builder(view.getContext()).setMessage(message).setPositiveButton(android.R.string.ok, new AlertDialog.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
jsResult.confirm();
}
}).setCancelable(false).create().show();
return true;
}
});
//覆盖WebView默认使用第三方或系统默认浏览器打开网页的行为,使网页用WebView打开
webview.setWebViewClient(new WebViewClient() {
//override
public void onReceivedHttpAuthRequest(WebView view, HttpAuthHandler handler, String host, String realm) {
handler.proceed("admin", "sunlight");
int d = Log.d("MyWebViewClient", "onReceivedHttpAuthRequest");
}
@Override
public boolean shouldOverrideUrlLoading(WebView view, String uri) {
// TODO Auto-generated method stub
//返回值是true的时候控制去WebView打开,为false调用系统浏览器或第三方浏览器
view.loadUrl(uri);
return true;
}
});
}
}
「注意」js代码调用一定要在onPageFinished() 回调之后才能调用,否则不会调用。
js调用Android方法: 通过WebView的addJavascriptInterface()进行对象映射
public class MainActivity2 extends AppCompatActivity {
private WebView webview;
private TextView tvAndroid;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main2);
webview = (WebView) findViewById(R.id.webview);
tvAndroid = (TextView) findViewById(R.id.tv_android);
tvAndroid.setText("//继承自Object类,别名是aa,即在html可以直接用aa.showToast(\"哈哈哈\")来调用android方法\n" +
"public class MyObject extends Object {\n" +
" @JavascriptInterface\n" +
" public void showToast(String name){\n" +
" Toast.makeText(MainActivity2.this, \"您好!\"+name, Toast.LENGTH_SHORT).show();\n" +
" }\n" +
"}");
initWebView();
}
public void initWebView() {
// 设置与Js交互的权限
webview.getSettings().setJavaScriptEnabled(true);
//将java对象暴露给JavaScript脚本
//参数1:java对象,里面定义了java方法
//参数2:Java对象在js里的对象名,可以看作第一个参数的别名,可以随便取,即在html可以直接用aa.showToast("哈哈哈")来调用android方法
webview.addJavascriptInterface(new MyObject(), "aa");//AndroidtoJS类对象映射到js的test对象
//加载网页
webview.loadUrl("file:///android_asset/index2.html");
}
//继承自Object类,别名是aa,即在html可以直接用aa.showToast("哈哈哈")来调用android方法
public class MyObject extends Object {
// 定义JS需要调用的方法
// 被JS调用的方法必须加入@JavascriptInterface注解
@JavascriptInterface
public void showToast(String name){
Toast.makeText(MainActivity2.this, "您好!"+name, Toast.LENGTH_SHORT).show();
}
}
}
- 页面返回
@Override
public boolean onKeyDown(int keyCode, KeyEvent event) {
if ((keyCode == KeyEvent.KEYCODE_BACK) && webView.canGoBack()) {
webView.goBack();
return true;
}
return super.onKeyDown(keyCode, event);
}
- 缓存配置
WebSettings webSettings = webView.getSettings();
//优先使用缓存
webSettings.setCacheMode(WebSettings.LOAD_CACHE_ELSE_NETWORK);
//只在缓存中读取
webSettings.setCacheMode(WebSettings.LOAD_CACHE_ONLY);
/不使用缓存
webSettings.setCacheMode(WebSettings.LOAD_NO_CACHE);
- 清除缓存
//清除网页访问留下的缓存,由于内核缓存是全局的因此这个方法不仅仅针对webview而是针对整个应用程序.
webview.clearCache(true);
//清除当前webview访问的历史记录,只会webview访问历史记录里的所有记录除了当前访问记录.
webview.clearHistory ();
//这个api仅仅清除自动完成填充的表单数据,并不会清除WebView存储到本地的数据。
webview.clearFormData ();
注意
WebView在使用过程中存在一些常见问题「性能问题」WebView加载H5页面时,由于JS解析过程复杂、前端页面涉及较多的JS代码文件,以及Android机型碎片化导致的手机硬件性能差异,可能会导致页面加载速度较慢。每次加载H5页面都会产生较多的网络请求,包括HTML的主URL请求以及HTML引用的外部JS、CSS、字体文件、图片文件等,会耗费一定的流量和时间。
「内存管理问题」WebView是依附于Activity的,而Activity的生命周期和WebView启动的线程的生命周期可能不一致,可能导致WebView一直持有对Activity的引用而无法释放,从而引发内存泄漏问题。WebView使用不当,可能会导致应用程序在运行过程中占用大量内存,甚至引发应用崩溃。
「安全漏洞」WebView中可能存在一些安全漏洞,如远程代码执行漏洞、密码明文存储漏洞和域控制不严格漏洞等。可能导致攻击者利用WebView执行任意Java对象的方法,窃取用户信息,甚至控制用户设备。
「兼容性问题」不同版本的Android系统或不同品牌的手机可能存在WebView兼容性问题。例如,一些机型可能不支持WebGL,导致部分网页内容无法正常显示。WebView在加载某些特定格式的网页或执行某些特定操作时也可能出现兼容性问题。