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

相關問題