Android WebView使用详解

本文详细介绍了Android中的WebView组件,包括其作用、使用方法、与JavaScript的交互、Js注入漏洞及其解决方案,并探讨了常见问题和最佳实践,旨在帮助开发者更好地理解和安全使用WebView。
部署运行你感兴趣的模型镜像

前言

现在越来越多的APP都内置了Web网页去加载视图,也就是我们常说的Hybrid APP混合应用,市场上很多平台都是这样做的,比如我们经常使用的淘宝、京东等电商平台。这些是如何实现的呢?在我们Android中有一个WebView组件,它就可以实现此类功能。它是Android中的原生UI控件,主要用于在APP应用中方便地访问远程网页或本地HTML资源,同时WebView也在Android中充当Java代码和JS代码之间交互的桥梁,实际上也可以将WebView看做一个功能最小化的浏览器。下面我们一起来看看WebView的一些常用的使用方法。

一、简介

WebView是Android系统提供的一个能显示网页的系统控件,它是一个特殊的View,同时也是一个ViewGroup,可以有很多其他子View。在Android 4.4以下(不包含4.4)系统WebView底层实现是采用WebKit内核,而在Android 4.4及其以上Google采用了Chromium内核作为系统WebView的底层内核支持。在这一变化中Android提供的WebView相关API并没有发生较大变化,在4.4上也兼容低版本的API并且引进了少部分API。这里简单介绍下基于Chromium的WebView和基于WebKit的WebView之间的差异,基于Chromium的Webview提供了更广的HTML5、CSS3、JavaScript支持,在Android系统版本5.0上基于Chromium 37,WebView提供了绝大多数的HTML5特性支持,除此之外Chromium也支持远程调试(Chrome DevTools)。WebKit JavaScript引起采用WebCore JavaScript在Android 4.4上换成了V8能直接提升JavaScript性能。

二、作用

1、显示和渲染Web页面
2、使用html文件(网络上或本地assets中)作为布局
3、可与JavaScript交互调用
注: WebView控件功能强大,除了具有一般View的属性和设置外,还可以对Url请求、页面加载、渲染、页面交互进行强大的处理。

三. 使用

3.1 配置网络权限

<uses-permission android:name="android.permission.INTERNET" />

3.2 Webview常用方法
3.2.1 WebView的状态

// 激活WebView为活跃状态,能正常执行网页的响应
webView.onResume() ;

// 当页面失去焦点或者切换到后台不可见状态时需要执行onPause方法
// 通过onPause方法通知内核暂停所有的动作,比如DOM解析、Plugin执行、JavaScript执行等
webView.onPause();

// 当应用程序(存在WebView)切换到后台时此方法不仅仅针对当前的WebView,而是全局、全应用程序的WebView
// 它会暂停所有WebView的layout、parsing、javascripttimer等,降低CPU功耗
webView.pauseTimers()

// 恢复pauseTimers状态
webView.resumeTimers();

// 停止当前加载
webView.stopLoading(); 

// 销毁WebView,当关闭了当前Activity时,如果WebView中的音乐或视频还在播放,此时就必须销毁当前的WebView
// 需要注意的是:WebView调用destory方法时,WebView仍然绑定在Activity上,这是由于自定义WebView构建时传入了该Activity的context对象
// 因此需要在销毁时先从父容器中移除当前的WebView,然后再销毁WebView
rootLayout.removeView(webView); 
// 清空子View
webView.removeAllViews();  
webView.destroy();

3.2.2 网页加载上一页和下一页

// 是否可以跳到上一页(如果返回false,说明已经是第一页)
Webview.canGoBack() 
// 跳到上个页面
Webview.goBack()

// 是否可以跳到下一页(如果返回false,说明已经是最后一页)                   
Webview.canGoForward()
// 跳到下个页面
Webview.goForward()

// 以当前的index为起始点前进或者后退到历史记录中指定的steps,如果steps为负数则为后退,正数则为前进
Webview.goBackOrForward(intsteps) 

例如:使用Back按键控制网页后退
问题:不做任何处理的前提下浏览网页时点击系统的Back按键,整个Browser浏览器会调用finish()方法而结束自身
目标:点击返回按键后只是网页回退而不是退出整个浏览器
方案:在当前Activity中处理并消费掉该Back事件

@Override
public boolean onKeyDown(int keyCode, KeyEvent event) {
    if ((keyCode == event.KEYCODE_BACK) && webView.canGoBack()) {
        webView.goBack();
        return true;
    }
    return super.onKeyDown(keyCode, event);
}

3.2.3 清除缓存数据

// 清除网页访问后留下的缓存,由于内核缓存是全局的,因此这个方法不仅仅针对WebView,而是针对整个应用程序
Webview.clearCache(true);

// 清除当前WebView访问的历史记录,只会将WebView访问的历史记录里中的所有记录清除,除了当前访问记录
Webview.clearHistory();

// 仅清除自动完成填充的表单数据,并不会清除WebView存储到本地的数据
Webview.clearFormData();

if (Build.VERSION.SDK_INT < Build.VERSION_CODES.JELLY_BEAN_MR2) {
    // 向Web端注入Java对象
    mWebView.removeJavascriptInterface("AndroidNative");
}

// 清除网页查找的高亮匹配字符
Webview.clearMatches();    
// 清除ssl信息
Webview.clearSslPreferences(); 

// 删除缓存数据库
context.deleteDatabase("webview.db"); 
context.deleteDatabase("webviewCache.db");

// 删除缓存文件夹
File file = CacheManager.getCacheFileBaseDir();
if (file != null && file.exists() && file.isDirectory()) {
    for (File item : file.listFiles()) {
        item.delete();
    }
    file.delete();
}

3.2.4 Cookie设置

// Cookie设置
CookieSyncManager.createInstance(this);
CookieManager cookieManager = CookieManager.getInstance();
cookieManager.setAcceptCookie(true);
String cookie = "name=xxx;age=18";
cookieManager.setCookie(URL, cookie);
CookieSyncManager.getInstance().sync();

// 获取Cookie
CookieManager cookieManager = CookieManager.getInstance();
String cookie = cookieManager.getCookie(URL);

// 清除Cookie
CookieSyncManager.createInstance(context);
CookieManager cookieManager = CookieManager.getInstance();
cookieManager.removeAllCookie();
CookieSyncManager.getInstance().sync();

3.3 常用类使用
3.3.1 WebSettings类的使用
注:对WebView进行配置和管理。

生成WebView组件(有两种方式):

// 方式1:直接在Activity中通过new关键字生成
WebView webView = new WebView(this);

// 方法2:在Activity的layout布局文件里添加WebView组件
WebView webView = (WebView) findViewById(R.id.webView);

使用WebSettings对WebView进行配置

// 声明WebSettings子类
WebSettings webSettings = webView.getSettings();

// 如果访问的页面中需要与JavaScript进行交互,则WebView必须设置支持JavaScript
webSettings.setJavaScriptEnabled(true);

// 设置字体缩放倍数,默认100
webSettings.setTextZoom(100);

// 支持插件
webSettings.setPluginsEnabled(true);

// 设置自适应屏幕,两者合用
webSettings.setUseWideViewPort(true);       // 支持双击缩放(wap网页不支持)
webSettings.setLoadWithOverviewMode(true);  // 缩放至屏幕大小

// 缩放操作
webSettings.setSupportZoom(true);           // 支持缩放,默认为true,是下面那个的前提
webSettings.setBuiltInZoomControls(true);   // 显示缩放按钮(wap网页不支持),若为false则该WebView不可缩放
webSettings.setDisplayZoomControls(false);  // 隐藏原生的缩放控件
webSettings.setLoadWithOverviewMode(true);  // 缩放至屏幕的大小

// 其他细节操作
webSettings.setLayoutAlgorithm(WebSettings.LayoutAlgorithm.SINGLE_COLUMN); // 支持内容重新布局
webSettings.setCacheMode(WebSettings.LOAD_CACHE_ELSE_NETWORK);  // 关闭WebView中缓存
webSettings.setAllowFileAccess(true);                           // 设置可以访问文件
webSettings.setJavaScriptCanOpenWindowsAutomatically(true);     // 支持通过js打开新窗口
webSettings.setLoadsImagesAutomatically(true);                  // 支持自动加载图片
webSettings.setDefaultTextEncodingName("utf-8");                // 设置编码格式
webSettings.supportMultipleWindows();   // 多窗口
webSettings.setNeedInitialFocus(true);  // 当WebView调用requestFocus时为WebView设置节点
// 开启DOM缓存
webSettings.setDomStorageEnabled(true);
// 开启数据库缓存
webSettings.setDatabaseEnabled(true);
// 支持启用缓存模式
webSettings.setAppCacheEnabled(true);
// 设置AppCache最大缓存值(现在官方已经不提倡使用,已废弃)
webSettings.setAppCacheMaxSize(8 * 1024 * 1024);
// Android私有缓存存储,如果你不调用setAppCachePath方法,WebView将不会产生这个目录
webSettings.setAppCachePath(getCacheDir().getAbsolutePath());
// 数据库路径
if (!hasKitkat()) {
    webSettings.setDatabasePath(getDatabasePath("html").getPath());
}
// 关闭密码保存提醒功能
webSettings.setSavePassword(false);
// 设置UserAgent属性
webSettings.setUserAgentString("");
// 允许通过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);

常见用法:设置WebView缓存
当加载html页面时WebView会在/data/data/包名目录下生成database与cache两个文件夹
请求的Url记录保存在WebViewCache.db,而Url的内容是保存在WebViewCache文件夹下

// 缓存模式有如下几种
// LOAD_CACHE_ONLY:不使用网络,只读取本地缓存数据
// LOAD_DEFAULT:(默认)根据cache-control决定是否从网络上读取数据
// LOAD_NO_CACHE:不使用缓存,只从网络上获取数据
// LOAD_CACHE_ELSE_NETWORK:只要本地有缓存,无论是否过期或者no-cache都使用缓存中的数据
// 优先使用缓存
webSettings.setCacheMode(WebSettings.LOAD_CACHE_ELSE_NETWORK);
// 不使用缓存
webSettings.setCacheMode(WebSettings.LOAD_NO_CACHE);

多种方式结合使用(离线加载)

if (NetWorkUtils.isConnected(getApplicationContext())) {
    webSettings.setCacheMode(WebSettings.LOAD_DEFAULT); // 根据cache-control来决定是否从网络上读取数据
} else {
    webSettings.setCacheMode(WebSettings.LOAD_CACHE_ELSE_NETWORK);  // 无网络时从本地获取数据即离线加载
}

webSettings.setDomStorageEnabled(true); // 开启DOM storage API功能
webSettings.setDatabaseEnabled(true);   // 开启database storage API功能
webSettings.setAppCacheEnabled(true);   // 开启Application Caches功能

String cacheDirPath = getFilesDir().getAbsolutePath() + APP_CACAHE_DIRNAME;
webSettings.setAppCachePath(cacheDirPath); // 设置Application Caches缓存目录

注:每个Application只调用一次WebSettings.setAppCachePath()和WebSettings.setAppCacheMaxSize()

3.3.2 WebViewClient类的使用
注:处理各类通知和请求事件。
loadUrl方法:

// 定义WebView组件
WebView webView = (WebView) findViewById(R.id.webView);
// 方式1:加载网页
webView.loadUrl("http://www.google.com/");
// 方式2:加载本地assets目录下的网页
webView.loadUrl("file:///android_asset/test.html");
// 方式3:加载手机本地的html页面
webView.loadUrl("content://com.android.htmlfileprovider/sdcard/test.html");

shouldOverrideUrlLoading方法:

// 复写shouldOverrideUrlLoading()方法,使打开网页时不调用系统浏览器而是在本WebView中显示
webView.setWebViewClient(new WebViewClient() {
    @Override
    public boolean shouldOverrideUrlLoading(WebView view, String url) {
        view.loadUrl(url);
        return true;
    }
});

其他方法:

webView.setWebViewClient(new WebViewClient() {
    @Override
    public void onPageStarted(WebView view, String url, Bitmap favicon) {
        super.onPageStarted(view, url, favicon);
        // 作用:开始载入页面时调用,我们可以设定一个loading动画,告知用户程序在等待网络响应。
    }

    @Override
    public void onPageFinished(WebView view, String url) {
        super.onPageFinished(view, url);
        // 作用:在页面加载结束时调用,我们可以关闭loading加载动画,切换程序动作。
    }

    @Override
    public void onLoadResource(WebView view, String url) {
        super.onLoadResource(view, url);
        // 作用:在加载页面资源时调用,每个资源(如图片)的加载都会调用一次。
    }

    @Override
    public void onReceivedError(WebView view, int errorCode, String description, String failingUrl) {
        super.onReceivedError(view, errorCode, description, failingUrl);
        // 作用:加载页面的服务器出现错误时(如404)调用。
        // App中使用WebView控件的时候遇到诸如404这类错误的时候,若显示浏览器里面的默认错误提示页就显得很难看了,
        // 那么此时我们的App就可以加载一个本地的错误提示页面即WebView如何加载一个本地的页面

        // 步骤1:定义一个html文件(error_handle.html)用于在出错时展示给用户看
        // 步骤2:将该html文件放置到代码根目录的assets文件夹下
        // 步骤3:复写WebViewClient的onReceivedError方法
        // 该方法回传了错误码,可根据错误类型进行不同的错误分类处理
        switch (errorCode) {
            case HttpStatus.SC_NOT_FOUND:
                view.loadUrl("file:///android_assets/error_handle.html");
                break;
        }
    }

    @Override
    public void onReceivedSslError(WebView view, SslErrorHandler handler, SslError error) {
        super.onReceivedSslError(view, handler, error);
        handler.proceed();  // 表示等待证书响应
        // handler.cancel();   // 表示挂起连接,为默认方式
        // handler.handleMessage(null);// 可做其他处理
    }

    @Override
    public void onScaleChanged(WebView view, float oldScale, float newScale) {
         super.onScaleChanged(view, oldScale, newScale);
         // 当WebView得页面Scale值发生改变时回调
     }

     /**
      * WebView可以拦截某一次的request来返回我们自己加载的数据,这个方法在后面缓存会有很大作用。
      *
      * @param view    WebView
      * @param request 当前产生 request 请求
      * @return WebResourceResponse
      */
     @Override
     public WebResourceResponse shouldInterceptRequest(WebView view, WebResourceRequest request) {
         return super.shouldInterceptRequest(view, request);

     }
});

3.3.3 WebChromeClient类的使用
注:辅助WebView处理JavaScript的对话框、网站图标、网站标题等等。

webView.setWebChromeClient(new WebChromeClient() {
    @Override
    public void onProgressChanged(WebView view, int newProgress) {
        super.onProgressChanged(view, newProgress);
        // 作用:获得网页的加载进度并显示
    }

    @Override
    public void onReceivedTitle(WebView view, String title) {
        super.onReceivedTitle(view, title);
        // 作用:获取Web网页中的标题
    }

    @Override
    public void onReceivedIcon(WebView view, Bitmap icon) {
          super.onReceivedIcon(view, icon);
          // 接收web页面的icon
      }

      @Override
      public boolean onJsAlert(WebView view, String url, String message, JsResult result) {
          // Js中调用alert()函数产生的对话框
          return super.onJsAlert(view, url, message, result);
      }

      @Override
      public boolean onJsConfirm(WebView view, String url, String message, JsResult result) {
          // 处理Js中的Confirm对话框
          return super.onJsConfirm(view, url, message, result);
      }

      @Override
      public boolean onJsPrompt(WebView view, String url, String message, String defaultValue, JsPromptResult result) {
          // 处理Js中的Prompt对话框
          return super.onJsPrompt(view, url, message, defaultValue, result);
      }
});

3.4 如何避免WebView内存泄露
3.4.1 不在xml中定义Webview,而是在Activity中进行创建且Context使用getApplicationContext()

LinearLayout.LayoutParams params = new LinearLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT);
WebView mWebView = new WebView(getApplicationContext());
mWebView.setLayoutParams(params);
mLayout.addView(mWebView);

3.4.2 在Activity销毁的时候,先将WebView加载null内容,然后移除WebView,再销毁WebView,最后置空

@Override
protected void onDestroy() {
    if (mWebView != null) {
        mWebView.loadDataWithBaseURL(null, "", "text/html", "utf-8", null);
        mWebView.clearHistory();

        ((ViewGroup) mWebView.getParent()).removeView(mWebView);
        mWebView.destroy();
        mWebView = null;
    }
    super.onDestroy();
}

四、与JavaScript交互

1、使用系统方法addJavascriptInterface注入Java对象来实现。

2、利用WebViewClient中shouldOverrideUrlLoading(WebView view, String url)接口来拦截操作。这个scheme是通过约定Url协议后双方各自解析,针对相应的Url使用iframe机制来调用对应的native代码来实现互通。

3、利用WebChromeClient中的onJsAlert、onJsConfirm、onJsPrompt提示接口来实现,其实也是属于拦截操作。

 // 开启Js可用
mWebView.getSettings().setJavaScriptEnabled(true);
// WebView注入即可
mWebView.addJavascriptInterface(new NativeInterface(this), "AndroidNative");
// 创建要注入的Java类
public class NativeInterface {

    private Context mContext;

    public NativeInterface(Context context) {
        mContext = context;
    }

    @JavascriptInterface
    public void hello() {
        Toast.makeText(mContext, "hello", Toast.LENGTH_SHORT).show();
    }

    @JavascriptInterface
    public void hello(String params) {
        Toast.makeText(mContext, params, Toast.LENGTH_SHORT).show();
    }

    @JavascriptInterface
    public String getAndroid() {
        Toast.makeText(mContext, "getAndroid", Toast.LENGTH_SHORT).show();
        return "Android Data";
    }
}
// Js编写
<script>
    function callHello() {
        AndroidNative.hello();
    }

    function callHello1() {
        AndroidNative.hello('Hello Android');
    }

    function callAndroid() {
        var temp = AndroidNative.getAndroid();
        console.log(temp);
        alert(temp);
    }
</script>

Native调用Js:mWebView.loadUrl(js);
Js调用Native :AndroidNative.getAndroid();

注: Android 4.2版本以下会存在漏洞,4.2以上需要添加@JavascriptInterface注解才能被调用到,Js调用方式不变。

五、Js注入漏洞

虽然可以通过注入方式来实现WebView和JS交互,但是实现功能的同时也带了安全问题,通过注入的Java类作为桥梁,JS 就可以利用这个漏洞来进行攻击操作。

常见漏洞:

目前已知的WebView漏洞有4个,它们分别是:
1、使用WebView中addJavascriptInterface接口会引起远程代码执行漏洞。
2、针对某些特定机型会存在addJavascriptInterface API引起的远程代码执行漏洞。
3、WebView中内置导出的“searchBoxJavaBridge_”Java Object接口可能被利用实现远程任意代码执行。
4、WebView中内置导出的“accessibility”和“accessibilityTraversal”两个Java Object接口可能被利用实现远程任意代码执行。

解决漏洞
1、在Android 4.2以下不在使用addJavascriptInterface方式,Android 4.2以上需要添加注解@JavascriptInterface才能调用。
2、解决方式如方式1即可。
3、创建WebView时使用removeJavascriptInterface方法将系统注入的”searchBoxJavaBridge_”对象删除。
4、当系统辅助功能服务被开启时,在Android 4.4以下系统中由系统提供的WebView组件都默认导出”accessibility”和”accessibilityTraversal”两个接口,这两个接口同样存在被远程任意代码执行的威胁,同样需要通过removeJavascriptInterface方法将这两个对象删除。

super.removeJavascriptInterface("searchBoxJavaBridge_");
super.removeJavascriptInterface("accessibility");
super.removeJavascriptInterface("accessibilityTraversal");

以上都是系统机制层面上出现的一些漏洞,还有一些是使用WebView时产生的漏洞,下面我们一起来看一下。

5、通过webSettings.setSavePassword(false)关闭密码保存提醒功能,防止明文密码存在本地被盗用。

6、WebView默认是可以使用File协议的,也就是设置setAllowFileAccess(true)方法,我们应主动将其设置为setAllowFileAccess(false)防止加载本地文件,移动版的Chrome默认禁止加载File协议的文件。

webSettings.setAllowFileAccess(true);   // 设置为false后将不能加载本地html文件
webSettings.setAllowFileAccessFromFileURLs(false);
webSettings.setAllowUniversalAccessFromFileURLs(false);
if (url.startsWith("file://")) {
    webSettings.setJavaScriptEnabled(false);
} else {
    webSettings.setJavaScriptEnabled(true);
}

修复案例

SafeWebView这个库解决了Android WebView中Js注入安全漏洞以及其它的一些异常问题。
解决问题如下:
1、WebView addJavascriptInterface安全漏洞问题;
2、支持网页将Js函数(function)传到Java层方便回调;
3、解决各种WebView的崩溃问题(附日志);
4、WebView设置代理(对不同Android系统版本调用Java反射实现);

第三方库解决

JsBridge
DSBridge-Android

六、常见问题

由于Android版本碎片化严重,在使用WebView的时候会遇到各种问题,特别是在Android 4.4之后更换了WebView内核,而且在4.2以下版本有部分漏洞,下面整理了一些常见的问题及解决方法,仅供参考。

1、android.webkit.AccessibilityInjector$TextToSpeechWrapper

java.lang.NullPointerException
    at android.webkit.AccessibilityInjector$TextToSpeechWrapper$1.onInit(AccessibilityInjector.java:753)
    at android.speech.tts.TextToSpeech.dispatchOnInit(TextToSpeech.java:640)
    at android.speech.tts.TextToSpeech.initTts(TextToSpeech.java:619)
    at android.speech.tts.TextToSpeech.<init>(TextToSpeech.java:553)
    at android.webkit.AccessibilityInjector$TextToSpeechWrapper.<init>(AccessibilityInjector.java:676)
    at android.webkit.AccessibilityInjector.addTtsApis(AccessibilityInjector.java:480)
    at android.webkit.AccessibilityInjector.addAccessibilityApisIfNecessary(AccessibilityInjector.java:168)
    at android.webkit.AccessibilityInjector.onPageStarted(AccessibilityInjector.java:340)
    at android.webkit.WebViewClassic.onPageStarted(WebViewClassic.java:4480)
    at android.webkit.CallbackProxy.handleMessage(CallbackProxy.java:366)
    at android.os.Handler.dispatchMessage(Handler.java:107)
    at android.os.Looper.loop(Looper.java:194)
    at android.app.ActivityThread.main(ActivityThread.java:5407)
    at java.lang.reflect.Method.invokeNative(Native Method)
    at java.lang.reflect.Method.invoke(Method.java:525)
    at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:833)
    at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:600)
    at dalvik.system.NativeStart.main(Native Method)

这个问题在Android 4.2.1和4.2.2版本上比较集中,可采用关闭辅助功能来解决。

解决方法:在初始化WebView的时候调用disableAccessibility方法即可。

public static void disableAccessibility(Context context) {
    // Android 4.2 Build.VERSION_CODES.JELLY_BEAN_MR1
    if (Build.VERSION.SDK_INT == 17) {
        if (context != null) {
            try {
                AccessibilityManager am = (AccessibilityManager) context.getSystemService(Context.ACCESSIBILITY_SERVICE);
                if (!am.isEnabled()) {
                    //Not need to disable accessibility
                    return;
                }

                Method setState = am.getClass().getDeclaredMethod("setState", int.class);
                setState.setAccessible(true);
                /**{@link AccessibilityManager#STATE_FLAG_ACCESSIBILITY_ENABLED}*/
                setState.invoke(am, 0);
            } catch (Exception ignored) {
                ignored.printStackTrace();
            }
        }
    }
}

2、android.content.pm.PackageManager$NameNotFoundException

AndroidRuntimeException: android.content.pm.PackageManager$NameNotFoundException: com.google.android.webview
    at android.app.ActivityThread.handleBindApplication(ActivityThread.java:4604)
    at android.app.ActivityThread.access$1500(ActivityThread.java:154)
    at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1389)
    at android.os.Handler.dispatchMessage(Handler.java:102)
    at android.os.Looper.loop(Looper.java:135)
    at android.app.ActivityThread.main(ActivityThread.java:5302)
    at java.lang.reflect.Method.invoke(Native Method)
    at java.lang.reflect.Method.invoke(Method.java:372)
    at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:916)
    at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:711)
Caused by: android.util.AndroidRuntimeException: android.content.pm.PackageManager$NameNotFoundException: com.google.android.webview
    at android.webkit.WebViewFactory.getFactoryClass(WebViewFactory.java:174)
    at android.webkit.WebViewFactory.getProvider(WebViewFactory.java:109)
    at android.webkit.WebView.getFactory(WebView.java:2194)
    at android.webkit.WebView.ensureProviderCreated(WebView.java:2189)
    at android.webkit.WebView.setOverScrollMode(WebView.java:2248)
    at android.view.View.<init>(View.java:3588)
    at android.view.View.<init>(View.java:3682)
    at android.view.ViewGroup.<init>(ViewGroup.java:497)
    at android.widget.AbsoluteLayout.<init>(AbsoluteLayout.java:55)
    at android.webkit.WebView.<init>(WebView.java:544)
    at android.webkit.WebView.<init>(WebView.java:489)
    at android.webkit.WebView.<init>(WebView.java:472)
    at android.webkit.WebView.<init>(WebView.java:459)
    at android.webkit.WebView.<init>(WebView.java:449)

在创建WebView的时候崩溃,跟进栈信息,提示我们需要在setOverScrollMode方法上添加异常保护处理。

解决方法如下:

try {
    super.setOverScrollMode(mode);
} catch (Throwable e) {
    e.printStackTrace();
}

上面方式捕获异常的范围有点广,下面提供一个有针对性的修复方法。

try {
    super.setOverScrollMode(mode);
} catch (Throwable e) {
    String messageCause = e.getCause() == null ? e.toString() : e.getCause().toString();
    String trace = Log.getStackTraceString(e);
    if (trace.contains("android.content.pm.PackageManager$NameNotFoundException")
            || trace.contains("java.lang.RuntimeException: Cannot load WebView")
            || trace.contains("android.webkit.WebViewFactory$MissingWebViewPackageException: Failed to load WebView provider: No WebView installed")) {
        e.printStackTrace();
    } else {
        throw e;
    }
}

3、android.webkit.WebViewClassic.clearView

at android.webkit.BrowserFrame.nativeLoadUrl(Native Method)
System.err:     at android.webkit.BrowserFrame.loadUrl(BrowserFrame.java:279)
System.err:     at android.webkit.WebViewCore.loadUrl(WebViewCore.java:2011)
System.err:     at android.webkit.WebViewCore.access$1900(WebViewCore.java:57)
System.err:     at android.webkit.WebViewCore$EventHub$1.handleMessage(WebViewCore.java:1303)
System.err:     at android.os.Handler.dispatchMessage(Handler.java:99)
System.err:     at android.os.Looper.loop(Looper.java:137)
System.err:     at android.webkit.WebViewCore$WebCoreThread.run(WebViewCore.java:812)
System.err:     at java.lang.Thread.run(Thread.java:856)
webcoreglue: *** Uncaught exception returned from Java call!
System.err: java.lang.NullPointerException
System.err:     at android.webkit.WebViewClassic.clearView(WebViewClassic.java:2868)
System.err:     at android.webkit.WebViewCore.setupViewport(WebViewCore.java:2497)
System.err:     at android.webkit.WebViewCore.updateViewport(WebViewCore.java:2479)
System.err:     at android.webkit.BrowserFrame.nativeLoadUrl(Native Method)
System.err:     at android.webkit.BrowserFrame.loadUrl(BrowserFrame.java:279)
System.err:     at android.webkit.WebViewCore.loadUrl(WebViewCore.java:2011)
System.err:     at android.webkit.WebViewCore.access$1900(WebViewCore.java:57)
System.err:     at android.webkit.WebViewCore$EventHub$1.handleMessage(WebViewCore.java:1303)
System.err:     at android.os.Handler.dispatchMessage(Handler.java:99)
System.err:     at android.os.Looper.loop(Looper.java:137)

这个异常是在某些设备上发生的,在调用webView.destroy()之前调用了loadUrl操作发生的,在清空webView destroy时调用清理方法,内部可能时机有问题会出现,WebViewClassic中mWebViewCore对象为null,其内部为Handler消息机制实现。

解决方法如下:

public void logdUrl(final String url) {
    try {
        super.loadUrl(url);
    } catch (NullPointerException e) {
        e.printStackTrace();
    }
}

项目地址 ☞ 传送门

您可能感兴趣的与本文相关的镜像

Stable-Diffusion-3.5

Stable-Diffusion-3.5

图片生成
Stable-Diffusion

Stable Diffusion 3.5 (SD 3.5) 是由 Stability AI 推出的新一代文本到图像生成模型,相比 3.0 版本,它提升了图像质量、运行速度和硬件效率

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值