是否可以在 onDestroy 之后调用回调方法?

Posted

技术标签:

【中文标题】是否可以在 onDestroy 之后调用回调方法?【英文标题】:Is it possible for a callback method to be called after 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

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

【问题讨论】:

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

【讨论】:

以上是关于是否可以在 onDestroy 之后调用回调方法?的主要内容,如果未能解决你的问题,请参考以下文章

使用 IntentService 进行位置监听,但在 onHandleIntent 之后立即调用 onDestroy

如果不能保证调用 onDestroy() ,为啥要实现它?

在 JobIntentService 中可以在没有 onHandleWork() 的情况下调用 onDestroy() 吗?

服务的 onDestroy() 回调

Activity的onStop()和onDestroy()延时执行回调的坑

什么是事件回调?