早上来到公司刚打开电脑,就被叫到会议室,召开紧急会议,线上出现重大bug,根据线上日志统计,崩溃率上升了0.3个百分点(我们项目是集成了腾讯Bugly统计日志,有兴趣的同学可以去了解下)。
这个可不得了,听说领导被老板叫过去训了好久,领导憋了一肚子火,我们的日子你大概能想象得到。没办法,那的赶紧找出bug来源,解决掉后赶紧发修复补丁(感谢现在的热修复技术,让我们免除了再次发包的痛苦)。
经过我们的查询,崩溃位置很快就被找到,是一处Fragment中的网络请求回调,在网络请求回调成功后,进行数据填充,报了空指针,造成了崩溃。可是咋看那一句也不可能造成空指针啊,控件findview后已经实例化,不可能为空,数据网络请求成功也不可能为空,那到底是为什么报空呢?测试组和我们对那一部分一起进行了模拟测试,可是未出现崩溃的现象,奇了怪了。突然想到是不是因为我们在公司都是连接的WIFI,信号好,所以没出现线上的问题,我们立即进行测试,切换到弱网环境,果然出现了(这也是在测试阶段我们未发现这个问题的原因,建议大家在测试时要再多种环境下进行测试)。
那到底是为什么呢?结合我们的重现,发现是因为界面被回收了,而网络请求还在线程中进行,当线程请求成功后那就会回调返回数据,但是这个时候界面已经被回收掉了,控件为空了,所以报了空。问题找到后那赶紧解决啊。
既然是因为界面回收掉了,那我们在网络回调后填充数据前进行一个界面是否被回收掉的判断,就像这样:
if (mactivity == null || mactivity.isFinishing()) {
return;
}
然后进行测试,果然问题解决了,赶紧发补丁吧,领导的怒火终于平息了一些,我们这些开发也松了口气,但是仍旧不能掉以轻心,我们继续紧盯线上日志,要看到问题彻底解决了我们才能放下心里的石头。果然,在发完补丁后一段时间,同样的崩溃问题出现的越来越少了,看来问题解决了,偷偷的看下领导,脸上也开始有笑容了,恩,这一关终于熬过去了。
过了几天,崩溃率也下去了,看来这一问题是彻底解决了,心里的石头这时候才彻底放下。渐渐地大家都忘记了这一问题,毕竟在大家看来这已经是解决了,但是我在看线上日志的时候,突然看到了这一问题,纳尼,还有,虽然很少,只有几例,但是还是存在。我立即偷偷的和几个同事商量这个问题,得出的结果是热修复有一定的失败几率,可能是因为那几个用户热修复补丁加载失败,恩,很有可能,那既然这样大家就不用再多想了,等下次发包用户更新了那就彻底没有这个问题了。
可是我还是不太放心,我又在网上查看相关的文章,资料,终于让我找到一位的文章,链接在这:
(参考文章链接)
主要原因是因为我们的判断不够谨慎,我们只判断了 ==null 和 .isFinishing() 两个条件,点进isFinishing() 方法里面看一下:
/**
* Check to see whether this activity is in the process of finishing,
* either because you called {@link #finish} on it or someone else
* has requested that it finished. This is often used in
* {@link #onPause} to determine whether the activity is simply pausing or
* completely finishing.
*
* @return If the activity is finishing, returns true; else returns false.
*
* @see #finish
*/
public boolean isFinishing() {
return mFinished;
}
这个方法主要是返回一个标记值 mFinished ,那我们在Activity 类中全局搜索下这个中介值mFinished ,可以发下他是起始为false,但是只有当activity 调用 finish() 时,在这个 mFinished 值会被置为true :
/**
* Finishes the current activity and specifies whether to remove the task associated with this
* activity.
*/
private void finish(boolean finishTask) {
if (mParent == null) {
int resultCode;
Intent resultData;
synchronized (this) {
resultCode = mResultCode;
resultData = mResultData;
}
if (false) Log.v(TAG, "Finishing self: token=" + mToken);
try {
if (resultData != null) {
resultData.prepareToLeaveProcess();
}
if (ActivityManagerNative.getDefault()
.finishActivity(mToken, resultCode, resultData, finishTask)) {
mFinished = true;
}
} catch (RemoteException e) {
// Empty
}
} else {
mParent.finishFromChild(this);
}
}
而我们知道 finish() 是主动调用的的,也就是说只有你在代码中调用了 finish() 这个方法,它才会被置为true,也就是说如果系统内存紧张,回收掉了它,那就不会走 finish() 这个方法,这个时候mFinished 还是为false,这个时候用 isFinishing() 判断 的话得不到正确的结果,那这种情况该如何判断呢?在API 17的时候,Google 添加了一个 isDestroyed() 的判断,专门针对这一情况 :
/**
* Returns true if the final {@link #onDestroy()} call has been made
* on the Activity, so this instance is now dead.
*/
public boolean isDestroyed() {
return mDestroyed;
}
所以为了保险起见,我们还需要加上 isDestroyed() 判断,做到万无一失。
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) {
if (mactivity.isDestroyed()) {
return;
}
}