Skip to content

Conversation

@kreativityapps
Copy link

This patch addresses critical bugs in FlutterTtsPlugin’s cold-start path:

1. ConcurrentModificationException

Root cause:
Iterating pendingMethodCalls while clearing it under the same lock.

Fix:
Copy the list under synchronized, clear the original, then invoke the copy outside the lock.

2. ANRs on TextToSpeech.defaultVoice / getVoices() / setLanguage()

Root cause:
Binder IPC and Java deserialization (ObjectInputStream / Voice.) were happening synchronously on the main (UI) thread during OnInitListener.

Fix:
Call engineResult.success(1) (or .error()) immediately on the UI thread so Flutter is unblocked.
Spawn a short‐lived Thread { … } to perform all heavy TTS service calls (tts.defaultVoice, tts.setLanguage, voice enumeration) off the Looper.

Applied consistently in both onInitListenerWithCallback and onInitListenerWithoutCallback.

Had several ANR and crash reports:

🚨 Crash Report (click to expand)
Exception java.util.ConcurrentModificationException:
  at java.util.ArrayList$Itr.checkForComodification (ArrayList.java:1111)
  at java.util.ArrayList$Itr.next (ArrayList.java:1064)
  at com.tundralabs.fluttertts.FlutterTtsPlugin.onInitListener$lambda$3 (FlutterTtsPlugin.kt:221)
  at android.speech.tts.TextToSpeech.lambda$dispatchOnInit$0 (TextToSpeech.java:934)
  at android.speech.tts.TextToSpeech.$r8$lambda$y-uaMPlobiYouiY_T7xFoJSwPww (Unknown Source)
  at android.speech.tts.TextToSpeech$$ExternalSyntheticLambda13.run (D8$$SyntheticClass)
  at android.speech.tts.TextToSpeech.dispatchOnInit (TextToSpeech.java:943)
  at android.speech.tts.TextToSpeech.-$$Nest$mdispatchOnInit (Unknown Source)
  at android.speech.tts.TextToSpeech$Connection$SetupConnectionAsyncTask.onPostExecute (TextToSpeech.java:2324)
  at android.speech.tts.TextToSpeech$Connection$SetupConnectionAsyncTask.onPostExecute (TextToSpeech.java:2284)
  at android.os.AsyncTask.finish (AsyncTask.java:771)
  at android.os.AsyncTask.-$$Nest$mfinish (Unknown Source)
  at android.os.AsyncTask$InternalHandler.handleMessage (AsyncTask.java:788)
  at android.os.Handler.dispatchMessage (Handler.java:107)
  at android.os.Looper.loopOnce (Looper.java:249)
  at android.os.Looper.loop (Looper.java:337)
  at android.app.ActivityThread.main (ActivityThread.java:9486)
  at java.lang.reflect.Method.invoke
  at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run (RuntimeInit.java:636)
  at com.android.internal.os.ZygoteInit.main (ZygoteInit.java:1004)
🚨 ANR Report (click to expand)
    #00  pc 0x00000000004a67e4  /apex/com.android.art/lib64/libart.so (art::DumpNativeStack(std::__1::basic_ostream<char, std::__1::char_traits<char> >&, int, BacktraceMap*, char const*, art::ArtMethod*, void*, bool)+140)
  

    #01  pc 0x00000000005b5704  /apex/com.android.art/lib64/libart.so (art::Thread::DumpStack(std::__1::basic_ostream<char, std::__1::char_traits<char> >&, bool, BacktraceMap*, bool) const+372)
  

    #02  pc 0x00000000005d2ca4  /apex/com.android.art/lib64/libart.so (art::DumpCheckpoint::Run(art::Thread*)+924)
  

    #03  pc 0x00000000005b6670  /apex/com.android.art/lib64/libart.so (art::Thread::RunCheckpointFunction()+176)
  

    #04  pc 0x0000000000674160  /apex/com.android.art/lib64/libart.so (art::JniMethodFastEndWithReference(_jobject*, unsigned int, art::Thread*)+112)
  

    at dalvik.system.VMStack.getClosestUserClassLoader (Native method)
  

    at java.io.ObjectInputStream.latestUserDefinedLoader (ObjectInputStream.java:2186)
  

    at java.io.ObjectInputStream.resolveClass (ObjectInputStream.java:682)
  

    at android.os.Parcel$2.resolveClass (Parcel.java:3452)
  

    at java.io.ObjectInputStream.readNonProxyDesc (ObjectInputStream.java:1703)
  

    at java.io.ObjectInputStream.readClassDesc (ObjectInputStream.java:1594)
  

    at java.io.ObjectInputStream.readOrdinaryObject (ObjectInputStream.java:1872)
  

    at java.io.ObjectInputStream.readObject0 (ObjectInputStream.java:1412)
  

    at java.io.ObjectInputStream.readObject (ObjectInputStream.java:427)
  

    at android.os.Parcel.readSerializable (Parcel.java:3455)
  

    at android.os.Parcel.readSerializable (Parcel.java:3425)
  

    at android.speech.tts.Voice.<init> (Voice.java:86)
  

    at android.speech.tts.Voice.<init> (Voice.java:32)
  

    at android.speech.tts.Voice$1.createFromParcel (Voice.java:112)
  

    at android.speech.tts.Voice$1.createFromParcel (Voice.java:109)
  

    at android.os.Parcel.readTypedObject (Parcel.java:3119)
  

    at android.os.Parcel.createTypedArrayList (Parcel.java:2849)
  

    at android.speech.tts.ITextToSpeechService$Stub$Proxy.getVoices (ITextToSpeechService.java:1032)
  

    at android.speech.tts.TextToSpeech.getVoice (TextToSpeech.java:1699)
  

    at android.speech.tts.TextToSpeech.lambda$setLanguage$8$TextToSpeech (TextToSpeech.java:1527)
  

    at android.speech.tts.-$$Lambda$TextToSpeech$RsLPOHbMGtIthv7AyRho2VQZrE0.run (lambda)
  

    at android.speech.tts.TextToSpeech$Connection.runAction (TextToSpeech.java:2312)
  

    at android.speech.tts.TextToSpeech.runAction (TextToSpeech.java:791)
  

    at android.speech.tts.TextToSpeech.runAction (TextToSpeech.java:781)
  

    at android.speech.tts.TextToSpeech.setLanguage (TextToSpeech.java:1481)
  

    at com.tundralabs.fluttertts.FlutterTtsPlugin.firstTimeOnInitListener$lambda$5 (FlutterTtsPlugin.kt:263)
  

    at android.speech.tts.TextToSpeech.dispatchOnInit (TextToSpeech.java:863)
  

    at android.speech.tts.TextToSpeech.access$800 (TextToSpeech.java:77)
  

    at android.speech.tts.TextToSpeech$Connection$SetupConnectionAsyncTask.onPostExecute (TextToSpeech.java:2226)
  

    at android.speech.tts.TextToSpeech$Connection$SetupConnectionAsyncTask.onPostExecute (TextToSpeech.java:2181)
  

    at android.os.AsyncTask.finish (AsyncTask.java:771)
  

    at android.os.AsyncTask.access$900 (AsyncTask.java:199)
  

    at android.os.AsyncTask$InternalHandler.handleMessage (AsyncTask.java:788)
  

    at android.os.Handler.dispatchMessage (Handler.java:106)
  

    at android.os.Looper.loop (Looper.java:223)
  

    at android.app.ActivityThread.main (ActivityThread.java:7712)
  

    at java.lang.reflect.Method.invoke (Native method)
  

    at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run (RuntimeInit.java:612)
  

    at com.android.internal.os.ZygoteInit.main (ZygoteInit.java:997)

@dlutton dlutton self-assigned this Sep 1, 2025
@dlutton dlutton mentioned this pull request Sep 1, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants