Android基础: WebView常用类、JS交互、内存泄漏
原始简单用法##
在布局文件中加入 WebView
<?xml version="1.0" encoding="utf-8"?>
<WebView xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/webview"
android:layout_width="match_parent"
android:layout_height="match_parent"
/>
使用LoadUrl()方法加载 WebView
WebView myWebView = (WebView) findViewById(R.id.webview);
// 如果在loadUrl()方法中,网页产生异常,并不会抛出到我们的 app 中
myWebView.loadUrl("http://www.example.com");
最后不要忘记添加权限
<manifest ... >
<uses-permission android:name="android.permission.INTERNET" />
...
</manifest>
打开系统浏览器
* Uri uri = Uri.parse("https://www.example.com");
* Intent intent = new Intent(Intent.ACTION_VIEW, uri);
* startActivity(intent);
WebView 常用方法
- public void onPause ()尽可能的暂停能够被暂停的进程,比如动画、定位,但是不能暂停 JavaScript 的调用。
- public void onResume ()从暂停状态恢复。
- public void pauseTimers ()停止应用内所有(不仅仅是当前 WebView)的 webView 的 界面绘制、元素解析、JavaScript执行等工作,当应用暂停的时候,调用这个方法能够有效降低 CPU 功耗。
- public void resumeTimers ()恢复所有的 WebView 的 layout,parsing,JavaScript timers 等。
- public void destroy ()销毁 WebView 的内部状态,该方法调用后,WebView 不会再执行任何操作。调用该方法之前,需要从视图系统中(比如Activity) remove(removeView) 掉该 WebView ,因为在构建的时候,WebView 持有了视图系统的上下文引用。
- public boolean canGoBack ()WebView是否有可回退的历史记录。
- public boolean canGoForward ()WebView 是否有可前进的历史记录。
- public void goBack () 回退到这个 WebView 的上一个历史页面。
- public void goForward () 前进到历史纪录中的前一个页面。对比网页浏览器的前进按钮。
- public void goBackOrForward (int steps)后退或者前进,steps 是正数就前进,负数就后退。
- public void clearCache (boolean includeDiskFiles)清除应用内所有 WebView 的缓存
- public void clearHistory ()通知 WebView 清除历史记录
- pageUp(boolean top):将WebView展示的页面滑动至顶部。
- pageDown(boolean bottom):将WebView展示的页面滑动至底部。
// 常见的让返回键在网页中回退,而不是直接退出网页
public boolean onKeyDown(int keyCode, KeyEvent event) {
if ((keyCode == KEYCODE_BACK) && mWebView.canGoBack()) {
mWebView.goBack();
return true;
}
return super.onKeyDown(keyCode, event);
}
三个常用类##
WebSettings###
//声明WebSettings子类,如果 WebView 已经 destroy,再调用 WebSettings 的方法会抛出 IllegalStateException
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); //网络可用就网络加载,否则使用缓存,通过改变常量值可以调整为只网络加载或者只缓存加载
webSettings.setAllowFileAccess(true); //设置可以访问文件
webSettings.setJavaScriptCanOpenWindowsAutomatically(true); //支持通过JS打开新窗口
webSettings.setLoadsImagesAutomatically(true); //支持自动加载图片
webSettings.setDefaultTextEncodingName("utf-8");//设置编码格式
// 设置网页字体大小
setTextZoom (int textZoom) //textZoom 默认大小是 100
// 设置是否支持导航
setGeolocationEnabled(boolean flag)
// 设置缓存模式和缓存地址的常规方法
File cacheFile = this.getApplicationContext().getCacheDir();
if (cacheFile != null) {
// 这个方法应该只被调用一次,重复调用会被无视
mWebView.getSettings().setAppCachePath(cacheFile.getAbsolutePath());
}
/**
* 设置缓存加载模式
* LOAD_DEFAULT(默认值):如果缓存可用且没有过期就使用,否则从网络加载
* LOAD_NO_CACHE:从网络加载
* LOAD_CACHE_ELSE_NETWORK:缓存可用就加载即使已过期,否则从网络加载
* LOAD_CACHE_ONLY:不使用网络,只加载缓存即使缓存不可用也不去网络加载
*/
int type = AppUtil.getNetWorkType(this);
switch (type) {
case AppUtil.NETWORKTYPE_4G:
case AppUtil.NETWORKTYPE_WIFI:
mWebView.getSettings().setCacheMode(WebSettings.LOAD_DEFAULT);
break;
default:
mWebView.getSettings().setCacheMode(WebSettings.LOAD_CACHE_ELSE_NETWORK);
break;
}
WebViewClient类###
处理各种通知和请求事件,当发生的事情影响到内容的渲染 (例如, 错误或表单提交) 时, 就会调用它,还可以在此处拦截 URL 加载。
-
WebResourceResponse shouldInterceptRequest(WebView view, WebResourceRequest request): 当WebView需要请求某个数据时,这个方法可以拦截该请求来告知app并且允许app本身返回一个数据来替代我们原本要加载的数据。
-
boolean shouldOverrideUrlLoading(WebView view, WebResourceRequest request): 当我们没有给WebView提供WebViewClient时,WebView如果要加载一个url会向ActivityManager寻求一个适合的处理者来加载该url(比如系统自带的浏览器),这通常是我们不想看到的。于是我们需要给WebView提供一个WebViewClient,并重写该方法返回true来告知WebView url的加载就在app中进行,返回false,表示当前 app 对 url 进行处理。这时便可以实现在app内访问网页。
-
onReceivedSslError(WebView view, SslErrorHandler handler, SslError error): 当WebView加载某个资源引发SSL错误时会回调该方法,这时WebView要么执行handler.cancel()取消加载,要么执行handler.proceed()方法继续加载(默认为cancel)。需要注意的是,这个决定可能会被保留并在将来再次遇到SSL错误时执行同样的操作。
-
onReceivedHttpError(WebView view, WebResourceRequest request, WebResourceResponse errorResponse):上一个方法提到onReceivedError并不会在服务器返回错误码时被回调,那么当我们需要捕捉HTTP ERROR并进行相应操作时应该怎么办呢?API23便引入了该方法。当服务器返回一个HTTP ERROR并且它的status code>=400时,该方法便会回调。这个方法的作用域并不局限于Main Frame,任何资源的加载引发HTTP ERROR都会引起该方法的回调,所以我们也应该在该方法里执行尽量少的操作,只进行非常必要的错误处理等。
-
onPageFinished(WebView view, String url):该方法只在WebView完成一个页面加载时调用一次(同样也只在Main frame loading时调用),我们可以可以在此时关闭加载动画,进行其他操作。特别注意:JS代码调用一定要在 onPageFinished() 回调之后才能调用,否则不会调用。
@Override
public boolean shouldOverrideUrlLoading(WebView view, String url) {
// 如果链接不是常规的 url , 尝试使用系统浏览器打开
if (URLUtil.isNetworkUrl(url)) {
view.loadUrl(url);
} else {
Intent intent = new Intent(Intent.ACTION_VIEW, Uri.parse(url));
PackageManager pm = getPackageManager();
List<ResolveInfo> activities = pm.queryIntentActivities(intent, 0);
if (activities.size() > 0) {
startActivity(intent);
}
}
return true;
}
// webView默认是不处理https请求的,页面显示空白,需要进行如下设置:
webView.setWebViewClient(new WebViewClient() {
@Override
public void onReceivedSslError(WebView view, SslErrorHandler handler, SslError error) {
handler.proceed(); //表示等待证书响应,继续进行请求
// handler.cancel(); //表示挂起连接,为默认方式
// handler.handleMessage(null); //可做其他处理
}
});
WebChromeClient类###
如果说WebViewClient是帮助WebView处理各种通知、请求事件的“内政大臣”的话,那么WebChromeClient就是辅助WebView处理Javascript的对话框,网站图标,网站title,加载进度等偏外部事件的“外交大臣”。
-
onProgressChanged(WebView view, int newProgress):当页面加载的进度发生改变时回调,用来告知主程序当前页面的加载进度。
-
onReceivedTitle(WebView view, String title):用来接收web页面的title,我们可以在这里将页面的title设置到Toolbar。
-
boolean onJsAlert(WebView view, String url, String message, JsResult result):处理Javascript中的Alert对话框
-
onShowCustomView(View view, WebChromeClient.CustomViewCallback callback):该方法在当前页面进入全屏模式时回调,主程序必须提供一个包含当前web内容(视频 or Something)的自定义的View。
-
onHideCustomView():该方法在当前页面退出全屏模式时回调,主程序应在这时隐藏之前show出来的View。
// gei WebView 添加一个进度条
@Override
public void onProgressChanged(WebView view, int newProgress) {
if (newProgress < 100) {
progress.setVisibility(View.VISIBLE);
progress.setProgress(newProgress);
} else {
progress.setProgress(newProgress);
progress.setVisibility(View.GONE);
}
super.onProgressChanged(view, newProgress);
}
// 将网页标题设置到 WebViewActivity
webview.setWebChromeClient(new WebChromeClient(){
@Override
public void onReceivedTitle(WebView view, String title) {
titleview.setText(title);
}
// 处理网页中有需要提交文件的情况
//For Android >= 4.1
public void openFileChooser(ValueCallback<Uri> valueCallback, String acceptType, String capture) {
uploadMessage = valueCallback;
openImageChooserActivity();
}
// For Android >= 5.0
@Override
public boolean onShowFileChooser(WebView webView, ValueCallback<Uri[]> filePathCallback, WebChromeClient.FileChooserParams fileChooserParams) {
uploadMessageAboveL = filePathCallback;
openImageChooserActivity();
return true;
}
...
// 在Activity 中选择文件和回调
private void openImageChooserActivity() {
Intent i = new Intent(Intent.ACTION_GET_CONTENT);
i.addCategory(Intent.CATEGORY_OPENABLE);
i.setType("image/*");
startActivityForResult(Intent.createChooser(i, "Image Chooser"), FILE_CHOOSER_RESULT_CODE);
}
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
super.onActivityResult(requestCode, resultCode, data);
if (requestCode == FILE_CHOOSER_RESULT_CODE) {
if (null == uploadMessage && null == uploadMessageAboveL) return;
Uri result = data == null || resultCode != RESULT_OK ? null : data.getData();
if (uploadMessageAboveL != null) {
onActivityResultAboveL(requestCode, resultCode, data);
} else if (uploadMessage != null) {
uploadMessage.onReceiveValue(result);
uploadMessage = null;
}
}
}
@TargetApi(Build.VERSION_CODES.LOLLIPOP)
private void onActivityResultAboveL(int requestCode, int resultCode, Intent intent) {
if (requestCode != FILE_CHOOSER_RESULT_CODE || uploadMessageAboveL == null)
return;
Uri[] results = null;
if (resultCode == Activity.RESULT_OK) {
if (intent != null) {
String dataString = intent.getDataString();
ClipData clipData = intent.getClipData();
if (clipData != null) {
results = new Uri[clipData.getItemCount()];
for (int i = 0; i < clipData.getItemCount(); i++) {
ClipData.Item item = clipData.getItemAt(i);
results[i] = item.getUri();
}
}
if (dataString != null)
results = new Uri[]{Uri.parse(dataString)};
}
}
uploadMessageAboveL.onReceiveValue(results);
uploadMessageAboveL = null;
}
Js与WebView交互##
对于Android调用JS代码的方法有2种:
- 通过WebView的webView.loadUrl("javascript:methodName(parameterValues)"); // 适用于无返回值的 JS方法
- 通过WebView的evaluateJavascript(String script, ValueCallback<String> resultCallback)// 适用于有返回值的 JS 方法
// 设置与Js交互的权限
webSettings.setJavaScriptEnabled(true);
try {
InputStream is = FileUtil.getStream(WebViewActivity.this, "raw://inithtml");
String js = FileUtil.readStreamString(is, "UTF-8");
mWebView.loadUrl("javascript:" + js);
} catch (IOException e) {
e.printStackTrace();
}
// 这种方法执行不会使页面刷新,但是 loadUrl执行 js 代码则会刷新页面
mWebView.evaluateJavascript("javascript:callJS()", new ValueCallback<String>() {
@Override
public void onReceiveValue(String value) {
//此处为 js 返回的结果
}
});
}
对于JS调用Android代码的方法有3种:
- 通过WebView的addJavascriptInterface()进行对象映射
- 通过 WebViewClient 的shouldOverrideUrlLoading ()方法回调拦截 url
- 通过 WebChromeClient 的onJsAlert()、onJsConfirm()、onJsPrompt()方法回调拦截JS对话框alert()、confirm()、prompt() 消息
// 1、在本地定义供 JS 调用的原生方法,被JS调用的方法必须加入@JavascriptInterface注解
@JavascriptInterface
public void show(String s){
Toast.makeText(getApplication(), s, Toast.LENGTH_SHORT).show();
}
// 2、通过WebView的addJavascriptInterface()进行对象映射,将JSObject类映射成为 JS 中的 JSObjectInJavascript 对象
mWebView.addJavascriptInterface(new JSObject(this), "JSObjectInJavascript");
// 3、编写 JS 方法,调用第一步中编写的原生方法
function toastClick(){
window.android.show("JavaScript called~!");
}
WebView 内存泄露###
// 在 onPause()中暂停 webview
mWebView.pauseTimers
//在 onDestroy 中释放资源
if (mWebView != null) {
ViewParent parent = mWebView.getParent();
if (parent != null) {
((ViewGroup) parent).removeView(mWebView);
}
mWebView.removeAllViews();
mWebView.destroy();
mWebView = null;
}
参考
Android:你要的WebView与 JS 交互方式 都在这里了
Android:最全面的 Webview 详解
WebView·开车指南