About the author
Recently, I found out that any multithreaded Android application written with the FireMonkey framework can crash, if you use the CallInUIThread routine.
When CallInUIThread is called from a worker thread (ie, not the main thread), a JRunnable instance is created, and passed to the main activity to call by using its runOnUiThread method.
After the JRunnable's run method calls the given callback, it then calls TFinishedRunnableCollector.Call, which creates an instance of TFinishedRunnableCollector and call its Start method.
TFinishedRunnableCollector.Start can cause a crash, due to TFinishedRunnableCollector.DoTimer not zeroing out FTimerHandle after it is destroyed.
When TFinishedRunnableCollector.Call is called the first time, as FFinishedThreadCollector is not initialized, it is created.
TFinishedRunnableCollector.Call then calls FFinishedThreadCollector.Start. Since this is the first call, and FTimerHandle is 0 (checked using HasTimer), the call to DestroyTimer is skipped.
FTimerHandle is then setup using FTimerService.CreateTimer(Timeout, DoTimer); When the timer created and assigned to FTimerHandle is run, it calls DoTimer, which destroys the timer specified by the FTimerHandle, but doesn’t zero out FTimerHandle.
The next time TFinishedRunnableCollector.Call is called, it calls FFinishedThreadCollector.Start again, and since FTimerHandle is not 0, it will call DestroyTimer.
if the memory pointed to by FTimerHandle is now invalid, or overwritten by other data, then DestroyTimer (which is implemented by TAndroidTimerService.DestroyTimer) will cause an AV, when _InstCopy is called in the statement "Timer := TAndroidTimer(ATimer); " in FMX.PlatformAndroid.Timer.pas
This can be very hard to track down.
How can you fix this? See the QP issue I've filed. Here's a screenshot of an Android app that I wrote to demonstrate the crash.
How to free more space on your home drive by redirecting the location for SDKs in RAD Studio
Learn the command line used to compile System.pas in Delphi
A method to design records so that they're allocated on a specific byte boundary, such as 16 bytes, 512 bytes, 4096 bytes, etc.
Learn why the map is cool in Go!