在Crashlytics,我們經常幫助開發者探索如何建立最穩定應用的方法。抱著這個想法,最近我們開始研究安卓應用崩潰的普遍原因。尤其令我們好奇的是能否在Android Support Library中找到應用崩潰的一些蛛絲馬跡,因為這是安卓應用中最廣泛使用的Library之一。
在我們分析的一億個崩潰裡,發現大約有4%的崩潰與這個Support Library有關。通過更深層次的分析,我們的研究表明,絕大多數的崩潰是由一些常見較小的錯誤造成,而且這些錯誤卻可以避免的。基於這些分析,我們確定了那些使用Support Library時往往被忽略的一些最佳實踐,以及三個提高穩定性的關鍵方法。
1、AsyncTasks和配置資訊改變
AsyncTasks(非同步任務)經常用於實現後臺操作,以及操作完成後選擇性更新使用者介面。使用AsyncTasks和處理配置資訊的改變(Configuration Changes)是常見漏洞的來源。當AsyncTask正在執行時,如果fragment與對應的activity脫離,當嘗試進入activity時,你的應用就會呼叫棧時崩潰,例如下面這個崩潰:
1 2 3 4 |
; html-script: false ] java.lang.IllegalStateException: Fragment MyFragment not attached to Activity at android.support.v4.app.Fragment.getResources(Fragment.java:551) at android.support.v4.app.Fragment.getString(Fragment.java:573) |
在堆疊的最頂端,fragment要依賴一個有效的activity來獲取應用的資源。預防這種崩潰的一種方法,是在配置發生變化時保留該AsyncTask任務。實現這種方法可以使用RetainedFragment來執行AsyncTask並通知監聽器關於AsyncTask操作的狀態。更多資訊請檢視這個示例FragmentRetainInstance.java
2、安全地執行Fragment Transaction
Fragment transactions用於在一個Activity上新增、移除或者替換fragment。大多數時候,fragment transaction會在activity的onCreate()方法中執行,也可能在與使用者互動中響應。然而,我們看過很多的例子都是當恢復一個activity時,fragment transaction被執行了。發生這種情況時,你的應用就可能發生下面的下崩潰:
1 2 3 4 5 6 7 |
; html-script: false ] java.lang.IllegalStateException: Can not perform this action after onSaveInstanceState at android.support.v4.app.FragmentManagerImpl.checkStateLoss(FragmentManager.java:1327) at android.support.v4.app.FragmentManagerImpl.enqueueAction(FragmentManager:1338) at android.support.v4.app.BackStackRecord.commitInternal(BackStackRecord.java:595) at android.support.v4.app.BackStackRecord.commit(BackStackRecord:574) at android.support.v4.app.DialogFragment.show(DialogFragment:127) |
不管何時,如果一個FragmentActivity放在後臺,對應FragmentMangerImpl中mStateSaved的flag就會設定為true。這個flag是用來檢查是否有state loss。當試圖執行一個transaction時,如果這個flag為true,那麼首先會丟擲IllegalStateException異常。要防止state loss,在onSaveInstanceState()方法呼叫前,不能執行fragment transaction。發生崩潰可能會的原因是,狀態已經儲存但flag還沒有設定回false就已經呼叫了onResume()方法。
要預防這種崩潰,就要防止在activity的onResume()方法中執行fragment transactions。不然就使用onResumeFragment()方法,這個方法推薦用來解決在合適的狀態與fragment打交道的情形。
3、管理Cursor的生命週期
CursorAdapter可以容易地將Cursor的資料繫結在一個ListView物件上。然而,如果一個cursor變為無效時,如果更新使用者介面,就會發生下面的崩潰:
1 2 3 4 |
; html-script: false ] java.lang.IllegalStateException: this should only be called when the cursor is valid at android.support.v4.widget.CursorAdapter.getView(CursorAdapter.java:245) at android.widget.HeaderViewListAdapter.getView(HeaderViewListAdapter.java:253) |
當CursorAdapter的mDataValid欄位設定為false時,就會丟擲這個異常。發生這種情況的原因如下:
- cursor被設定為空;
- cursor上的一個重新查詢操作失敗了;
- data上的onInvalidated()方法被呼叫了。
發生這種情況的一個原因是,是否同時使用CursorLoader和startManagingCursor()方法來管理你的cursor。startManagingCursor()已經被棄用了,取而代之的是CursorLoader。如果你正使用fragments,請確保使用CursorLoader來管理cursor的生命週期,並且移除所有startManagingCursor()和stopManagingCursor()方法的引用。
小結
通過上述三個注意事項,能大大減少Support Library丟擲致命異常的機率。這樣能帶來更好的使用者體驗,更好的評分和一個更成功的應用。
Crashlytics for Android會報告來由Support Library或者應用其它地方丟擲的未捕獲異常。新增Android SDK到你的應用上,來看看還有哪些被你忽略的崩潰吧。