Skip to content

Potential main-thread SVG stream read during multimedia image preview #21276

Description

@venkyqz

Checked for duplicates?

  • This issue is not a duplicate

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

  • I have checked the manual and the FAQ and could not find a solution to my issue

Metadata

Metadata

Assignees

No one assigned

    Type

    Priority

    None yet

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions