關(guān)于作者:CSDN內(nèi)容合伙人、技術(shù)專家, 從零開始做日活千萬級APP。
專注于分享各領(lǐng)域原創(chuàng)系列文章 ,擅長java后端、移動開發(fā)、商業(yè)變現(xiàn)、人工智能等,希望大家多多支持。
未經(jīng)允許不得轉(zhuǎn)載
一、導(dǎo)讀
我們繼續(xù)總結(jié)學(xué)習(xí)遇到的問題,溫故知新。
今天遇到一個線上問題,啟動就閃退,比較坑,在此做一個記錄,防止掉坑。
本文記錄一次bug解決的過程,
Using WebView from more than one process
二、概覽
今天將 targetSdkVersion 的版升級到了29,出現(xiàn)了一些奇怪的報(bào)錯,日志如下
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)
三、問題過程
我們查看文檔發(fā)現(xiàn), google 文檔
在android 9.0系統(tǒng)上如果多個進(jìn)程使用WebView需要使用官方提供的api在子進(jìn)程中給webview的數(shù)據(jù)文件夾設(shè)置后綴:
如果不設(shè)置,則會報(bào)錯,不過這個影響范圍有限,影響范圍: 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);
官方提供方案
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);
// 根據(jù)進(jìn)程名稱,設(shè)置多個目錄
if(!CommonConstant.NEW_PACKAGE_NAME.equals(processName)){
WebView.setDataDirectorySuffix(getString(processName,"這里隱藏名字,自己設(shè)置個目錄"));
}
}
}
public String getString(String processName, String defValue) {
return TextUtils.isEmpty(processName) ? defValue : processName;
}
通過使用官方提供的方法后,實(shí)際在項(xiàng)目中運(yùn)用 application中設(shè)置多個存儲目錄,雖然能減少問題發(fā)生的次數(shù),但從bugly后臺依然能收到此問題的大量崩潰信
源碼追蹤
那么這個問題發(fā)生的原因究竟是什么?一起來分析下拋出這個異常的邏輯吧
https://chromium.googlesource.com/chromium/src/+/refs/heads/main/android_webview/java/src/org/chromium/android_webview/AwDataDirLock.java#126
從源碼分析調(diào)用鏈最終調(diào)用到了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) {
我們已經(jīng)調(diào)用了lock(),并在此過程中成功獲取了鎖
return;
}
如果我們已經(jīng)調(diào)用了lock(),但沒有成功獲得鎖,則可能應(yīng)用程序捕獲到異常,進(jìn)行自動重啟。
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數(shù)據(jù)目錄中的webview_data.lock文件在for循環(huán)中嘗試加鎖16次
for (int attempts = 1; attempts <= LOCK_RETRIES; ++attempts) {
try {
sExclusiveFileLock = sLockFile.getChannel().tryLock();
} catch (IOException e) {
}
如果加鎖成功會將該進(jìn)程id和進(jìn)程名寫入到文件
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 {
}
}
}
}
分析了原因,我們來看看解決思路,我們可以在應(yīng)用啟動時(shí)對該文件嘗試加鎖,如果加鎖失敗就刪除該文件并重新創(chuàng)建,加鎖成功就立即釋放鎖,這樣當(dāng)系統(tǒng)嘗試加鎖時(shí)理論上是可以加鎖成功的。
通過檢查目標(biāo)目錄的文件鎖,如果能夠獲得到鎖,就表明無異常;如果獲取不到文件鎖,再次重新設(shè)置存儲目錄。
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)) {//判斷不等于默認(rèn)進(jìn)程名稱
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;
}
}
但是這樣上線后發(fā)現(xiàn)還有問題,原因是不同機(jī)型,目錄可能不一樣,
我們自己使用debug包查看webview數(shù)據(jù)目錄發(fā)現(xiàn)系統(tǒng)默認(rèn)添加了進(jìn)程名后綴,這是由于用戶更新了手機(jī)系統(tǒng)導(dǎo)致,
使用華為mate20X測試調(diào)用 WebView.selDataDirecloySufx 自定義后綴已不生效,會默認(rèn)強(qiáng)制指定后綴為進(jìn)程名,
另外還發(fā)現(xiàn)部分華為手機(jī)直接將webview目錄名app webview改為了app hws webview。
綜上所述,我們需要針對不同手機(jī)系統(tǒng)遍歷可能的文件路徑,最新解決代碼如下:
```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();
}
}
然后在application的oncreate方法中調(diào)用 handleWebViewDir();
參考文章:
文章 1
文章 2
文章 3
四、 推薦閱讀
Java 專欄
SQL 專欄
數(shù)據(jù)結(jié)構(gòu)與算法
Android學(xué)習(xí)專欄
未經(jīng)允許不得轉(zhuǎn)載文章來源:http://www.zghlxwxcb.cn/news/detail-838401.html
文章來源地址http://www.zghlxwxcb.cn/news/detail-838401.html
到了這里,關(guān)于Using WebView from more than one process的文章就介紹完了。如果您還想了解更多內(nèi)容,請?jiān)谟疑辖撬阉鱐OY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!