如何正确清理 Android 侦听器?

How to properly clean an Android listener?

在 Android 下,我们可以声明一些侦听器,如下例所示:

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;

然后我们可以像这样激活 OnErrorListener :

FMediaPlayer.setOnErrorListener(FOnErrorListener);

然而,停用这样的OnErrorListener是有问题的。

In Delphi for Android UI 线程和主线程是不同的线程,onError 将在 Android [=41= 的上下文中调用] 线程因为底层 Java 框架中的这个函数:

  //   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;
  //   } 

在主线程中设置 FMediaPlayer.setOnErrorListener(nil); 可能会导致问题,因为它不同于可能正在对内部变量 mOnErrorListener 执行某些操作的 UI 线程。

我尝试将停用与以下代码同步:

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

但是,有时应用程序会在此时崩溃,我不知道为什么。我怎样才能避免这种情况?

我找到了如何避免偶发性应用程序崩溃的方法:

永远不要打电话给

CallInUIThreadAndWaitFinishing(procedure
begin
  FmyObject.dosomething
end);

改用以下内容

CallInUIThreadAndWaitFinishing(aProcedureOfObject);

为什么?

因为第一个变体将保持对 FmyObject 的强引用(不增加引用计数 - 我不知道为什么),稍后,在你已经释放 FmyObject 之后,尝试释放它再次导致异常。

如您所述,CallInUIThreadAndWaitFinishing 有两个重载:

TMethodCallback = procedure of object;
TCallBack = reference to procedure;

procedure CallInUIThreadAndWaitFinishing(AMethod: TMethodCallback); overload;
procedure CallInUIThreadAndWaitFinishing(AMethod: TCallBack); overload;

第一个回调是常规方法,不涉及任何特殊处理。执行后,您的代码将继续正常运行。

使用

CallInUIThreadAndWaitFinishing(aProcedureOfObject);

之所以有效,是因为在幕后没有隐藏的捕获。


第二个回调是具有特殊功能的匿名方法 - 强烈捕获方法主体中使用的任何变量。如果

CallInUIThreadAndWaitFinishing(procedure
begin
  FmyObject.dosomething
end);

它将捕获 FmyObject 个变量。

这是使用匿名方法回调调用 CallInUIThreadAndWaitFinishing 的第一步。接下来发生的事情是 Java 可运行对象被创建

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

以上代码将您的匿名方法 (AMethod) 存储在 TRunnable FCallback 字段中,创建对您的匿名方法以及该方法捕获的任何变量的强引用。

现在真正的收获来了。 Runnable 对象在您的代码执行时不会自动清除 - 它存储在 ActiveJavaRunnables 列表中并且该列表以 non-deterministic 方式定期清除 - 从某种意义上说您不知道它什么时候会被清除,什么时候它会真正释放 Runnable 并存储 FCallback 以及沿途捕获的所有内容。

如果您想使用匿名方法,您必须为您在匿名方法体内使用的任何对象创建临时弱引用,并改用该弱引用。