Checked for duplicates?
What are the steps to reproduce this bug?
Hi AnkiDroid Team,
I’m a PhD student researching Android performance issues. My research group recently ran a static analysis scan for thread-affinity and main-thread blocking bugs in real-world Android apps, and our prototype flagged a potential issue in AnkiDroid.
This report has been strictly re-audited against the latest default branch source listed below, rather than only relying on the older commit URL from the initial static-analysis export. I did not dynamically reproduce an ANR/crash, so this should be treated as a source-confirmed main-thread blocking risk rather than a reproduced runtime failure.
App version
Current default branch: ankidroid/Anki-Android@main
Checked target
- Repository:
ankidroid/Anki-Android
- Source-level caller:
MultimediaImageFragment#previewImage -> WebView#loadSvgImage -> loadSvgFromUri
- Detected API / pattern:
ContentResolver#openInputStream(...) / stream-to-string conversion
- Underlying platform APIs:
ContentResolver#openInputStream(...), InputStream#read(...)
- Observed context: image preview path before updating a WebView
- Expected context: worker/background thread before file/provider stream read
What I found
The latest SVG preview path still reads the selected SVG content synchronously. previewImage(...) calls loadSvgImage(...) for SVG MIME types, and loadSvgImage(...) immediately calls loadSvgFromUri(...). That helper opens an input stream from the URI and converts the whole stream into a string before calling WebView.loadDataWithBaseURL(...).
Verified source trace
User selects an SVG image
-> MultimediaImageFragment.previewImage(imageUri)
-> WebView.loadSvgImage(imageUri)
-> loadSvgFromUri(imageUri)
-> contentResolver.openInputStreamSafe(uri)
-> inputStream.convertToString()
-> WebView.loadDataWithBaseURL(...)
Why this matters
A selected SVG may be large or backed by a slow document provider. Reading the whole stream synchronously can delay preview rendering and block the multimedia UI.
Android’s ANR guidance lists slow I/O, long calculations, and synchronous Binder calls on the main thread as common ANR patterns. In this case, the risky operation is user-visible because it runs from an Activity/Fragment/View/service lifecycle path or a direct UI action path.
Possible fix
Load and parse/read the SVG string on Dispatchers.IO, then call WebView.loadDataWithBaseURL(...) on the main thread after the data is available.
A typical Android pattern is to move the blocking work to an IO/background dispatcher or executor, then publish only the final UI state on the main thread, for example:
lifecycleScope.launch {
val result = withContext(Dispatchers.IO) {
// perform database/content-provider/file/media work here
}
// update UI here on the main thread
}
For non-UI classes, a dedicated executor, repository-level coroutine scope, or existing background worker would also work. The important point is to avoid performing the blocking operation synchronously on the main thread.
Source references
Research
Checked for duplicates?
What are the steps to reproduce this bug?
Hi AnkiDroid Team,
I’m a PhD student researching Android performance issues. My research group recently ran a static analysis scan for thread-affinity and main-thread blocking bugs in real-world Android apps, and our prototype flagged a potential issue in AnkiDroid.
This report has been strictly re-audited against the latest default branch source listed below, rather than only relying on the older commit URL from the initial static-analysis export. I did not dynamically reproduce an ANR/crash, so this should be treated as a source-confirmed main-thread blocking risk rather than a reproduced runtime failure.
App version
Current default branch:
ankidroid/Anki-Android@mainChecked target
ankidroid/Anki-AndroidMultimediaImageFragment#previewImage -> WebView#loadSvgImage -> loadSvgFromUriContentResolver#openInputStream(...)/ stream-to-string conversionContentResolver#openInputStream(...), InputStream#read(...)What I found
The latest SVG preview path still reads the selected SVG content synchronously.
previewImage(...)callsloadSvgImage(...)for SVG MIME types, andloadSvgImage(...)immediately callsloadSvgFromUri(...). That helper opens an input stream from the URI and converts the whole stream into a string before callingWebView.loadDataWithBaseURL(...).Verified source trace
Why this matters
A selected SVG may be large or backed by a slow document provider. Reading the whole stream synchronously can delay preview rendering and block the multimedia UI.
Android’s ANR guidance lists slow I/O, long calculations, and synchronous Binder calls on the main thread as common ANR patterns. In this case, the risky operation is user-visible because it runs from an Activity/Fragment/View/service lifecycle path or a direct UI action path.
Possible fix
Load and parse/read the SVG string on
Dispatchers.IO, then callWebView.loadDataWithBaseURL(...)on the main thread after the data is available.A typical Android pattern is to move the blocking work to an IO/background dispatcher or executor, then publish only the final UI state on the main thread, for example:
lifecycleScope.launch { val result = withContext(Dispatchers.IO) { // perform database/content-provider/file/media work here } // update UI here on the main thread }For non-UI classes, a dedicated executor, repository-level coroutine scope, or existing background worker would also work. The important point is to avoid performing the blocking operation synchronously on the main thread.
Source references
Research