Search code examples
androiddelphifiremonkeydelphi-10.1-berlin

How to properly clean an Android listener?


Under Android we can declare some listener like in following example :

TErrorListener = class(TJavaLocal, JMediaPlayer_OnErrorListener)
  private
  public
  function onError(mp: JMediaPlayer; what: Integer; extra: Integer): Boolean; cdecl;
end;    

TmyObject = class
private
  FMediaPlayer: jmediaplayer;
  FOnErrorListener: TErrorListener;
end;

and then we can activate the OnErrorListener like this :

FMediaPlayer.setOnErrorListener(FOnErrorListener);

However, deactivation of such OnErrorListener is problematic.

In Delphi for Android UI thread and main thread are different threads and onError will be called in context of Android UI thread because of this function in underlying Java framework:

  //   Looper looper;
  //   if ((looper = Looper.myLooper()) != null) {
  //     mEventHandler = new EventHandler(this, looper);
  //   } else if ((looper = Looper.getMainLooper()) != null) {
  //     mEventHandler = new EventHandler(this, looper);
  //   } else {
  //     mEventHandler = null;
  //   } 

Setting FMediaPlayer.setOnErrorListener(nil); in the main thread can cause problems because it's different from the UI thread that might be doing some work on internal variable mOnErrorListener.

I tried to synchronize deactivation with following code:

  CallInUIThreadAndWaitFinishing(
    procedure
    begin
      FMediaPlayer.setOnErrorListener(nil);
    end);

However, sometimes application crashes at that point and I don't know why. How can I avoid that?


Solution

  • Like you noted CallInUIThreadAndWaitFinishing has two overloads:

    TMethodCallback = procedure of object;
    TCallBack = reference to procedure;
    
    procedure CallInUIThreadAndWaitFinishing(AMethod: TMethodCallback); overload;
    procedure CallInUIThreadAndWaitFinishing(AMethod: TCallBack); overload;
    

    In first one callback is regular method that does not have any special handling involved. After it executes your code continues to function in regular manner.

    Using

    CallInUIThreadAndWaitFinishing(aProcedureOfObject);
    

    works because there is no hidden capturing behind the scenes.


    In second one callback is anonymous method that comes with special feature - strong capturing of any variables used in method body. In case of

    CallInUIThreadAndWaitFinishing(procedure
    begin
      FmyObject.dosomething
    end);
    

    it will capture FmyObject variable.

    That is first step in calling CallInUIThreadAndWaitFinishing with anonymous method callback. Next thing what happens is that Java runnable object is created

    Runnable := TRunnable.Create(AMethod);
    ActiveJavaRunnables.Add(Runnable);
    Runnable.Start;
    

    Above code stores your anonymous method (AMethod) in TRunnable FCallback field creating strong reference to your anonymous method as well as any variables that method captured.

    And now it comes the real catch. Runnable object is not automatically cleared when your code executes - it is stored in ActiveJavaRunnables list and that list is cleared periodically in non-deterministic fashion - in sense that you don't know when it will be cleared and when it will actually release that Runnable and stored FCallback along with everything captured along the way.

    If you want to use anonymous methods you have to create temporary weak reference to any object you use inside anonymous method body and use that weak reference instead.