问题背景

安卓日常开发和学习过程中,经常会使用到webview来加载网页,比如前两天过来了这样一个需求:拦截某一个链接不执行此链接,执行指定跳转到其他activity页面。这就要求我们对webview加载的URL进行判断,如果是属于需要屏蔽的URL,我们就进行拦截,并且执行相应的操作。那么首先,我们来熟悉下webview的正常使用操作。

问题分析

1、webview的基本使用

(1)首先,我们在布局文件中来添加WebView控件,代码如下:

  1. <?xml version="1.0" encoding="utf-8"?>
  2. <androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
  3. xmlns:tools="http://schemas.android.com/tools"
  4. android:layout_width="match_parent"
  5. android:layout_height="match_parent"
  6. tools:context="composer.WebviewlActivity">
  7. <WebView
  8. android:id="@+id/webView"
  9. android:layout_width="match_parent"
  10. android:layout_height="match_parent"/>
  11. </androidx.constraintlayout.widget.ConstraintLayout>

(2)代码中让WebView控件加载显示网页,代码如下:

  1. class WebviewlActivity : AppCompatActivity() {
  2. private lateinit var binding: ActivityOpenGlBinding
  3. override fun onCreate(savedInstanceState: Bundle?) {
  4. super.onCreate(savedInstanceState)
  5. binding = ActivityOpenGlBinding.inflate(LayoutInflater.from(this));
  6. setContentView(binding.root)
  7. initWebview()
  8. }
  9. private fun initWebview() {
  10. // 访问网页
  11. binding.webView.loadUrl("http://www.baidu.com")
  12. // 系统默认会通过手机浏览器打开网页,为了能够直接通过WebView显示网页,则必须设置
  13. binding.webView.webViewClient = object : WebViewClient() {
  14. override fun shouldOverrideUrlLoading(view: WebView, url: String?): Boolean {
  15. // 使用WebView加载显示url
  16. if (url != null) {
  17. view.loadUrl(url)
  18. }
  19. //返回true
  20. return true
  21. }
  22. }
  23. }
  24. }

我们需要加载网络上的数据内容,因此还需要添加网络权限:

  1. <!-- 添加网络权限 -->
  2. <uses-permission android:name="android.permission.INTERNET" />

同时因为可能我们测试要支持http的URL格式进行明文网络传输,manifest中需要配置:

  1. <application
  2. ...
  3. android:usesCleartextTraffic="true">

运行结果如下: image.png

2、webview的常用方法

(1)清除缓存

  1. // 清除网页访问留下的缓存
  2. // 由于内核缓存是全局的因此这个方法不仅仅针对webview而是针对整个应用程序.
  3. Webview.clearCache(true);
  4. // 清除当前webview访问的历史记录
  5. // 只会webview访问历史记录里的所有记录除了当前访问记录
  6. Webview.clearHistory();
  7. // 这个api仅仅清除自动完成填充的表单数据,并不会清除WebView存储到本地的数据
  8. Webview.clearFormData()

(2)WebView 中网页的前进 / 后退

  1. webView.goBack();//跳到上个页面
  2. webView.goForward();//跳到下个页面
  3. webView.canGoBack();//是否可以跳到上一页(如果返回false,说明已经是第一页)
  4. webView.canGoForward();//是否可以跳到下一页(如果返回false,说明已经是最后一页)
  5. // 以当前的index为起始点前进或者后退到历史记录中指定的steps
  6. // 如果steps为负数则为后退,正数则为前进
  7. Webview.goBackOrForward(intsteps)
  8. // 重新加载当前请求
  9. Webview.reload()

(3)解决跳转用默认浏览器打开的问题

  1. webView.setWebViewClient(new WebViewClient(){
  2. @Override
  3. public boolean shouldOverrideUrlLoading(WebView view, String url) {
  4. view.loadUrl(url);// 强制在当前 WebView 中加载 url
  5. return true;
  6. }
  7. });

(4)Back键控制网页后退

  1. public boolean onKeyDown(int keyCode, KeyEvent event) {
  2. if ((keyCode == KEYCODE_BACK) && mWebView.canGoBack()) {
  3. mWebView.goBack();
  4. return true;
  5. }
  6. return super.onKeyDown(keyCode, event);
  7. }

3、webview使用相关关键类

(1)WebSettings类

  1. // 声明WebSettings子类
  2. WebSettings webSettings = webView.getSettings();
  3. // 如果访问的页面中要与Javascript交互,则webview必须设置支持Javascript
  4. webSettings.setJavaScriptEnabled(true);
  5. // 支持插件
  6. webSettings.setPluginsEnabled(true);
  7. // 设置自适应屏幕,两者合用
  8. webSettings.setUseWideViewPort(true); //将图片调整到适合webview的大小
  9. webSettings.setLoadWithOverviewMode(true); // 缩放至屏幕的大小
  10. // 缩放操作
  11. webSettings.setSupportZoom(true); //支持缩放,默认为true。是下面那个的前提。
  12. webSettings.setBuiltInZoomControls(true); //设置内置的缩放控件。若为false,则该WebView不可缩放
  13. webSettings.setDisplayZoomControls(false); //隐藏原生的缩放控件
  14. // 其他细节操作
  15. webSettings.setCacheMode(WebSettings.LOAD_CACHE_ELSE_NETWORK); //关闭webview中缓存
  16. webSettings.setAllowFileAccess(true); //设置可以访问文件
  17. webSettings.setJavaScriptCanOpenWindowsAutomatically(true); //支持通过JS打开新窗口
  18. webSettings.setLoadsImagesAutomatically(true); //支持自动加载图片
  19. weSettings.setDefaultTextEncodingName("utf-8");//设置编码格式

(2)WebViewClient类

  1. private fun initWebview() {
  2. // 访问网页
  3. binding.webView.loadUrl("http://www.baidu.com")
  4. // 系统默认会通过手机浏览器打开网页,为了能够直接通过WebView显示网页,则必须设置
  5. binding.webView.webViewClient = object : WebViewClient() {
  6. override fun shouldOverrideUrlLoading(view: WebView, url: String?): Boolean {
  7. // 使用WebView加载显示url
  8. if (url != null) {
  9. view.loadUrl(url)
  10. }
  11. //返回true
  12. return true
  13. }
  14. override fun onPageStarted(view: WebView?, url: String?, favicon: Bitmap?) {
  15. // 网页加载开始的操作
  16. super.onPageStarted(view, url, favicon)
  17. }
  18. override fun onPageFinished(view: WebView?, url: String?) {
  19. // 网页加载结束的操作
  20. super.onPageFinished(view, url)
  21. }
  22. }
  23. }

(3)WebChromeClient类

  1. webView.setWebChromeClient(new WebChromeClient(){
  2. // 获得网页的加载进度并显示
  3. @Override
  4. public void onProgressChanged(WebView view, int newProgress) {
  5. Log.d("MainActivity","newProgress:"+ newProgress );
  6. super.onProgressChanged(view, newProgress);
  7. }
  8. // 输出Web 端日志
  9. @Override
  10. public boolean onConsoleMessage(ConsoleMessage consoleMessage) {
  11. Log.d("MainActivity","consoleMessage:"+ consoleMessage.message() );
  12. return super.onConsoleMessage(consoleMessage);
  13. }
  14. // 获取Web页中的标题
  15. @Override
  16. public void onReceivedTitle(WebView view, String title) {
  17. super.onReceivedTitle(view, title);
  18. Log.d("MainActivity","标题:"+ title);
  19. }
  20. // 获取Web页中的图标
  21. @Override
  22. public void onReceivedIcon(WebView view, Bitmap icon) {
  23. super.onReceivedIcon(view, icon);
  24. }
  25. // 获取Web页中 Js中调用alert()函数,产生的对话框
  26. @Override
  27. public boolean onJsAlert(WebView view, String url, String message, final JsResult result) {
  28. Log.d("MainActivity","onJsAlert:"+ message);
  29. return super.onJsAlert(view, url, message, result);
  30. }
  31. @Override
  32. public boolean onJsPrompt(WebView view, String url, String message, String defaultValue, JsPromptResult result) {
  33. Log.d("MainActivity","onJsPrompt:"+ message);
  34. return super.onJsPrompt(view, url, message, defaultValue, result);
  35. }
  36. @Override
  37. public boolean onJsConfirm(WebView view, String url, String message, JsResult result) {
  38. Log.d("MainActivity","onJsConfirm:"+ message);
  39. return super.onJsConfirm(view, url, message, result);
  40. }
  41. });

4、如何避免WebView内存泄露?

(1)建议不要在xml布局文件中定义Webview控件 ,而是在需要的时候在Activity中直接创建,并且getApplicationgContext()上下文对象推荐使用,代码如下:

  1. LinearLayout.LayoutParams params = new LinearLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT);
  2. webView = new WebView(getApplicationContext());
  3. webView.setLayoutParams(params);
  4. mLayout.addView(webView);

(2)在Activity销毁(WebView)时,先让WebView加载null内容,然后移除WebView,再销毁WebView,最后把WebView设置为null。代码如下:

  1. @Override
  2. protected void onDestroy() {
  3. if (webView != null) {
  4. webView.loadDataWithBaseURL(null, "", "text/html", "utf-8", null);
  5. webView.clearHistory(); ((ViewGroup)
  6. webView.getParent()).removeView(mWebView);
  7. webView.destroy();
  8. webView = null;
  9. }
  10. super.onDestroy();
  11. }

问题解决

上面是对webview基本使用的一些总结,现在回到我们第一节背景中提到的需求:拦截某一个链接不执行此链接,执行指定跳转到其他activity页面。这就要求我们对webview加载的URL进行判断,如果是属于需要屏蔽的URL,我们就进行拦截,并且执行相应的操作。 根据前面的梳理总结,不难推出,这个需求就是需要在shouldOverrideUrlLoading方法中做处理,代码如下:

  1. webView.setWebViewClient(new WebViewClient() {
  2. @Override
  3. // 在点击请求的是链接是才会调用,重写此方法返回true表明点击网页里面的链接还是在当前的webview里跳转,不跳到浏览器那边。这个函数我们可以做很多操作,比如我们读取到某些特殊的URL,于是就可以不打开地址,取消这个操作,进行预先定义的其他操作,这对一个程序是非常必要的。
  4. public boolean shouldOverrideUrlLoading(WebView view, String url) {
  5. // 判断url链接中是否含有某个字段,如果有就执行指定的跳转(不执行跳转url链接),如果没有就加载url链接
  6. if (url.contains("/urlxxx-")) {
  7. Intent i = new Intent(MainActivity.this, MainActivity.class);
  8. startActivity(i);
  9. // 返回true,拦截URL,避免走后面的加载流程。
  10. return true;
  11. } else {
  12. return false;
  13. }
  14. }
  15. });

问题总结

本文从一个webview加载网页的具体需求出发,先对webview的基本使用进行了相关梳理,然后对该需求的处理方案进行了总结,有兴趣的同学可以进一步深入研究。