5

在我的应用程序的最新版本中,有些用户遇到了我无法重现的崩溃。目前只有Samsung设备运行Lollipop有问题,但这可能只是巧合。 分析堆栈跟踪和相关代码后,我认为我可能找到了罪魁祸首。为了测试我的假设,我简化了代码,下面的代码片段:在onDestroy之后是否可以调用回调方法?

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()之前执行,或者在某种情况下不会出现这种情况?在我的真实应用程序中,当然会有更多的事情发生(像其他线程发布到主线程以及更多操作发生在回调中)。但是这个简单的片段似乎足以证明我的担忧。

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

D/TAG: onDestroy 
D/TAG: listenerNotified 

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

+0

你为什么要通过处理程序发布runnable?同时,你可以看看http://stackoverflow.com/questions/31432014/onclicklistener-fired-after-onpause –

+1

当你有像这样的异步回调时,你应该总是检查回调,如果'活动'仍然活着处理回调。最简单的方法是调用'isFinishing()',如果'Activity'不再是“活动”,则返回'true'。 –

回答

3

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

是的。

让我们来更改一下您的示例代码,将Runnable发布到Handler。我还假设(根据你的描述),你可能有多个Runnable小号发布到主线程,所以在某些时候可能存在的Runnable个队列这是我带来的延迟在实验如下:

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的消息队列相关联,发送并处理Runnable和来自队列的消息。那些Runnable和消息有一个对目标Handler的引用。即使ActivityonDestroy()方法不是“析构函数”,即当方法返回Activity的实例不会立即死亡(see)时,内存不能被GC-ed,因为隐式引用* Activity。您将会泄漏,直到Runnable将从Looper的消息队列中退出并进行处理。

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


*实例匿名内部类Runnable中找到具有指匿名内部类View.OnClickListener的一个实例是,在其反过来,具有对一个参考Activity实例。

+0

'Handler'没有对'Activity'的引用。是什么让你这么想的? –

+0

@David Wasser非常感谢您指出我所犯的错误。 – Onik

+0

我读过那篇关于使用'Handler'和'Context'泄漏内存的文章。实际上,这通常不是一个真正的问题,除非你有一个'static'变量引用了一个死的'Context'。这将是一个真正的内存泄漏(即:永远不会被回收的内存)。大多数时候这些“泄漏”是短暂的,所以它们不是真正的泄漏。正如你所说的那样:“你将会泄漏,直到'Runnable'将被取消......”通常是很短的时间。没什么可担心的。 –

1

您可能需要考虑的不仅仅是发布延迟的可运行给处理程序。当您将任务运行到单独的线程并且您的活动已被销毁时,您可能会遇到问题。你可以这样做和实施。

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

由此,处理程序消息队列上的任何待处理任务将在活动被销毁后销毁。

只有让你知道你不需要在活动被销毁后运行任何任务。

0

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

相关问题