WebView的核心用法与最佳实践,避免常见陷阱和优化技巧

移动开发 Android
WebView在使用过程中存在一些常见问题「性能问题」WebView加载H5页面时,由于JS解析过程复杂、前端页面涉及较多的JS代码文件,以及Android机型碎片化导致的手机硬件性能差异,可能会导致页面加载速度较慢。

WebView介绍

WebView是Android平台中用于显示网页内容的控件,基于Chromium项目(并非完整版的Chrome浏览器,不包括Chrome中的所有功能)。WebView使用WebKit引擎来渲染网页,可以很好地兼容Web标准,可以显示HTML、CSS和JavaScript等内容,还可以用于动态加载网页内容,并与网页进行交互,如点击链接、输入文本等。

WebView在Android应用开发中非常有用,在需要展示网页内容或者与网页交互的场景中。例如,在微信或微博等应用程序中,WebView常用于打开应用程序内的共享超链接。通过WebView在应用中直接展示网页内容,提供了更为丰富的用户体验。

WebView的生命周期:

  1. onResume():当WebView处于活跃状态时,会回调此方法。WebView可以正常执行网页的响应,包括加载网页内容、执行JavaScript等。
  2. onPause():当WebView被切换到后台或失去焦点时,会回调此方法。WebView会暂停所有进行中的动作,如DOM的解析、CSS和JavaScript的执行等,以降低CPU功耗。
  3. 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使用

添加网络权限

<uses-permission android:name="android.permission.INTERNET" />
  1. 布局文件添加WebView控件
<WebView
    android:id="@+id/webview"
    android:layout_width="match_parent"
    android:layout_height="match_parent" />
  1. 初始化WebView
WebView webView = (WebView) findViewById(R.id.webview);
  1. 设置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");//设置编码格式
  1. 加载网页内容 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="<p>我的第一个段落。</p>";
webView.loadDataWithBaseURL(null, WebUtil.getHtmlData(goods_content), "text/html", "utf-8", null);
 
public static String getHtmlData(String bodyHTML) {
 String head = "<head>" +
   "<meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0, user-scalable=no\"> " +
   "<style>div,p,img{max-width: 100%; width: 100% !important; height: auto !important;}" +
   "body {" +
   "margin-right:8px;" +//限定网页中的文字右边距为15px(可根据实际需要进行行管屏幕适配操作)
   "margin-left:8px;" +//限定网页中的文字左边距为15px(可根据实际需要进行行管屏幕适配操作)
   "margin-top:8px;" +//限定网页中的文字上边距为15px(可根据实际需要进行行管屏幕适配操作)
   "font-size:16px;" +//限定网页中文字的大小为40px,请务必根据各种屏幕分辨率进行适配更改
   "word-wrap:break-word;" +//允许自动换行(汉字网页应该不需要这一属性,这个用来强制英文单词换行,类似于word/wps中的西文换行)
   "}" +
   "p { margin: 0; }" +
   "</style>" +
   "</head>";
 return "<html>" + head + "<body>" + bodyHTML + "</body><ml>";
}
  1. 处理网页加载事件 常规用法,复写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() {
     /**
  * shouldOverrideUrlLoading
  * <p>
  * 当加载的网页需要重定向的时候就会回调这个函数告知我们应用程序是否需要接管控制网页加载,如果应用程序接管,
  *并且return true意味着主程序接管网页加载,如果返回false让webview自己处理。
  * </p>
  * 参数说明:
  * 
  * @param view
  *            接收WebViewClient的那个实例,前面看到webView.setWebViewClient(new
  *            MyAndroidWebViewClient()),即是这个webview。
  * @param url
  *            即将要被加载的url
  * @return true 当前应用程序要自己处理这个url, 返回false则不处理。 注:"post"请求方式不会调用这个回调函数
  */
 @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;
 }
 
 /**
  * onPageStarted 当内核开始加载访问的url时,会通知应用程序,对每个main frame
  * 这个函数只会被调用一次,页面包含iframe或者framesets 不会另外调用一次onPageStarted,
     * 当网页内内嵌的frame 发生改变时也不会调用onPageStarted。
  * 
  * 参数说明:
  * 
  * @param view
  *            接收WebViewClient的那个实例,前面看到webView.setWebViewClient(new
  *            MyAndroidWebViewClient()),即是这个webview。
  * @param url
  *            即将要被加载的url
  * @param favicon
  *            如果这个favicon已经存储在本地数据库中,则会返回这个网页的favicon,否则返回为null。
  */
 @Override
 public void onPageStarted(WebView view, String url, Bitmap favicon) {
  // TODO Auto-generated method stub
  super.onPageStarted(view, url, favicon);
  Log.i(TAG, "onPageStarted:页面开始加载");
 }
 
 /**
  * onPageFinished 当内核加载完当前页面时会通知我们的应用程序,这个函数只有在main
  * frame情况下才会被调用,当调用这个函数之后,渲染的图片不会被更新,如果需要获得新图片的通知可以使用@link
  * WebView.PictureListener#onNewPicture。 参数说明:
  * 
  * @param view
  *            接收WebViewClient的那个实例,前面看到webView.setWebViewClient(new
  *            MyAndroidWebViewClient()),即是这个webview。
  * @param url
  *            即将要被加载的url
  */
 @Override
 public void onPageFinished(WebView view, String url) {
  // TODO Auto-generated method stub
  super.onPageFinished(view, url);
  Log.i(TAG, "onPageStarted:页面加载结束");
 }
 
 /**
  * onLoadResource 通知应用程序WebView即将加载url 制定的资源
  * 
  * 参数说明:
  * 
  * @param view
  *            接收WebViewClient的那个实例,前面看到webView.setWebViewClient(new
  *            MyAndroidWebViewClient()),即是这个webview。
  * @param url
  *            即将加载的url 资源
  */
 @Override
 public void onLoadResource(WebView view, String url) {
  // TODO Auto-generated method stub
  super.onLoadResource(view, url);
  Log.i(TAG, "onLoadResource:加载资源指定的网址");
 }
 
 /**
  * shouldInterceptRequest
  * 通知应用程序内核即将加载url制定的资源,应用程序可以返回本地的资源提供给内核,若本地处理返回数据,内核不从网络上获取数据。
  * 
  * 参数说明:
  * 
  * @param view
  *            接收WebViewClient的那个实例,前面看到webView.setWebViewClient(new
  *            MyAndroidWebViewClient()),即是这个webview。
  * @param url
  *            raw url 制定的资源
  * @return 返回WebResourceResponse包含数据对象,或者返回null
  */
 @Override
 public WebResourceResponse shouldInterceptRequest(WebView view, String url) {
  // TODO Auto-generated method stub
  Log.i(TAG, "shouldInterceptRequest");
  return super.shouldInterceptRequest(view, url);
 }
 
 /**
  * onReceivedError
  * <p>
  * 当浏览器访问制定的网址发生错误时会通知我们应用程序 参数说明:
  * </p>
  * 
  * @param view
  *            接收WebViewClient的那个实例,前面看到webView.setWebViewClient(new
  *            MyAndroidWebViewClient()),即是这个webview。
  * @param errorCode
  *            错误号可以在WebViewClient.ERROR_* 里面找到对应的错误名称。
  * @param description
  *            描述错误的信息
  * @param failingUrl
  *            当前访问失败的url,注意并不一定是我们主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");
 }
 
 /**
  * 如果浏览器需要重新发送POST请求,可以通过这个时机来处理。默认是不重新发送数据。 参数说明
  * 
  * @param view
  *            接收WebViewClient的webview
  * @param dontResend
  *            浏览器不需要重新发送的参数
  * @param resend
  *            浏览器需要重新发送的参数
  */
 @Override
 public void onFormResubmission(WebView view, Message dontResend,
   Message resend) {
  // TODO Auto-generated method stub
  super.onFormResubmission(view, dontResend, resend);
  Log.i(TAG, "onFormResubmission");
 }
 
 /**
  * doUpdateVisitedHistory
  * 通知应用程序可以将当前的url存储在数据库中,意味着当前的访问url已经生效并被记录在内核当中。这个函数在网页加载过程中只会被调用一次。
  * 注意网页前进后退并不会回调这个函数。
  * 
  * 参数说明:
  * 
  * @param view
  *            接收WebViewClient的那个实例,前面看到webView.setWebViewClient(new
  *            MyAndroidWebViewClient()),即是这个webview。
  * @param url
  *            当前正在访问的url
  * @param isReload
  *            如果是true 这个是正在被reload的url
  */
 @Override
 public void doUpdateVisitedHistory(WebView view, String url,
   boolean isReload) {
  // TODO Auto-generated method stub
  super.doUpdateVisitedHistory(view, url, isReload);
  Log.i(TAG, "doUpdateVisitedHistory");
 }
 
 /**
  * 当网页加载资源过程中发现SSL错误会调用此方法。我们应用程序必须做出响应,是取消请求handler.cancel(),还是继续请求handler.
  * proceed();内核的默认行为是handler.cancel();
  * 
  * 参数说明:
  * 
  * @param view
  *            接收WebViewClient的那个实例,前面看到webView.setWebViewClient(new
  *            MyAndroidWebViewClient()),即是这个webview。
  * @param handler
  *            处理用户请求的对象。
  * @param error
  *            SSL错误对象
  * 
  */
 @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");
 }
 
 /**
  * onReceivedHttpAuthRequest 通知应用程序WebView接收到了一个Http
  * auth的请求,应用程序可以使用supplied 设置webview的响应请求。默认行为是cancel 本次请求。
  * 
  * 
  * 参数说明:
  * 
  * @param view
  *            接收WebViewClient的那个实例,前面看到webView.setWebViewClient(new
  *            MyAndroidWebViewClient()),即是这个webview。
  * @param handler
  *            用来响应WebView请求的对象
  * @param host
  *            请求认证的host
  * @param realm
  *            认真请求所在的域
  */
 @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");
 }
 
 /**
  * shouldOverrideKeyEvent
  * 提供应用程序同步一个处理按键事件的机会,菜单快捷键需要被过滤掉。如果返回true,webview不处理该事件,如果返回false,
  * webview会一直处理这个事件,因此在view 链上没有一个父类可以响应到这个事件。默认行为是return false;
  * 
  * 
  * 参数说明:
  * 
  * @param view
  *            接收WebViewClient的那个实例,前面看到webView.setWebViewClient(new
  *            MyAndroidWebViewClient()),即是这个webview。
  * @param event
  *            键盘事件名
  * @return 如果返回true,应用程序处理该时间,返回false 交由webview处理。
  */
 @Override
 public boolean shouldOverrideKeyEvent(WebView view, KeyEvent event) {
  Log.i(TAG, "shouldOverrideKeyEvent");
  // TODO Auto-generated method stub
  return super.shouldOverrideKeyEvent(view, event);
 }
 
 /**
  * 通知应用程序webview 要被scale。应用程序可以处理改事件,比如调整适配屏幕。
  */
 @Override
 public void onScaleChanged(WebView view, float oldScale, float newScale) {
  // TODO Auto-generated method stub
  super.onScaleChanged(view, oldScale, newScale);
  Log.i(TAG, "onScaleChanged");
 }
 
 /**
  * onReceivedLoginRequest 通知应用程序有个自动登录的帐号过程
  * 
  * 参数说明:
  * 
  * @param view
  *            请求登陆的webview
  * @param realm
  *            账户的域名,用来查找账户。
  * @param account
  *            一个可选的账户,如果是null 需要和本地的账户进行check, 如果是一个可用的账户,则提供登录。
  * @param args
  *            验证制定参数的登录用户
  */
 @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() {
 /**
  * onProgressChanged 通知应用程序当前网页加载的进度。
  * 
  * 参数说明:
  * 
  * @param view
  *            接收WebChromeClient的的webview实例
  * @param newProgress
  *            webview接受的进度
  */
 @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===");
  }
 }
 
 /**
  * 当document 的title变化时,会通知应用程序
  * 
  * 
  * 参数说明:
  * 
  * @param view
  *            接收WebViewClient的webview实例
  * @param title
  *            document新的title
  */
 @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);
 
 }
 
 /**
  * 当前页面有个新的favicon时候,会回调这个函数。 参数说明:
  * 
  * @param view
  *            接收WebViewClient的那个实例,前面看到webView.setWebViewClient(new
  *            MyAndroidWebViewClient()),即是这个webview。
  * @param icon
  *            当前页面的favicon 注:很多时间不会跳转到此回调函数,因为很多网站设置了icon,没有设置favicon,
  */
 @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);
 }
 
 /**
  * 通知应用程序 apple-touch-icon的 url
  * 
  * 参数说明:
  * 
  * @param view
  *            接收WebViewClient的那个实例,前面看到webView.setWebViewClient(new
  *            MyAndroidWebViewClient()),即是这个webview。
  * @param url
  *            apple-touch-icon 的服务端地址
  * @param precomposed
  *            如果precomposed 是true 则touch-icon是预先创建的
  * 
  *            Tips
  * 
  *            如果应用程序需要这个icon的话, 可以通过这个url获取得到 icon。
  */
 @Override
 public void onReceivedTouchIconUrl(WebView view, String url,
   boolean precomposed) {
  // TODO Auto-generated method stub
  super.onReceivedTouchIconUrl(view, url, precomposed);
  Log.i(TAG, "====onReceivedTouchIconUrl====");
 }
 
  
 /**
  * webview请求得到focus,发生这个主要是当前webview不是前台状态,是后台webview。
  */
 @Override
 public void onRequestFocus(WebView view) {
  // TODO Auto-generated method stub
  super.onRequestFocus(view);
  Log.i(TAG, "====onRequestFocus====");
 }
 
 /**
  * 覆盖默认的window.alert展示界面,
  */
 @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);
 }
 
 /**
  * 覆盖默认的window.confirm展示界面,
  */
 @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;
 }
 
 /**
  * 覆盖默认的window.prompt展示界面,
  */
 @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);
 }
});
  1. 处理JavaScript与Android代码的交互 如果网页中包含JavaScript,并且需要与Android代码进行交互,可以使用WebView的addJavascriptInterface方法来实现。在Android代码中定义一个对象,并在JavaScript中调用这个对象的方法。

编写html文件,放到assets文件里面:

<!DOCTYPE html>
<html>
 <head>
  <meta charset="utf-8">
 </head>
 <body>
   <div>
    function say(value){</br>
      callJS(value);</br>
    }
   </div>
 </body>
 
 <script>
  function callJS(value){
   alert(value);
   return value;
  }
 </script>
</html>

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<String>() {
                        @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()进行对象映射

<!DOCTYPE html>
<html>
 <head>
  <meta charset="utf-8">
 </head>
 <body>
   <button style="width:100%;height:50px; margin-top: 100px;" onclick="aa.showToast('哈哈哈')">js调用Android方法</button>
 </body>
 
</html>
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();
        }
    }
}
  1. 页面返回
@Override
public boolean onKeyDown(int keyCode, KeyEvent event) {
    if ((keyCode == KeyEvent.KEYCODE_BACK) && webView.canGoBack()) {
        webView.goBack();
        return true;
    }
    return super.onKeyDown(keyCode, event);
}
  1. 缓存配置
WebSettings webSettings = webView.getSettings();
//优先使用缓存
webSettings.setCacheMode(WebSettings.LOAD_CACHE_ELSE_NETWORK); 
//只在缓存中读取
webSettings.setCacheMode(WebSettings.LOAD_CACHE_ONLY);
/不使用缓存
webSettings.setCacheMode(WebSettings.LOAD_NO_CACHE);
  1. 清除缓存
//清除网页访问留下的缓存,由于内核缓存是全局的因此这个方法不仅仅针对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在加载某些特定格式的网页或执行某些特定操作时也可能出现兼容性问题。

责任编辑:武晓燕 来源: 沐雨花飞蝶
相关推荐

2017-03-22 09:44:04

DevOps转型陷阱实践

2021-02-28 13:19:42

大数据IT数据管理

2018-11-18 16:31:14

Kubernetes监控容器

2011-12-31 10:18:33

响应设计

2023-07-25 11:22:31

2024-03-08 10:50:44

Spring技术应用程序

2022-03-08 09:26:41

物联网安全物联网

2013-12-31 09:26:31

JavaScript技巧

2018-11-15 08:07:33

Kubernetes监控IT团队

2021-08-24 10:51:19

多云云计算云平台

2010-07-06 09:07:09

2014-12-17 09:46:30

AndroidListView最佳实践

2023-11-12 11:54:55

UX性能widget

2013-05-17 11:43:55

主数据数据管理

2021-03-01 15:52:14

开源开源软件陷阱

2012-03-29 09:35:32

WEBCSS

2018-08-02 15:09:20

PyTorch深度学习神经网络

2017-10-20 10:19:49

Kotlin语言陷阱

2012-03-19 09:55:38

CSS

2023-04-20 16:38:14

智能建筑物联网
点赞
收藏

51CTO技术栈公众号