在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到你的应用上,来看看还有哪些被你忽略的崩溃吧。