【问题标题】:Is it possible for a callback method to be called after onDestroy?是否可以在 onDestroy 之后调用回调方法?
【发布时间】:2017-01-26 12:54:29
【问题描述】:

在我的应用程序的最新版本中,一些用户遇到了我无法重现的崩溃。目前只有运行LollipopSamsung 设备存在问题,但这可能只是巧合。 在分析了堆栈跟踪和相关代码之后,我认为我可能已经找到了罪魁祸首。为了测试我的假设,我将代码简化为下面的 sn-p:

public class TestActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        Button b = new Button(this);
        b.setText("Click me!");
        b.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                new Handler().post(new Runnable() {
                    @Override
                    public void run() {
                        // This is the callback method
                        Log.d("TAG", "listenerNotified");
                    }
                });
            }
        });

        setContentView(b);
    }

    @Override
    protected void onDestroy() {
        super.onDestroy();
        Log.d("TAG", "onDestroy");
    }

}

每次我通过先点击点击我按钮然后返回按钮来测试上述应用程序时,listenerNotified 会在onDestroy() 之前打印到控制台。

但我不确定我是否可以依赖这种行为。 Android 是否对上述情况做出任何保证?我是否可以安全地假设我的Runnable 将始终在onDestroy() 之前执行,或者是否有一种情况并非如此?在我的真实应用程序中,当然会发生更多事情(例如其他线程发布到主线程以及回调中发生的更多操作)。但这个简单的 sn-p 似乎足以表明我的担忧。

是否有可能(可能由于其他线程或发布到主线程的回调的影响)我得到下面的调试输出?

D/TAG: onDestroy
D/TAG: listenerNotified

我想知道这一点,因为可能的结果可以解释崩溃。

【问题讨论】:

  • 为什么要通过处理程序发布可运行文件?同时你可以看看stackoverflow.com/questions/31432014/…
  • 当你有这样的异步回调时,如果Activity 仍然存在,你应该在处理回调之前检查回调。最简单的方法是调用isFinishing(),如果Activity 不再“活动”,则返回true

标签: java android multithreading android-activity android-lifecycle


【解决方案1】:

onDestroy()之后可以调用回调方法吗?

是的。

让我们稍微修改一下关于将Runnable 发布到Handler 的示例代码。我还假设(根据您的描述)您可能有多个Runnables 发布到主线程,所以在某些时候可能会有Runnables 的队列,这使我在下面的实验中延迟:

public void onClick(View view) {
    new Handler().postDelayed(new Runnable() {
        @Override
        public void run() {
            // This is the callback method
            Log.d("TAG", "listenerNotified");
        }
    }, 3000);
}

现在按下按钮b,然后按下返回按钮,您应该会看到有问题的输出。

Might it be the reason of your app crash? 没有看到你得到了什么很难说。我只想指出,当new Handler() 在线程(您的情况下为主线程)上实例化时,Handler 与线程的Looper 的消息队列相关联,发送到并处理@ 987654334@s 和来自队列的消息。那些Runnables 和消息都引用了目标Handler。即使ActivityonDestroy() 方法不是“析构函数”,即当方法返回时Activity 的实例不会立即被杀死(see),内存不能被GC-ed因为对Activity 的隐式引用*。在RunnableLooper 的消息队列中出列并处理之前,您将一直泄漏。

更详细的解释可以在How to Leak a Context: Handlers & Inner Classes找到


* 匿名内部类Runnable 的实例引用了匿名内部类View.OnClickListener 的实例,而该实例又引用了Activity 实例。

【讨论】:

  • Handler 没有对 Activity 的引用。是什么让你这么认为?
  • @David Wasser 非常感谢您指出我所犯的错误。
  • 我读过那篇关于HandlerContext 泄漏内存的文章。在实践中,这通常不是一个真正的问题,除非您有一个 static 变量引用了一个死的 Context。那将是真正的内存泄漏(即:永远不会回收的内存)。大多数情况下,这些“泄漏”是短暂的,因此它们不是真正的泄漏。正如您所说的“您将一直泄漏,直到 Runnable 被取消队列......”这通常是一个很短的时间。没什么好担心的。
【解决方案2】:

您可能需要考虑的不仅仅是将延迟的可运行文件发布到处理程序。当您将任务运行到单独的线程中并且您的活动已被破坏时,您可能会遇到问题。您可以像这样执行和实现。

your class activity
{
  Handler mHandler;

  .. onCreate ()
  {
    mHandler = new Handler();
  }

  .. onDestory ()
  {
    if (mHandler != null)
    {
      mHandler.removeCallbacksAndMessages(null);
      mHandler = null;
    }
  }

  private void post (Runnable r)
  {
    if (mHandler != null)
    {
      mHandler.post(r);
    }
  }
}

这样,处理程序消息队列上的任何待处理任务都将在活动被销毁时被销毁。

只是考虑到您知道在 Activity 被销毁后您不需要运行任何任务。

【讨论】:

    【解决方案3】:

    答案是“是”。顺便说一下,这可能会导致Memory Leak

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2014-06-24
      • 2011-03-03
      • 1970-01-01
      • 1970-01-01
      • 2019-05-22
      • 1970-01-01
      相关资源
      最近更新 更多