WebView common Crash analysis and Solutions

1 Preface

Unexpected flash back of App is called crash. Crash rate is an important indicator to measure the stability of App. The stability of App will not only affect the use experience, but even cause the loss of users. Because there are many reasons and types that lead to App Crash, an article cannot be explained clearly, nor is it the focus of this article. We won't go into too much detail.

As we all know, WebView has the advantage of cross end operation. In most scenarios, there is no need to follow the App release, and the penetration rate of the latest content is significantly higher than that of Native, which makes more and more application scenarios of WebView. The Crash caused by WebView also accounts for a large proportion, and it is urgent to effectively control the Crash caused by WebVi ew.

This article mainly describes the common Crash and solutions of Android WebView.

2 01. WebView crashes caused by opening multiple processes

On Android 9.0 System, if multiple processes are introduced to use WebView, you need to use the officially provided api to set the suffix to the data folder of WebView in the sub process.

WebView.setDataDirectorySuffix(suffix);

Otherwise, the following exceptions will occur:

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)

After using the official API, the problems have been reduced, but there are still a large number of crashes reported online.

problem analysis

By reading the source code, it is found that the lock method in AwDataDirLock is finally called.

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

The core code that throws the above exception in AwDataDirLock:

// 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);
}

The lock method caches the WebView in the WebView directory_ The data.lock file attempts to lock 16 times in the for loop, as explained in the note: the extreme case may be that a new process is started when an old process is being killed. If the loading is successful, the process id and process name will be written to the file. If the locking fails, an exception will be thrown, In Android P and later versions, the principle of detecting whether there is a multi process public WebView data directory in the application is that the process holds the WebView in the WebView data directory_ Lock of data.lock file. So if the child process also tries to_ Locking the data.loc file will cause the application to crash.

// 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);
    }
}

Solution

Since the file will crash if it fails to obtain the file lock, and the file is only used to determine whether there is a shared WebView data directory for multiple processes, and the corresponding process information will be rewritten every time the lock is successful, we can try to lock the file when the application starts. If the lock fails, delete the file and re create it, and release the lock immediately after the lock is successful, In this way, when the system tries to lock, it can lock successfully in theory, and this problem can be avoided.

private static void fixWebviewDataDirLock(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;
            }
            ensureSuffixLock(context,suffix);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    @TargetApi(Build.VERSION_CODES.P)
    private static void ensureSuffixLock(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();
        }
    }

3 02. Support Crash caused by 64 bit CPU architecture upgrade

After the App is upgraded to support 64 bit system, after some domestic mobile phones are upgraded to cover the installation, a Crash occurs on the page of WebView, and the log is as follows:

pid: 1947, tid: 2230, name: Chrome_InProcGp >>> com.####### <<<
signal 11 (SIGSEGV), code 1 (SEGV_MAPERR), fault addr 0x958caf8000
x0 000000718baf4000 x1 0000000000000000 x2 000000718baf4000 x3 000000718baf4250
x4 0000000000000000 x5 000000718baf4b44 x6 00000000000029a4 x7 000000718baf69a4
x8 0000000000000000 x9 0000007188e2a748 x10 000000718baf4afc x11 0000000000000b44
x12 0000000000000001 x13 000000718baf4b44 x14 0000000000000000 x15 0000002401004000
x16 0000007188e2a748 x17 0000000000000000 x18 000000958caf8000 x19 0000007187361200
x20 00000071a54cb080 x21 000000718baf1000 x22 000000718baf4000 x23 000000718baf4070
x24 00000071a147b07c x25 0000007188e2a748 x26 0000000000000000 x27 0000000000000000
x28 0000000000000130 x29 0000000000000000 x30 00000071a118a12c
sp 0000007188e2a4d0 pc 00000071a118a384 pstate 0000000060000000
08-20 16:38:46.697 2304-2304/? A/DEBUG: backtrace:
#00 pc 0000000000a7b384 /vendor/lib64/libllvm-glnext.so
(ShaderObjects::loadProgramBinary(CompilerContext, void, unsigned long, QGLC_LINKPROGRAM_RESULT)+1400)
#01 pc 00000000009b0e38 /vendor/lib64/libllvm-glnext.so 
(CompilerContext::loadProgramBinary(void, unsigned long, QGLC_LINKPROGRAM_RESULT)+156)
#02 pc 0000000000a92550 /vendor/lib64/libllvm-glnext.so 
(QGLCLoadProgramBinary(void, void, unsigned long, QGLC_LINKPROGRAM_RESULT)+88)
#03 pc 00000000001b9694 /vendor/lib64/egl/libGLESv2_adreno.so (EsxShaderCompiler::LoadProgramBinaryBlob
(EsxContext, EsxProgram, void const, unsigned long, EsxInfoLog)+256)
08-20 16:38:48.125 1395-1743/? E/TaskPersister: File error accessing recents directory (directory doesn't exist?).

problem analysis

WebView in Android version 8.0 has a memory overflow when reading the WebView cache.

Solution

Delete / data/data / package name / App after App upgrade_ WebView / gpucache directory, because mobile phone manufacturers_ The WebView / gpucache directory has been modified too much. The deletion details are different for different models. For example, the directory of Huawei mobile phones is app_hws_webview/Default/GPUCacheï¼› The directory of OPPO and Xiaomi mobile phones is app_webview/Default/GPUCache. In order to be completely compatible with the adaptation problems of different models, we use the method of violent deletion. After the App version is upgraded, delete / data / package name / all cache directories containing WebView at the first startup.

SharedPreferences prefs =
    application.getSharedPreferences("WebViewChromiumPrefs", Context.MODE_PRIVATE);
prefs.edit().clear().apply();
final File dir = context.getFilesDir();
if (dir != null && dir.getParent() != null) {
  File file = new File(dir.getParent());
  if (file.exists() && file.isDirectory()) {
    final File[] files = file.listFiles();
    if (files != null) {
      for (File child : files) {
        final String path = child.getAbsolutePath();
        if (!TextUtils.isEmpty(path) && path.toLowerCase().contains("webview")) {
          deleteFile(child);
        }
      }
    }
  }
}

private static void deleteFile(File file) {
  if (file == null || !file.exists()) {
    return;
  }
  if (file.isDirectory()) {
    File[] files = file.listFiles();
    if (files != null) {
      for (File child : files) {
        deleteFile(child);
      }
    }
  } else {
    file.delete();
  }
}

4 03. Crash caused by WebView local cache data

After the App overlay upgrade installation, the WebView page directly crashes on some mobile phones, which is inevitable. This problem will not occur unless it is installed for the first time. For the mobile phone with problems, you can only enter settings - Application Management - Application to clear the data, and the user experience is very poor.

signal 6 (SIGABRT), code -6 (SI_TKILL), fault addr --------
Abort message: 'java_vm_ext.cc:534] JNI DETECTED ERROR IN APPLICATION: 
JNI GetMethodID called with pending exception java.lang.NoSuchMethodError: no non-static method
"Lorg/android/spdy/SpdyAgent;.spdySessionConnectCB(Lorg/android/spdy/SpdySession;
Lorg/android/spdy/SuperviseConnectInfo;)V"'
    r0 00000000  r1 000001fe  r2 00000006  r3 00000008
    r4 0000018c  r5 000001fe  r6 89219074  r7 0000010c
    r8 00000000  r9 a4319368  sl 0000000a  fp 892190c0
    ip 972e59e0  sp 89219060  lr a4ec18bd  pc a4ebb40e  cpsr 200b0030

backtrace:
    #00 pc 0001a40e  /system/lib/libc.so (abort+63)
    #01 pc 0035ca45  /system/lib/libart.so (art::Runtime::Abort(char const*)+392)
    #02 pc 0041fe2d  /system/lib/libart.so (android::base::LogMessage::~LogMessage()+452)
    #03 pc 0024e545  /system/lib/libart.so (art::JavaVMExt::JniAbort(char const*, char const*)+1212)
    #04 pc 0024e6c7  /system/lib/libart.so (art::JavaVMExt::JniAbortV(char const*, char const*, std::__va_list)+58)
    #05 pc 000d6403  /system/lib/libart.so (art::ScopedCheck::AbortF(char const*, ...)+42)
    #06 pc 000d5f83  /system/lib/libart.so (art::ScopedCheck::CheckThread(_JNIEnv*)+274)
    #07 pc 000d492d  /system/lib/libart.so (art::ScopedCheck::
    Check(art::ScopedObjectAccess&, bool, char const*, art::JniValueType*)+596)
    #08 pc 000d79e9  /system/lib/libart.so (art::CheckJNI::
    GetMethodIDInternal(char const*, _JNIEnv*, _jclass*, char const*, char const*, bool)+464)
    #09 pc 000c9365  /system/lib/libart.so (art::CheckJNI::
    GetMethodID(_JNIEnv*, _jclass*, char const*, char const*)+20)
    #10 PC 00003657 / data / APP / here is the application package name - K2pKM3UWvCbPitz1xjJr7A==/lib/arm/libtnet-3.1.11.so
    #11 PC 00004a0d / data / APP / here is the application package name - K2pKM3UWvCbPitz1xjJr7A==/lib/arm/libtnet-3.1.11.so
    #12 PC 0003d0cd / data / APP / here is the application package name - K2pKM3UWvCbPitz1xjJr7A==/oat/arm/base.odex 
    (offset 0x3b000)

problem analysis

After clearing the application data, it will not crash and can be used normally. Combined with the data/data / application package name / lib/***.so in the above log, it can be inferred that when the system overwrites the installation or upgrades the new version, if the old version and the new version have the same library file, it will not be reloaded into the system, resulting in that the library file loaded by the old version is still used after the new version is installed, However, there is no necessary association between the cache files of the new version and the old version, resulting in an error when the method name cannot be found.

Solution

According to the above analysis, the application cache is cleaned up when the new version enters the application initialization. After the system detects that there is no cache, the new library file will be reloaded. The main clear cache file path is: getFileDir().getParent(). There are some folders under this path, such as: / lib, / database, / shared_ prefs..... /shared_ The SharedPreference data is saved under prefs and the db database file is saved under / database. The files under these two files save user data without deletion or selective deletion. The deletion code is as follows:

public static void deleteFolder(File file) {
   if (!file.exists())
      return;

   if (file.isDirectory()) {
      File files[] = file.listFiles();
      for (int i = 0; i < files.length; i++) {
         if(files[i].getAbsolutePath().endsWith("/databases")){ 
            continue;
         }
         deleteFolder(files[i]);
      }
   }
   if(!file.getAbsolutePath().endsWith("/shared_prefs/user_info.xml") ){
      file.delete();
   }
   SharePreferenceUtils.saveOrUpdateAttribute(context,MyConstants.USER_INFO, "dataCleared", true);
}

In the onCreate method of Application, we call:

@Override
public void onCreate() {
  super.onCreate();
  if(!SharePreferenceUtils.getAttributeByKey(this,MyConstants.USER_INFO, "dataCleared",SharePreferenceUtil.VALUE_IS_BOOLEAN)){
    deleteFolder(new File(getFilesDir().getParent()));
  }
}

When deleting the SharePreference data, only the files created by yourself are retained, and the rest are deleted, because it is found that if the whole / shared is retained during the test_ There will still be errors in the prefs folder. There are some data written in other third-party library files. If it is not clear, an error will be reported.

5 04. WebView page OOM

2973  8387 W Adreno-GSL: <sharedmem_gpuobj_alloc:2736>: sharedmem_gpumem_alloc: mmap failed errno 12 Out of memory
2973  8387 E Adreno-GSL: <gsl_memory_alloc_pure:2604>: GSL MEM ERROR: kgsl_sharedmem_alloc ioctl failed.
2973  8387 W Adreno-GSL: <sharedmem_gpuobj_alloc:2736>: sharedmem_gpumem_alloc: mmap failed errno 12 Out of memory
2973  8387 E Adreno-GSL: <gsl_memory_alloc_pure:2604>: GSL MEM ERROR: kgsl_sharedmem_alloc ioctl failed.
2973  8387 W Adreno-GSL: <sharedmem_gpuobj_alloc:2736>: sharedmem_gpumem_alloc: mmap failed errno 12 Out of memory
2973  8387 E Adreno-GSL: <gsl_memory_alloc_pure:2604>: GSL MEM ERROR: kgsl_sharedmem_alloc ioctl failed.
2973  8387 W Adreno-GSL: <sharedmem_gpuobj_alloc:2736>: sharedmem_gpumem_alloc: mmap failed errno 12 Out of memory
2973  8387 E Adreno-GSL: <gsl_memory_alloc_pure:2604>: GSL MEM ERROR: kgsl_sharedmem_alloc ioctl failed.
2973  8387 W Adreno-GSL: <sharedmem_gpuobj_alloc:2736>: sharedmem_gpumem_alloc: mmap failed errno 12 Out of memory
2973  8387 E Adreno-GSL: <gsl_memory_alloc_pure:2604>: GSL MEM ERROR: kgsl_sharedmem_alloc ioctl failed.
973  8387 W Adreno-GSL: <sharedmem_gpuobj_alloc:2736>: sharedmem_gpumem_alloc: mmap failed errno 12 Out of memory
2973  8387 E Adreno-GSL: <gsl_memory_alloc_pure:2604>: GSL MEM ERROR: kgsl_sharedmem_alloc ioctl failed.
2973  8387 W Adreno-GSL: <sharedmem_gpuobj_alloc:2736>: sharedmem_gpumem_alloc: mmap failed errno 12 Out of memory
2973  8387 E Adreno-GSL: <gsl_memory_alloc_pure:2604>: GSL MEM ERROR: kgsl_sharedmem_alloc ioctl failed.
2973  8387 W Adreno-GSL: <sharedmem_gpuobj_alloc:2736>: sharedmem_gpumem_alloc: mmap failed errno 12 Out of memory
2973  8387 E Adreno-GSL: <gsl_memory_alloc_pure:2604>: GSL MEM ERROR: kgsl_sharedmem_alloc ioctl failed.
2973  8387 W Adreno-GSL: <sharedmem_gpuobj_alloc:2736>: sharedmem_gpumem_alloc: mmap failed errno 12 Out of memory
2973  8387 E Adreno-GSL: <gsl_memory_alloc_pure:2604>: GSL MEM ERROR: kgsl_sharedmem_alloc ioctl failed.
2973  8387 W Adreno-GSL: <sharedmem_gpuobj_alloc:2736>: sharedmem_gpumem_alloc: mmap failed errno 12 Out of memory
2973  8387 E Adreno-GSL: <gsl_memory_alloc_pure:2604>: GSL MEM ERROR: kgsl_sharedmem_alloc ioctl failed.
2973  8387 W Adreno-GSL: <sharedmem_gpuobj_alloc:2736>: sharedmem_gpumem_alloc: mmap failed errno 12 Out of memory
2973  8387 E Adreno-GSL: <gsl_memory_alloc_pure:2604>: GSL MEM ERROR: kgsl_sharedmem_alloc ioctl failed.
2973  8387 E chromium: [ERROR:buffer_manager.cc(488)] [.RendererMainThread-0xe90cdf20]GL ERROR
:GL_OUT_OF_MEMORY : glBufferData: <- error from previous GL command 
2973  8387 E chromium: [ERROR:gles2_cmd_decoder.cc(5995)] Error: 5 for Command kBufferData
MemFree:     1211920 kB

VmSize:  3912496 kB

problem analysis

Through log sharedmem_gpumem_alloc: mmap failed errno 12 Out of memory. It is not difficult to find that the reason for the Crash is that there is a problem in memory allocation, which leads to the exit of rendermain thread. Rendermain thread 8387 is responsible for applying for the memory required for drawing from GPU. GPU and RAM are essentially a piece of physical memory, sharedme m_gpumem_alloc needs to allocate memory VmSize of nearly 4G. At this time, the available effective memory is 1.16G. The application for allocation far exceeds the free memory, causing memory OOM and Crash.

Solution

For this Crash, contact the relevant operators urgently to stop the launch of the activity, and replace the page with text + small picture and launch it again, so that the Crash can be stopped.

However, this scheme is not a long-term solution. We effectively restrict all operators to configure according to our standards. Therefore, the short-term solution is to limit the resolution of the picture at the back end. When the resolution is exceeded, it will prompt that the upload fails and give a prompt and guidance scheme.

The long-term effective solution is to verify the resolution and size of the image when loading the image on the WebView page, and compress the response to the non-conforming image, like Glide. We are still planning and developing this content in an orderly way and will output it to you in time when it is mature.

6.0.5 WebView FAQs

  • Security policy causes white screen
// After Android 5.0, it is not allowed to load the mixed content of http and https by default. You need to set webView to allow it to load the mixed network protocol content
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
    webviewSetting.setMixedContentMode(WebSettings.MIXED_CONTENT_ALWAYS_ALLOW);
}
  • Safe Browsing function
//Android 8 above is enabled by default & < span class = "specialtitle" > [Google Play Service]</span>
webviewSetting.setSafeBrowsingEnabled(false) 26 following xml Medium configuration
WebView.setSafeBrowsingWhitelist(List<String> hosts, ValueCallback<Boolean>callback)
  • Certificate verification
public void onReceivedSslError(WebView view, SslErrorHandler handler, SslError error) {
    // handler.cancel();  Do not process or cancel the default white screen
    // handler.proceed(); //  Ignore the certificate exception and load the page normally. It is not recommended
    // TODO for certificate verification
}
  • White screen of WebView page caused by compatibility

1.WebView does not support tags by default:

      localStorage.XXX = YYY <br />
       webviewSetting.setDomStorageEnbale(true)
  1. The page cannot be rendered due to syntax error
  2. Network resource acquisition error
  • WebView page white screen caused by memory problems
  1. Webview memory problem
if (mWebView != null) {
      mWebView.loadDataWithBaseURL(null, "", "text/html", "utf-8", null); 
      mWebView.clearHistory(); 
      mLayout.removeView(mWebView); 
       mWebView.destroy(); 
       mWebView = null; 
  }
  1. Memory problems caused by loading content

Handle the webviewclient.onrenderprocessgone (WebView, renderprocessgone detail detail) callback, and remove and destroy the WebView causing the problem ().

Posted by enemeth on Wed, 10 Nov 2021 21:38:39 -0800