您现在的位置是:首页 > 技术教程 正文

Using WebView from more than one process

admin 阅读: 2024-03-26
后台-插件-广告管理-内容页头部广告(手机)

关于作者:CSDN内容合伙人、技术专家, 从零开始做日活千万级APP。
专注于分享各领域原创系列文章 ,擅长java后端、移动开发、商业变现、人工智能等,希望大家多多支持。
未经允许不得转载

目录

  • 一、导读
  • 二、概览
  • 三、问题过程
    • 源码追踪
  • 四、 推荐阅读

在这里插入图片描述

一、导读

我们继续总结学习遇到的问题,温故知新。

今天遇到一个线上问题,启动就闪退,比较坑,在此做一个记录,防止掉坑。

本文记录一次bug解决的过程,

Using WebView from more than one process

二、概览

今天将 targetSdkVersion 的版升级到了29,出现了一些奇怪的报错,日志如下

Fatal Exception: java.lang.RuntimeException: Using WebView from more than one process at once with the same data directory is not supported. https://crbug.com/558377 : Current process com.xx.xxapp(pid 13862), lock owner com.xx.xx.xxAPP (pid 13559) at org.chromium.android_webview.AwDataDirLock.b(AwDataDirLock.java:27) at as0.i(as0.java:30) at Zr0.run(Zr0.java:2) at android.os.Handler.handleCallback(Handler.java:883) at android.os.Handler.dispatchMessage(Handler.java:100) at android.os.Looper.loop(Looper.java:224) at android.app.ActivityThread.main(ActivityThread.java:7520) at java.lang.reflect.Method.invoke(Method.java) at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:539) at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:950)
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13

三、问题过程

我们查看文档发现, google 文档
在android 9.0系统上如果多个进程使用WebView需要使用官方提供的api在子进程中给webview的数据文件夹设置后缀:

如果不设置,则会报错,不过这个影响范围有限,影响范围: Android 9及以上 且targetSdkVersion >= 28

Starting Android Pie (API 28), Google isn't allowing using a single WebView instance in 2 different processes. WebView.setDataDirectorySuffix(suffix);
  • 1
  • 2
  • 3

官方提供方案

protected void attachBaseContext(Context base) { mApplicationContext = base; webViewSetPath(this); } public void webViewSetPath(Context context) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) { String processName = SpecialUtils.getCurProcessName(context); // 根据进程名称,设置多个目录 if(!CommonConstant.NEW_PACKAGE_NAME.equals(processName)){ WebView.setDataDirectorySuffix(getString(processName,"这里隐藏名字,自己设置个目录")); } } } public String getString(String processName, String defValue) { return TextUtils.isEmpty(processName) ? defValue : processName; }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20

通过使用官方提供的方法后,实际在项目中运用 application中设置多个存储目录,虽然能减少问题发生的次数,但从bugly后台依然能收到此问题的大量崩溃信

源码追踪

那么这个问题发生的原因究竟是什么?一起来分析下抛出这个异常的逻辑吧
https://chromium.googlesource.com/chromium/src/+/refs/heads/main/android_webview/java/src/org/chromium/android_webview/AwDataDirLock.java#126

从源码分析调用链最终调用到了AwDataDirLock类中的lock方法

abstract class AwDataDirLock { static void lock(final Context appContext) { try (ScopedSysTraceEvent e1 = ScopedSysTraceEvent.scoped("AwDataDirLock.lock"); StrictModeContext ignored = StrictModeContext.allowDiskWrites()) { if (sExclusiveFileLock != null) { 我们已经调用了lock(),并在此过程中成功获取了锁 return; } 如果我们已经调用了lock(),但没有成功获得锁,则可能应用程序捕获到异常,进行自动重启。 if (sLockFile == null) { String dataPath = PathUtils.getDataDirectory(); File lockFile = new File(dataPath, EXCLUSIVE_LOCK_FILE); try { // Note that the file is kept open intentionally. sLockFile = new RandomAccessFile(lockFile, "rw"); } catch (IOException e) { throw new RuntimeException("Failed to create lock file " + lockFile, e); } } 对webview数据目录中的webview_data.lock文件在for循环中尝试加锁16for (int attempts = 1; attempts <= LOCK_RETRIES; ++attempts) { try { sExclusiveFileLock = sLockFile.getChannel().tryLock(); } catch (IOException e) { } 如果加锁成功会将该进程id和进程名写入到文件 if (sExclusiveFileLock != null) { writeCurrentProcessInfo(sLockFile); return; } if (attempts == LOCK_RETRIES) break; try { Thread.sleep(LOCK_SLEEP_MS); } catch (InterruptedException e) { } } 如果加锁失败则会抛出异常 // Using WebView from more than one process String error = getLockFailureReason(sLockFile); boolean dieOnFailure = Build.VERSION.SDK_INT >= Build.VERSION_CODES.P && appContext.getApplicationInfo().targetSdkVersion >= Build.VERSION_CODES.P; if (dieOnFailure) { throw new RuntimeException(error); } else { } } } }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53

分析了原因,我们来看看解决思路,我们可以在应用启动时对该文件尝试加锁,如果加锁失败就删除该文件并重新创建,加锁成功就立即释放锁,这样当系统尝试加锁时理论上是可以加锁成功的。
通过检查目标目录的文件锁,如果能够获得到锁,就表明无异常;如果获取不到文件锁,再次重新设置存储目录。

public class WebViewUtil { public static void handleWebViewDir(Context context) { if (Build.VERSION.SDK_INT < Build.VERSION_CODES.P) { return; } try { String suffix = ""; String processName = getCurProcessName(context); if (!TextUtils.equals(context.getPackageName(), processName)) {//判断不等于默认进程名称 suffix = TextUtils.isEmpty(processName) ? context.getPackageName() : processName; WebView.setDataDirectorySuffix(suffix); suffix = "_" + suffix; } tryLockOrRecreateFile(context,suffix); } catch (Exception e) { e.printStackTrace(); } } @TargetApi(Build.VERSION_CODES.P) private static void tryLockOrRecreateFile(Context context, String suffix) { String sb = context.getDataDir().getAbsolutePath() + "/app_webview"+suffix+"/webview_data.lock"; File file = new File(sb); if (file.exists()) { try { FileLock tryLock = new RandomAccessFile(file, "rw").getChannel().tryLock(); if (tryLock != null) { tryLock.close(); } else { createFile(file, file.delete()); } } catch (Exception e) { e.printStackTrace(); boolean deleted = false; if (file.exists()) { deleted = file.delete(); } createFile(file, deleted); } } } private static void createFile(File file, boolean deleted){ try { if (deleted && !file.exists()) { file.createNewFile(); } } catch (Exception e) { e.printStackTrace(); } } public static String getCurProcessName(Context context) { int pid = android.os.Process.myPid(); ActivityManager activityManager = (ActivityManager) context .getSystemService(Context.ACTIVITY_SERVICE); List<ActivityManager.RunningAppProcessInfo> appProcesses = activityManager .getRunningAppProcesses(); if (appProcesses == null) { return null; } for (ActivityManager.RunningAppProcessInfo appProcess : appProcesses) { if (appProcess == null) { continue; } if (appProcess.pid == pid) { return appProcess.processName; } } return null; } }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76

但是这样上线后发现还有问题,原因是不同机型,目录可能不一样,
我们自己使用debug包查看webview数据目录发现系统默认添加了进程名后缀,这是由于用户更新了手机系统导致,
使用华为mate20X测试调用 WebView.selDataDirecloySufx 自定义后缀已不生效,会默认强制指定后缀为进程名,
另外还发现部分华为手机直接将webview目录名app webview改为了app hws webview。

在这里插入图片描述
综上所述,我们需要针对不同手机系统遍历可能的文件路径,最新解决代码如下:

```java public static void handleWebViewDir(Context context) { if (Build.VERSION.SDK_INT < Build.VERSION_CODES.P) { return; } String webViewDir = "/app_webview"; String huaweiWebViewDir = "/app_hws_webview"; String lockFile = "/webview_data.lock"; try { xxx } catch (Exception e) { e.printStackTrace(); } } @TargetApi(Build.VERSION_CODES.P) private static void tryLockOrRecreateFile(String path) { File file = new File(path); if (file.exists()) { try { FileLock tryLock = (new RandomAccessFile(file, "rw")).getChannel().tryLock(); if (tryLock != null) { tryLock.close(); } else { createFile(file, file.delete()); } } catch (Exception e) { boolean deleted = false; if (file.exists()) { deleted = file.delete(); } createFile(file, deleted); } } } private static void createFile(File file, boolean deleted) { try { if (deleted && !file.exists()) { boolean var2 = file.createNewFile(); } } catch (Exception e) { e.printStackTrace(); } }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51

然后在application的oncreate方法中调用 handleWebViewDir();

参考文章:
文章 1
文章 2
文章 3

四、 推荐阅读

Java 专栏

SQL 专栏

数据结构与算法

Android学习专栏

未经允许不得转载

ddd

标签:
声明

1.本站遵循行业规范,任何转载的稿件都会明确标注作者和来源;2.本站的原创文章,请转载时务必注明文章作者和来源,不尊重原创的行为我们将追究责任;3.作者投稿可能会经我们编辑修改或补充。

在线投稿:投稿 站长QQ:1888636

后台-插件-广告管理-内容页尾部广告(手机)
关注我们

扫一扫关注我们,了解最新精彩内容

搜索