Summary
UserAgentRetriever calls WebSettings.getDefaultUserAgent() from a background thread on every
Adapty.activate. When the WebView engine fails to start on that device, the resulting exception
is delivered on the main thread and kills the process. The try/catch already present in
retrieveUserAgent() does not cover this path, so there is nothing an integrator can do about it.
Environment
- Adapty Android SDK 3.17.2 (
io.adapty:adapty-bom)
- Reproduces on various Android versions and devices, not tied to one OS version
- Reported via Firebase Crashlytics from production
What happens
- We call
Adapty.activate(applicationContext, AdaptyConfig.Builder(apiKey).build()) in
Application.onCreate().
UserAgentRetriever is created and calls WebSettings.getDefaultUserAgent(appContext) inside a
plain thread { }.
- Because the call is off the main thread, WebView posts the chromium startup as a task to the
main thread, and the background thread blocks on a CountDownLatch (visible as Thread-5 in
the dump below).
- On the main thread, chromium startup fails to resolve its own resource:
Resources$NotFoundException: String array resource ID #0x1070005 in
AwResource.getConfigKeySystemUuidMapping.
- Chromium wraps it into
org.chromium.base.JniAndroid$UncaughtExceptionException, which escapes
MessageQueue.nativePollOnce inside Looper.loop and kills the process.
The underlying WebView bug is known: https://issues.chromium.org/issues/40205792. It fires when the
WebView package is unavailable at that moment (being updated, disabled, or the process resources
went stale after an apk update). Other libraries hit the same thing and moved away from
getDefaultUserAgent (for example react-native-device-info#272).
Why the current try/catch does not help
private fun retrieveUserAgent() {
thread {
try {
lock.writeLock().lock()
userAgent = WebSettings.getDefaultUserAgent(appContext)
} catch (_: Throwable) {
} finally {
lock.writeLock().unlock()
}
}
}
The exception never reaches this catch. It is thrown on the main thread, inside a chromium task
that nothing wraps, and goes straight to the default uncaught handler.
Impact
Our app does not use WebView anywhere. The WebView engine is started in our process only because
of this user agent call, and that is the sole reason the app crashes. There is also no way to opt
out: AdaptyConfig.Builder has no option related to user agent collection.
Stack traces
Fatal exception (main thread)
Fatal Exception: org.chromium.base.JniAndroid$UncaughtExceptionException
at org.chromium.base.JniAndroid.handleException(chromium-SystemWebViewGoogle6432.aab-stable-787118103:21)
at android.os.MessageQueue.nativePollOnce(MessageQueue.java)
at android.os.MessageQueue.next(MessageQueue.java:381)
at android.os.Looper.loopOnce(Looper.java:191)
at android.os.Looper.loop(Looper.java:325)
at android.app.ActivityThread.main(ActivityThread.java:10404)
at java.lang.reflect.Method.invoke(Method.java)
at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:635)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:970)
Caused by android.content.res.Resources$NotFoundException: String array resource ID #0x1070005
at android.content.res.Resources.getStringArray(Resources.java:900)
at org.chromium.android_webview.common.AwResource.getConfigKeySystemUuidMapping(chromium-SystemWebViewGoogle6432.aab-stable-787118103:5)
at android.os.MessageQueue.nativePollOnce(MessageQueue.java)
at android.os.MessageQueue.next(MessageQueue.java:381)
at android.os.Looper.loopOnce(Looper.java:191)
at android.os.Looper.loop(Looper.java:325)
at android.app.ActivityThread.main(ActivityThread.java:10404)
Blocked Adapty thread at the same moment
Thread-5:
at jdk.internal.misc.Unsafe.park(Unsafe.java)
at java.util.concurrent.locks.LockSupport.park(LockSupport.java:221)
at java.util.concurrent.CountDownLatch.await(CountDownLatch.java:230)
at com.android.webview.chromium.q0.n(chromium-SystemWebViewGoogle6432.aab-stable-787118103:21)
at com.android.webview.chromium.WebViewChromiumFactoryProvider$StaticsAdapter.getDefaultUserAgent(chromium-SystemWebViewGoogle6432.aab-stable-787118103:44)
at android.webkit.WebSettings.getDefaultUserAgent(WebSettings.java:1408)
at com.adapty.internal.utils.UserAgentRetriever$retrieveUserAgent$1.invoke(UserAgentRetriever.kt:35)
at com.adapty.internal.utils.UserAgentRetriever$retrieveUserAgent$1.invoke(UserAgentRetriever.kt:32)
at kotlin.concurrent.ThreadsKt$thread$thread$1.run(Thread.kt:30)
Possible fixes
- Fall back to
System.getProperty("http.agent") and never touch WebView. This is what other
libraries switched to after the same bug, and it needs no WebView process at all.
- Add an option to
AdaptyConfig.Builder to disable user agent collection or to supply a custom
user agent string.
- At minimum, make the call lazy (only when a request actually needs the user agent) and run it on
the main thread, so the failure lands inside your own try/catch instead of escaping to the
looper.
Summary
UserAgentRetrievercallsWebSettings.getDefaultUserAgent()from a background thread on everyAdapty.activate. When the WebView engine fails to start on that device, the resulting exceptionis delivered on the main thread and kills the process. The
try/catchalready present inretrieveUserAgent()does not cover this path, so there is nothing an integrator can do about it.Environment
io.adapty:adapty-bom)What happens
Adapty.activate(applicationContext, AdaptyConfig.Builder(apiKey).build())inApplication.onCreate().UserAgentRetrieveris created and callsWebSettings.getDefaultUserAgent(appContext)inside aplain
thread { }.main thread, and the background thread blocks on a
CountDownLatch(visible asThread-5inthe dump below).
Resources$NotFoundException: String array resource ID #0x1070005inAwResource.getConfigKeySystemUuidMapping.org.chromium.base.JniAndroid$UncaughtExceptionException, which escapesMessageQueue.nativePollOnceinsideLooper.loopand kills the process.The underlying WebView bug is known: https://issues.chromium.org/issues/40205792. It fires when the
WebView package is unavailable at that moment (being updated, disabled, or the process resources
went stale after an apk update). Other libraries hit the same thing and moved away from
getDefaultUserAgent(for example react-native-device-info#272).Why the current try/catch does not help
The exception never reaches this
catch. It is thrown on the main thread, inside a chromium taskthat nothing wraps, and goes straight to the default uncaught handler.
Impact
Our app does not use WebView anywhere. The WebView engine is started in our process only because
of this user agent call, and that is the sole reason the app crashes. There is also no way to opt
out:
AdaptyConfig.Builderhas no option related to user agent collection.Stack traces
Fatal exception (main thread)
Blocked Adapty thread at the same moment
Possible fixes
System.getProperty("http.agent")and never touch WebView. This is what otherlibraries switched to after the same bug, and it needs no WebView process at all.
AdaptyConfig.Builderto disable user agent collection or to supply a customuser agent string.
the main thread, so the failure lands inside your own
try/catchinstead of escaping to thelooper.