Using WebView from more than one process

谁践踏了优雅 2024-05-28 10:01 141阅读 0赞

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

目录

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

在这里插入图片描述

一、导读

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

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

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

Using WebView from more than one process

二、概览

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

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

三、问题过程

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

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

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

官方提供方案

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

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

源码追踪

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

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

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

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

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

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

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

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

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

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

四、 推荐阅读

Java 专栏

SQL 专栏

数据结构与算法

Android学习专栏

未经允许不得转载

ddd

发表评论

表情:
评论列表 (有 0 条评论,141人围观)

还没有评论,来说两句吧...

相关阅读