0


Using WebView from more than one process

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

目录

在这里插入图片描述

一、导读

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

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

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

Using WebView from more than one process

二、概览

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

FatalException:java.lang.RuntimeException:UsingWebView from more than one process at once withthe 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)

三、问题过程

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

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

StartingAndroidPie(API28),Google isn't allowing using a single WebView instance in 2 different processes.
    
    WebView.setDataDirectorySuffix(suffix);

官方提供方案

protectedvoidattachBaseContext(Context base){
    mApplicationContext = base;webViewSetPath(this);}publicvoidwebViewSetPath(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,"这里隐藏名字,自己设置个目录"));}}}publicStringgetString(String processName,String defValue){returnTextUtils.isEmpty(processName)? defValue : processName;}

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

源码追踪

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

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

abstractclassAwDataDirLock{staticvoidlock(finalContext 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 =newFile(dataPath,EXCLUSIVE_LOCK_FILE);try{// Note that the file is kept open intentionally.
                    sLockFile =newRandomAccessFile(lockFile,"rw");}catch(IOException e){thrownewRuntimeException("Failed to create lock file "+ lockFile, e);}}
            
            对webview数据目录中的webview_data.lock文件在for循环中尝试加锁16次
            for(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){thrownewRuntimeException(error);}else{}}}}

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

publicclassWebViewUtil{publicstaticvoidhandleWebViewDir(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)privatestaticvoidtryLockOrRecreateFile(Context context,String suffix){String sb = context.getDataDir().getAbsolutePath()+"/app_webview"+suffix+"/webview_data.lock";File file =newFile(sb);if(file.exists()){try{FileLock tryLock =newRandomAccessFile(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);}}}privatestaticvoidcreateFile(File file,boolean deleted){try{if(deleted &&!file.exists()){
                file.createNewFile();}}catch(Exception e){
            e.printStackTrace();}}publicstaticStringgetCurProcessName(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){returnnull;}for(ActivityManager.RunningAppProcessInfo appProcess : appProcesses){if(appProcess ==null){continue;}if(appProcess.pid == pid){return appProcess.processName;}}returnnull;}}

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

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

```java

    publicstaticvoidhandleWebViewDir(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)privatestaticvoidtryLockOrRecreateFile(String path){File file =newFile(path);if(file.exists()){try{FileLock tryLock =(newRandomAccessFile(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);}}}privatestaticvoidcreateFile(File file,boolean deleted){try{if(deleted &&!file.exists()){boolean var2 = file.createNewFile();}}catch(Exception e){
            e.printStackTrace();}}

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

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

四、 推荐阅读

Java 专栏

SQL 专栏

数据结构与算法

Android学习专栏

未经允许不得转载

ddd

标签: webview

本文转载自: https://blog.csdn.net/fumeidonga/article/details/136580630
版权归原作者 Android西红柿 所有, 如有侵权,请联系我们删除。

“Using WebView from more than one process”的评论:

还没有评论