Crash排查系列第八篇|记录webview相关问题处理

1,803 阅读4分钟

多进程问题导致crash

ava.lang.RuntimeException: Using WebView from more than one process at once with the same data directory is not supported. https://crbug.com/558377
        at com.android.webview.chromium.WebViewChromiumAwInit.startChromiumLocked(WebViewChromiumAwInit.java:100)
        at com.android.webview.chromium.WebViewChromiumAwInitForP.startChromiumLocked(WebViewChromiumAwInitForP.java:3)
        at com.android.webview.chromium.WebViewChromiumAwInit.ensureChromiumStartedLocked(WebViewChromiumAwInit.java:180)
        at com.android.webview.chromium.WebViewChromiumAwInit.startYourEngines(WebViewChromiumAwInit.java:161)
        at com.android.webview.chromium.WebViewChromiumFactoryProvider.startYourEngines(WebViewChromiumFactoryProvider.java:217)
        at com.android.webview.chromium.WebViewChromium.init(WebViewChromium.java:44)
        at android.webkit.WebView.<init>(WebView.java:432)
        at android.webkit.WebView.<init>(WebView.java:358)
        at android.webkit.WebView.<init>(WebView.java:341)

三年前 Android 9 Android10出现的问题 通过setDataDirectorySuffix设置后 还是top1 的crash 。 多进程如果用到同一个webview目录就会导致crash, 看日志现象是不同的pid相同的进程名,猜测有些情况下存在两个相同包名的进程。解决方式也比较简单, 启动时发现目录已经被其他进程占用了 ,那就再加上一个pid后缀。

解决:

if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
    try {
        if (application.getPackageName().equals(currentProcessName)) {
            File lockFile = new File(application.getDir("webview", Context.MODE_PRIVATE).getPath(), "webview_data.lock");
            if (lockFile.exists()) {
                RandomAccessFile lock = new RandomAccessFile(lockFile, "rw");
                //没有被其他进程锁住
                if (lock.getChannel().tryLock() != null) {
                    lockFile.delete()
                } else {
                    //被其他进程锁了
                    HAS_WEB_LOCK = true;
                    WEBVIEW_PATH = "webview";
                    WebView.setDataDirectorySuffix(application.getPackageName() + "_" + Process.myPid());
                }
            }
            //华为部分android 10 手机webview缓存目录为hws_webview
            if (Build.VERSION.SDK_INT >= 29 && ("HuaWei".equalsIgnoreCase(Build.BRAND) || "HONOR".equalsIgnoreCase(Build.BRAND))) {
                File hwLockFile = new File(application.getDir("hws_webview", Context.MODE_PRIVATE).getPath(), "webview_data.lock");
                if (hwLockFile.exists()) {
                    RandomAccessFile hwLock = new RandomAccessFile(hwLockFile, "rw");
                    //没有被其他进程锁住
                    if (hwLock.getChannel().tryLock() != null) {
                        lockFile.delete()
                    } else {
                        //被其他进程锁了
                        HAS_WEB_LOCK = true;
                        WEBVIEW_PATH = "hws_webview";
                        WebView.setDataDirectorySuffix(application.getPackageName() + "_" + Process.myPid());
                    }
                }
            }
        } else {
            WebView.setDataDirectorySuffix(currentProcessName);
        }
    } catch (Exception e) {
        e.printStackTrace();
    }
}

渲染问题导致crash

image.png 看堆栈没有任何分析思路 使用分析工具查一波。

  1. 结合crash行为日志分析出现场景。

有一次线上大量出现该问题,分析行为日志路径大都是出现在BrowserActivity之后 查看load的url也是同一个,大概率是前端代码原因导致,前端排查发现加载了一个特别大的图片,下线图片后crash下降。

  1. 结合abort message ,backtrace ,java stacktrace, logcat排查问题。

不同行为路径下还少量存在该crash,这时就要去看具体crash是如何触发的了。

image.png

image.png

  1. 首先通过上面信息我们可以复制粘贴到浏览器搜索一波。
  2. 源码跟踪 chromium.googlesource.com/chromium/sr…

image.png

看delegate->onRenderProcessGone方法返回的不同值做不同的处理,这个方法最终会调用到

android.webkit.WebViewClient的onRenderProcessGone方法

image.png

看注释webview 渲染进程退出会通知到该方法。可以用loadUrl("chrome://crash")测试这个case .多个webview共享render process 都需要处理不仅仅是loadUrl("chrome://crash")的这个webview。

这边如果不处理默认返回false render process挂了会导致应用进程也挂了。

解决:

对项目中的Webview client复现该方法 调用后上报异常埋点及当前加载url 处理webview 返回true。

但是用loadUrl("chrome://crash")做测试时 并没有回调该方法而直接crash。 后续对framwork层android.webkit.WebViewClient进行调试 发现这边确实断点到了 没有走子类的方法。发现是两个webview不一样。原来是我们项目做了webview缓存池的一个功能。会提前构建下一个webview 但是这个webview的WebViewClient用是默认的WebViewClient所以返回的是false。 回到上面触发crash代码片断 确实是一个循环去触发AwContents的onRenderProcessGone 发现有没处理的就crash了。最后针对提前构建的webview也去设置一个自己的WebViewClient 。建立异常url报表

apk&so 异常不兼容导致crash

89.0.4389.90版本导致 crash

Android系统的WebView错误更新,致使大量应用崩溃 zhuanlan.zhihu.com/p/359482553

so 架构不匹配导致 crash

Caused by: java.lang.RuntimeException: Cannot load WebView
at org.chromium.android_webview.AwBrowserProcess.a(PG:20)
at com.android.webview.chromium.WebViewChromiumFactoryProvider.a(PG:81)
at com.android.webview.chromium.WebViewChromiumFactoryProvider.<init>(PG:12)
at com.android.webview.chromium.WebViewChromiumFactoryProviderForOMR1.<init>(PG:1)
at com.android.webview.chromium.WebViewChromiumFactoryProviderForOMR1.create(PG:1)
... 39 more
Caused by: Qy0
at org.chromium.base.library_loader.LibraryLoader.a(PG:40)
at org.chromium.base.library_loader.LibraryLoader.a(PG:16)
at org.chromium.android_webview.AwBrowserProcess.a(PG:16)
... 43 more
Caused by: java.lang.UnsatisfiedLinkError: dlopen failed: "/data/app/com.android.chrome-b7SMbfSB6LfJn2_2Ai7yHA==/base.apk!/lib/armeabi-v7a/libmonochrome.so" is 32-bit instead of 64-bit
at java.lang.Runtime.loadLibrary0(Runtime.java:1016)
at java.lang.System.loadLibrary(System.java:1657)
at org.chromium.base.library_loader.LibraryLoader.a(PG:31)
... 45 more

解决: 创建webview的时候做try catch 根据堆栈进行判断 降级提示用户webview版本不兼容,无法使用,指导和建议进行升级

webview 预创建提速

启动优化发现webview 预创建耗时特别明显,已经到了耗时任务top1

trace结合xposed我们很容易做到竞品webview 启动分析,对比效果,比如一些app可以把解析webview apk的逻辑放到子线程,我们也可以对这个方案进行尝试。

image.png 把上面的代码放到子线程 ,当然有个条件如下图,子线程加载要和主线程任务A同时,因为主线程webview任务肯定是要加载webview apk后执行的。 线上测试下来能平均优化100ms,理想情况有200多ms, 当然业务如果允许在使用时再创建webview 更能提高启动速度,不过第一次打开h5页面体验就会受到影响。

未命名文件 (2).jpg

处理Webview crash 相关的报表

除了通常处理crash的一些报表 比如设备维度,系统版本维度等报表,webview crash有额外几个维度的报表能帮助我们快速定位问题。

  • crash时load url的报表

    •   还有一些crash时是和加载的url有关的,这部分客户端能做的是做一个url crash的趋势报表 发现异常及时告警推进h5同学去解决。
  • webview.apk version维度报表

比如之前89.0.4389.90的webview.apk版本的问题 就很容易通过这个报表发现。

public static List<String> getWebviewApkVersions(Application application) {
    ArrayList<String> versions = new ArrayList<>();
    if (application == null) {
        return versions;
    }
    PackageManager packageManager = application.getPackageManager();
    ArrayList<String> webviewPackages = new ArrayList<>();
    webviewPackages.add("com.google.android.webview");
    webviewPackages.add("com.android.webview");
    if ("xiaomi".equalsIgnoreCase(Build.BRAND) || "redmi".equalsIgnoreCase(Build.BRAND)) {
        webviewPackages.add("com.mi.webkit.core");
    }
    if ("huawei".equalsIgnoreCase(Build.BRAND) || "HONOR".equalsIgnoreCase(Build.BRAND)) {
        webviewPackages.add("com.huawei.webview");
    }

    if (packageManager != null) {
        for (String packageName : webviewPackages) {
            PackageInfo packageInfo;
            try {
                packageInfo = packageManager.getPackageInfo(packageName, 0);
                if (packageInfo != null) {
                    versions.add(packageName + "-" + packageInfo.versionName);
                }
            } catch (PackageManager.NameNotFoundException e) {
                e.printStackTrace();
            }
        }
    }
    return versions;

比如这个版本肯定是有问题的。 这部分可以去查看厂商,系统版本,行为日志尝试复现,进行问题反馈。