多进程问题导致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
看堆栈没有任何分析思路 使用分析工具查一波。
- 结合crash行为日志分析出现场景。
有一次线上大量出现该问题,分析行为日志路径大都是出现在BrowserActivity之后 查看load的url也是同一个,大概率是前端代码原因导致,前端排查发现加载了一个特别大的图片,下线图片后crash下降。
- 结合abort message ,backtrace ,java stacktrace, logcat排查问题。
不同行为路径下还少量存在该crash,这时就要去看具体crash是如何触发的了。
- 首先通过上面信息我们可以复制粘贴到浏览器搜索一波。
- 源码跟踪 chromium.googlesource.com/chromium/sr…
看delegate->onRenderProcessGone方法返回的不同值做不同的处理,这个方法最终会调用到
android.webkit.WebViewClient的onRenderProcessGone方法
看注释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的逻辑放到子线程,我们也可以对这个方案进行尝试。
把上面的代码放到子线程 ,当然有个条件如下图,子线程加载要和主线程任务A同时,因为主线程webview任务肯定是要加载webview apk后执行的。 线上测试下来能平均优化100ms,理想情况有200多ms, 当然业务如果允许在使用时再创建webview 更能提高启动速度,不过第一次打开h5页面体验就会受到影响。
处理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;
比如这个版本肯定是有问题的。 这部分可以去查看厂商,系统版本,行为日志尝试复现,进行问题反馈。