多进程webview(Android如何解决WebView多进程崩溃的问题)

发布时间:2025-12-10 23:01:24 浏览次数:1

问题

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

WebView.setDataDirectorySuffix(suffix);

否则将会报出以下错误:

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

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

问题分析

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

publicclassWebViewChromiumAwInit{protectedvoidstartChromiumLocked(){...AwBrowserProcess.start();...}}publicfinalclassAwBrowserProcess{publicstaticvoidstart(){...AwDataDirLock.lock(appContext);}

AwDataDirLock.java

abstractclassAwDataDirLock{privatestaticfinalStringTAG="AwDataDirLock";privatestaticfinalStringEXCLUSIVE_LOCK_FILE="webview_data.lock";//Thisresultsinamaximumwaittimeof1.5sprivatestaticfinalintLOCK_RETRIES=16;privatestaticfinalintLOCK_SLEEP_MS=100;privatestaticRandomAccessFilesLockFile;privatestaticFileLocksExclusiveFileLock;staticvoidlock(finalContextappContext){try(ScopedSysTraceEvente1=ScopedSysTraceEvent.scoped("AwDataDirLock.lock");StrictModeContextignored=StrictModeContext.allowDiskWrites()){if(sExclusiveFileLock!=null){//Wehavealreadycalledlock()andsuccessfullyacquiredthelockinthisprocess.//Thisshouldn'thappen,butislikelytobetheresultofanappcatchingan//exceptionthrownduringinitializationanddiscardingit,causingustolater//attempttoinitializeWebViewagain.There'snorealadvantagetofailingthe//lockingcodewhenthishappens;wemayaswellcountthisasthelockbeing//acquiredandletinitcontinue(thoughtheappmayexperienceotherproblems//later).return;}//Ifwealreadycalledlock()butdidn'tsucceedingettingthelock,it'spossiblethe//appcaughttheexceptionandtriedagainlater.Asabove,there'snorealadvantage//tofailinghere,soonlyopenthelockfileifwedidn'talreadyopenitbefore.if(sLockFile==null){StringdataPath=PathUtils.getDataDirectory();FilelockFile=newFile(dataPath,EXCLUSIVE_LOCK_FILE);try{//Notethatthefileiskeptopenintentionally.sLockFile=newRandomAccessFile(lockFile,"rw");}catch(IOExceptione){//Failingtocreatethelockfileisalwaysfatal;evenifmultipleprocesses//areusingthesamedatadirectoryweshouldalwaysbeabletoaccessthefile//itself.thrownewRuntimeException("Failedtocreatelockfile"+lockFile,e);}}//Androidversionsbefore11haveedgecaseswhereanewinstanceofanappprocesscan//bestartedwhileanexistingoneisstillintheprocessofbeingkilled.Thiscan//stillhappenonAndroid11+becausetheplatformhasatimeoutforwaiting,butit's//muchlesslikely.Retrythelockafewtimestogivetheoldprocesstimetofullygo//away.for(intattempts=1;attempts<=LOCK_RETRIES;++attempts){try{sExclusiveFileLock=sLockFile.getChannel().tryLock();}catch(IOExceptione){//OlderversionsofAndroidincorrectlythrowIOExceptionwhentheflock()//callfailswithEAGAIN,insteadofreturningnull.Justignoreit.}if(sExclusiveFileLock!=null){//Wegotthelock;writeoutinfofordebugging.writeCurrentProcessInfo(sLockFile);return;}//Ifwe'renotoutofretries,sleepandtryagain.if(attempts==LOCK_RETRIES)break;try{Thread.sleep(LOCK_SLEEP_MS);}catch(InterruptedExceptione){}}//Wefailedtogetthelockevenafterretrying.//Manyexistingappsrelyonthiseventhoughit'sknowntobeunsafe.//MakeitfatalwhenonPforappsthattargetPorhigherStringerror=getLockFailureReason(sLockFile);booleandieOnFailure=Build.VERSION.SDK_INT>=Build.VERSION_CODES.P&&appContext.getApplicationInfo().targetSdkVersion>=Build.VERSION_CODES.P;if(dieOnFailure){thrownewRuntimeException(error);}else{Log.w(TAG,error);}}}privatestaticvoidwriteCurrentProcessInfo(finalRandomAccessFilefile){try{//Truncatethefilefirsttogetridofolddata.file.setLength(0);file.writeInt(Process.myPid());file.writeUTF(ContextUtils.getProcessName());}catch(IOExceptione){//Don'tcrashjustbecausesomethingfailedhere,asit'sonlyfordebugging.Log.w(TAG,"Failedtowriteinfotolockfile",e);}}privatestaticStringgetLockFailureReason(finalRandomAccessFilefile){finalStringBuildererror=newStringBuilder("UsingWebViewfrommorethanoneprocessat"+"oncewiththesamedatadirectoryisnotsupported.https://crbug.com/558377"+":Currentprocess");error.append(ContextUtils.getProcessName());error.append("(pid").append(Process.myPid()).append("),lockowner");try{intpid=file.readInt();StringprocessName=file.readUTF();error.append(processName).append("(pid").append(pid).append(")");//Checkthestatusofthepidholdingthelockbysendingitanullsignal.//Thisdoesn'tactuallysendasignal,justrunsthekernelaccesschecks.try{Os.kill(pid,0);//Noexceptionmeanstheprocessexistsandhasthesameuidasus,sois//probablyaninstanceofthesameapp.Leavethemessagealone.}catch(ErrnoExceptione){if(e.errno==OsConstants.ESRCH){//piddidnotexist-thelockshouldhavebeenreleasedbythekernel,//sothisprocessinfoisprobablywrong.error.append("doesn'texist!");}elseif(e.errno==OsConstants.EPERM){//pidexistedbutdidn'thavethesameuidasus.//Mostlikelythepidhasjustbeenrecycledforanewprocesserror.append("pidhasbeenreused!");}else{//EINVAListheonlyotherdocumentedreturnvalueforkill(2)andshouldnever//happenforsignal0,sojustcomplaingenerally.error.append("statusunknown!");}}}catch(IOExceptione){//We'llgetIOExceptionifwefailedtoreadthepidandprocessname;e.g.ifthe//lockfileisfromanoldversionofWebVieworanIOerroroccurredsomewhere.error.append("unknown");}returnerror.toString();}}

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

解决方案

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

privatestaticvoidhandleWebviewDir(Contextcontext){if(Build.VERSION.SDK_INT<Build.VERSION_CODES.P){return;}try{Stringsuffix="";StringprocessName=getProcessName(context);if(!TextUtils.equals(context.getPackageName(),processName)){//判断不等于默认进程名称suffix=TextUtils.isEmpty(processName)?context.getPackageName():processName;WebView.setDataDirectorySuffix(suffix);suffix="_"+suffix;}tryLockOrRecreateFile(context,suffix);}catch(Exceptione){e.printStackTrace();}}@TargetApi(Build.VERSION_CODES.P)privatestaticvoidtryLockOrRecreateFile(Contextcontext,Stringsuffix){Stringsb=context.getDataDir().getAbsolutePath()+"/app_webview"+suffix+"/webview_data.lock";Filefile=newFile(sb);if(file.exists()){try{FileLocktryLock=newRandomAccessFile(file,"rw").getChannel().tryLock();if(tryLock!=null){tryLock.close();}else{createFile(file,file.delete());}}catch(Exceptione){e.printStackTrace();booleandeleted=false;if(file.exists()){deleted=file.delete();}createFile(file,deleted);}}}privatestaticvoidcreateFile(Filefile,booleandeleted){try{if(deleted&&!file.exists()){file.createNewFile();}}catch(Exceptione){e.printStackTrace();}}

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

多进程webview
需要做网站?需要网络推广?欢迎咨询客户经理 13272073477