问题

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

WebView.setDataDirectorySuffix(suffix);

否则将会报出以下错误:

Using WebView from more than one process at once with the same data directory is not supported. https://crbug.com/558377

1 com.android.webview.chromium.WebViewChromiumAwInit.startChromiumLocked(WebViewChromiumAwInit.java:63)
2 com.android.webview.chromium.WebViewChromiumAwInitForP.startChromiumLocked(WebViewChromiumAwInitForP.java:3)
3 com.android.webview.chromium.WebViewChromiumAwInit$3.run(WebViewChromiumAwInit.java:3)
4 android.os.Handler.handleCallback(Handler.java:873)
5 android.os.Handler.dispatchMessage(Handler.java:99)
6 android.os.Looper.loop(Looper.java:220)
7 android.app.ActivityThread.main(ActivityThread.java:7437)
8 java.lang.reflect.Method.invoke(Native Method)
9 com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:500)
10 com.android.internal.os.ZygoteInit.main(ZygoteInit.java:865)

通过使用官方提供的方法后问题只减少了一部分,从bugly后台依然能收到此问题的大量崩溃信息,以至于都冲上了崩溃问题Top3。

问题分析

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

public class WebViewChromiumAwInit {
 protected void startChromiumLocked() {
   ...
   AwBrowserProcess.start();
   ... 
 }
}
public final class AwBrowserProcess {
 public static void start() {
   ...
   AwDataDirLock.lock(appContext);
}

AwDataDirLock.java

abstract class AwDataDirLock {
 private static final String TAG = "AwDataDirLock";
 private static final String EXCLUSIVE_LOCK_FILE = "webview_data.lock";
 // This results in a maximum wait time of 1.5s
 private static final int LOCK_RETRIES = 16;
 private static final int LOCK_SLEEP_MS = 100;
 private static RandomAccessFile sLockFile;
 private static FileLock sExclusiveFileLock;

 static void lock(final Context appContext) {
  try (ScopedSysTraceEvent e1 = ScopedSysTraceEvent.scoped("AwDataDirLock.lock");
    StrictModeContext ignored = StrictModeContext.allowDiskWrites()) {
   if (sExclusiveFileLock != null) {
    // We have already called lock() and successfully acquired the lock in this process.
    // This shouldn't happen, but is likely to be the result of an app catching an
    // exception thrown during initialization and discarding it, causing us to later
    // attempt to initialize WebView again. There's no real advantage to failing the
    // locking code when this happens; we may as well count this as the lock being
    // acquired and let init continue (though the app may experience other problems
    // later).
    return;
   }
   // If we already called lock() but didn't succeed in getting the lock, it's possible the
   // app caught the exception and tried again later. As above, there's no real advantage
   // to failing here, so only open the lock file if we didn't already open it before.
   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) {
    // Failing to create the lock file is always fatal; even if multiple processes
    // are using the same data directory we should always be able to access the file
    // itself.
     throw new RuntimeException("Failed to create lock file "   lockFile, e);
    }
   }
   // Android versions before 11 have edge cases where a new instance of an app process can
   // be started while an existing one is still in the process of being killed. This can
   // still happen on Android 11  because the platform has a timeout for waiting, but it's
   // much less likely. Retry the lock a few times to give the old process time to fully go
   // away.
   for (int attempts = 1; attempts <= LOCK_RETRIES;   attempts) {
    try {
     sExclusiveFileLock = sLockFile.getChannel().tryLock();
    } catch (IOException e) {
    // Older versions of Android incorrectly throw IOException when the flock()
    // call fails with EAGAIN, instead of returning null. Just ignore it.
    }
    if (sExclusiveFileLock != null) {
     // We got the lock; write out info for debugging.
     writeCurrentProcessInfo(sLockFile);
     return;
    }
    // If we're not out of retries, sleep and try again.
    if (attempts == LOCK_RETRIES) break;
    try {
     Thread.sleep(LOCK_SLEEP_MS);
    } catch (InterruptedException e) {
    }
   }
   // We failed to get the lock even after retrying.
   // Many existing apps rely on this even though it's known to be unsafe.
   // Make it fatal when on P for apps that target P or higher
   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 {
    Log.w(TAG, error);
   }
  }
 }

 private static void writeCurrentProcessInfo(final RandomAccessFile file) {
  try {
   // Truncate the file first to get rid of old data.
   file.setLength(0);
   file.writeInt(Process.myPid());
   file.writeUTF(ContextUtils.getProcessName());
  } catch (IOException e) {
   // Don't crash just because something failed here, as it's only for debugging.
   Log.w(TAG, "Failed to write info to lock file", e);
  }
 }

 private static String getLockFailureReason(final RandomAccessFile file) {
  final StringBuilder error = new StringBuilder("Using WebView from more than one process at "
      "once with the same data directory is not supported. https://crbug.com/558377 "
      ": Current process ");
  error.append(ContextUtils.getProcessName());
  error.append(" (pid ").append(Process.myPid()).append("), lock owner ");
  try {
   int pid = file.readInt();
   String processName = file.readUTF();
   error.append(processName).append(" (pid ").append(pid).append(")");
   // Check the status of the pid holding the lock by sending it a null signal.
   // This doesn't actually send a signal, just runs the kernel access checks.
   try {
    Os.kill(pid, 0);
    // No exception means the process exists and has the same uid as us, so is
    // probably an instance of the same app. Leave the message alone.
   } catch (ErrnoException e) {
    if (e.errno == OsConstants.ESRCH) {
     // pid did not exist - the lock should have been released by the kernel,
     // so this process info is probably wrong.
     error.append(" doesn't exist!");
    } else if (e.errno == OsConstants.EPERM) {
     // pid existed but didn't have the same uid as us.
     // Most likely the pid has just been recycled for a new process
     error.append(" pid has been reused!");
    } else {
     // EINVAL is the only other documented return value for kill(2) and should never
     // happen for signal 0, so just complain generally.
     error.append(" status unknown!");
    }
   }
  } catch (IOException e) {
   // We'll get IOException if we failed to read the pid and process name; e.g. if the
   // lockfile is from an old version of WebView or an IO error occurred somewhere.
   error.append(" unknown");
  }
  return error.toString();
 }
}

lock方法会对webview数据目录中的webview_data.lock文件在for循环中尝试加锁16次,注释中也说明了这么做的原因:可能出现的极端情况是一个旧进程正在被杀死时一个新的进程启动了,看来Google工程师对这个问题也很头痛;如果加锁成功会将该进程id和进程名写入到文件,如果加锁失败则会抛出异常。所以在android9.0以上检测应用是否存在多进程共用WebView数据目录的原理就是进程持有WebView数据目录中的webview_data.lock文件的锁。所以如果子进程也对相同文件尝试加锁则会导致应用崩溃。

解决方案

目前大部分手机会在应用崩溃时自动重启应用,猜测当手机系统运行较慢时这时就会出现注释中提到的当一个旧进程正在被杀死时一个新的进程启动了的情况。既然获取文件锁失败就会发生崩溃,并且该文件只是用于加锁判断是否存在多进程共用WebView数据目录,每次加锁成功都会重新写入对应进程信息,那么我们可以在应用启动时对该文件尝试加锁,如果加锁失败就删除该文件并重新创建,加锁成功就立即释放锁,这样当系统尝试加锁时理论上是可以加锁成功的,也就避免了这个问题的发生。

private static void handleWebviewDir(Context context) {
  if (Build.VERSION.SDK_INT < Build.VERSION_CODES.P) {
   return;
  }
  try {
   String suffix = "";
   String processName = getProcessName(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();
  }
 }

使用此方案应用上线后该问题崩溃次数减少了90%以上。也许Google工程师应该考虑下换一种技术方案检测应用是否存在多进程共用WebView数据目录。

以上就是Android 解决WebView多进程崩溃的方法的详细内容,更多关于Android 解决WebView多进程崩溃的资料请关注Devmax其它相关文章!

Android 解决WebView多进程崩溃的方法的更多相关文章

  1. 详解如何通过H5(浏览器/WebView/其他)唤起本地app

    这篇文章主要介绍了详解如何通过H5(浏览器/WebView/其他)唤起本地app的相关资料,小编觉得挺不错的,现在分享给大家,也给大家做个参考。一起跟随小编过来看看吧

  2. html5 canvas合成海报所遇问题及解决方案总结

    这篇文章主要介绍了html5 canvas合成海报所遇问题及解决方案总结,小编觉得挺不错的,现在分享给大家,也给大家做个参考。一起跟随小编过来看看吧

  3. Html5 video标签视频的最佳实践

    这篇文章主要介绍了Html5 video标签视频的最佳实践,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧

  4. HTML5页面无缝闪开的问题及解决方案

    这篇文章主要介绍了HTML5页面无缝闪开方案,本文通过实例代码给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友可以参考下

  5. HTML5在微信内置浏览器下右上角菜单的调整字体导致页面显示错乱的问题

    HTML5在微信内置浏览器下,在右上角菜单的调整字体导致页面显示错乱的问题,本文给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友参考下吧

  6. ios – 在WKWebView中获取链接URL

    我想在WKWebView中获取tapped链接的url.链接采用自定义格式,可触发应用中的某些操作.例如HTTP://我的网站/帮助#深层链接对讲.我这样使用KVO:这在第一次点击链接时效果很好.但是,如果我连续两次点击相同的链接,它将不报告链接点击.是否有解决方法来解决这个问题,以便我可以检测每个点击并获取链接?任何关于这个的指针都会很棒!解决方法像这样更改addobserver在observeValue函数中,您可以获得两个值

  7. ios – containerURLForSecurityApplicationGroupIdentifier:在iPhone和Watch模拟器上给出不同的结果

    我使用默认的XCode模板创建了一个WatchKit应用程序.我向iOSTarget,WatchkitAppTarget和WatchkitAppExtensionTarget添加了应用程序组权利.(这是应用程序组名称:group.com.lombax.fiveminutes)然后,我尝试使用iOSApp和WatchKitExtension访问共享文件夹URL:延期:iOS应用:但是,测试NSURL

  8. ios – 永远不会调用shouldStartLoadWithRequest

    我已经研究和研究,但仍然不明白为什么从未调用过StartLoadWithRequest.我的页面加载正常,并调用了一些UIWebview委托协议方法.请从以下代码中找到相关的摘要:在我的.m中合成我的webview(在头文件中定义):我成功加载了我的webview:把我的代表设置为自己我的所有协议方法都被称为EXCEPTshouldStartLoadWithRequest提前致谢.解决方法尝试在V

  9. ios – UIWebView不适合设备屏幕

    我有一个网页视图,我想填写iDevice的全屏.我把它放在视图中心设置为中心并与容器边缘齐平.然而,当我加载应用程序时,视图比它运行的模拟iPhone大.我做了一些搜索,一些建议自动布局,这已经应该是视图的中心.我发现的另一件事是通过代码设置大小.我甚至将应用程序从通用应用程序更改为iPhone,对布局没有影响.完整来源:解决方法设置缩放以适合视图边界.试试这个:希望这可以帮助..:)

  10. ios – 如何在Swift中手动为UIWebView设置Cookie

    我需要在swift中为webview设置一个cookie.我找到了一个解决方案,但它是针对objective-c的.如何在Swift中做到这一点?

随机推荐

  1. Flutter 网络请求框架封装详解

    这篇文章主要介绍了Flutter 网络请求框架封装详解,小编觉得挺不错的,现在分享给大家,也给大家做个参考。一起跟随小编过来看看吧

  2. Android单选按钮RadioButton的使用详解

    今天小编就为大家分享一篇关于Android单选按钮RadioButton的使用详解,小编觉得内容挺不错的,现在分享给大家,具有很好的参考价值,需要的朋友一起跟随小编来看看吧

  3. 解决android studio 打包发现generate signed apk 消失不见问题

    这篇文章主要介绍了解决android studio 打包发现generate signed apk 消失不见问题,具有很好的参考价值,希望对大家有所帮助。一起跟随小编过来看看吧

  4. Android 实现自定义圆形listview功能的实例代码

    这篇文章主要介绍了Android 实现自定义圆形listview功能的实例代码,本文通过实例代码给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友可以参考下

  5. 详解Android studio 动态fragment的用法

    这篇文章主要介绍了Android studio 动态fragment的用法,本文给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友可以参考下

  6. Android用RecyclerView实现图标拖拽排序以及增删管理

    这篇文章主要介绍了Android用RecyclerView实现图标拖拽排序以及增删管理的方法,帮助大家更好的理解和学习使用Android,感兴趣的朋友可以了解下

  7. Android notifyDataSetChanged() 动态更新ListView案例详解

    这篇文章主要介绍了Android notifyDataSetChanged() 动态更新ListView案例详解,本篇文章通过简要的案例,讲解了该项技术的了解与使用,以下就是详细内容,需要的朋友可以参考下

  8. Android自定义View实现弹幕效果

    这篇文章主要为大家详细介绍了Android自定义View实现弹幕效果,文中示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下

  9. Android自定义View实现跟随手指移动

    这篇文章主要为大家详细介绍了Android自定义View实现跟随手指移动,文中示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下

  10. Android实现多点触摸操作

    这篇文章主要介绍了Android实现多点触摸操作,实现图片的放大、缩小和旋转等处理,文中示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下

返回
顶部