表现:Qzone进入后天,过一会在点Qzone图标启动,出现crash
模拟条件:启动Qzone,按home进入后台,用手机管家小火箭清理内存,再点开Qzone,出现QQ空间停止运行

根据经验,首先怀疑这里可能是fragment的恢复机制导致的,fragment的机制长期为人所诟病,比如曾经遇到过fragment被回收之后按back键退栈出现crash等问题,但是具体问题症结在那里,又有些飘忽说不出个所以然来,因为非毕现,看源码行数等都对不上。

第一步:分析堆栈,当头迎来一个问题,栈太深了,Log.getStackTraceString打不全,cause by的起点找不到,幸好log里面还打了一份,但是时而能看到crash栈底,时而看不到,于是找到最近的路径QzoneFriendFeedActivity?onCreate里面捕获这个crash,然后打印出来,再抛出去,崩溃吧……少年。

第二步:好了,堆栈有了,,也能每次在logcat里面看到出问题的点,先看看源码吧,不管它有多坑爹,发现Fragment在恢复的时候会用反射的方式方式类初始化曾经用过的fragment,反射调用哪个类的构造函数,结合提示知道fragment的子类没有默认构造函数,ok,给你加上,一口气把所以的用到的fragment都加上默认构造函数,结果是。。。不顶用,继续crash,尝试失败 :(

再次仔细看crash日志,貌似是activity没有默认构造函数,不合道理啊,不管,加上再说,结果,依然crash,无耐,拿出杀手锏,debug,幸好有神机google亲儿子在手,sdksupport v4里面的代码一样可以调试

第三步:开始debug 框架的sdk,断点打到Fragmentinstantiate里面,打出那个构造不了的className,发现是 com.qzone.ui.feed.friendfeed.QzoneFriendFeedActivity?$4 ,。。。。 原来是个匿名内部内,坑爹啊,怎么会有这个东西,匿名内部类不是没构造函数么,有迷糊了,还是先找出来这个$4这个孩子是谁吧,怎么找呢?

第四步:找$4,貌似记得dump内存的时候看到过这种东西,可以关联到class,于是dump了一个试试,发现不行,果然是匿名的啊
突然想起,既然是class,那么从dex里面可以弄出来啊,于是乎折腾一番,从dex里面提取class,反编译出来,发现是这玩意:
class QZoneFriendFeedActivity?$4 extends CompoundFriendFeedFragment?
{ QZoneFriendFeedActivity?$4(QZoneFriendFeedActivity?paramQZoneFriendFeedActivity)
{
}
public Context getAppContext()
{
return this.this$0;
}
}

这孩子果然没有构造函数啊!,而且由于该匿名内部类是继承自其它类,所以有了一个以他包含类为参数的构造函数,而fragment恢复的时候又用了反射去实例化fragment,牛头不对马嘴,然后就被这东西给坑了。

这是4.2新出现的,改动就是: 
mCurrentFeedFragment = new CompoundFriendFeedFragment?();
||
||
V
mCurrentFeedFragment = new CompoundFriendFeedFragment?(){
@Override
public Context getAppContext() {
return QZoneFriendFeedActivity?.this;
}
};
这样就把一个好端端的内部成员对象改弄成一个匿名内部类对象 
匿名内部类在实现接口和回调的时候是好东西,但是对于有反射的情况下,要记住,android的反射无处不在,要千万小心,搞不好就崩了。

解决:改掉这个匿名类,用其他方式来传递activity

总结: 一方面要对使用的系统方法要深入了解,另外一方面要对java的潜规则使用准确无误,方能万无一失。

 

posted on 2016-05-26 10:34  冰幻孤影  阅读(667)  评论(0编辑  收藏  举报