我和另一个模块做了交互,用反射实现的依赖注入
1.这过程中由于模块不同,编译隔离,我只能通过debug来发现问题
2.我们对方案产生了分歧,我进入我们的完成页,需要调接口,然后才会去模块交互,另一模块又会调接口,同时展示loading。但是在我request的时候没有loading,或者我可以自己loading,但是需要和他们的loading统一,这维护不便,最终我在讨论的时候,提出了打2次信号的方式,第一次进页面,直接远程调用,展示loading,这个loading吞掉了我和别人模块的两次请求架加在一起的时间。我为此还很高兴,很好地符合了产品的口味,感觉自己又是技术又是产品了
没成想,最后大家修改了需求,远程request的数据基础,不需要基于我们request的结果,直接就可以同时请求。
3.这不是最难受的,最难受的是出现了个匪夷所思的bug。他们那个模块一开始是呼吸动画的loading,然后数据加载出来后,呼吸loading没有消失。但是我跑到他们的小组,发现他们本地调一点事情都没有。所以我和那位老哥苦思冥想啊?线程问题?当然不是,已经切换到主线程了;反射问题?当然不是;机型问题?我这里所有机型都不行啊。反正想了许多办法。
难道是动画的时候,gone无效?可是他们那里为什么可以?
要不改一下,cancel一下试一试?然后他们模块打个class bundle上传我们再跑一次?成本太大了吧?万一不成,真的血崩了!所以说模块交互一旦出现bug真是太恐怖了,调试是非常非常非常之恐怖的~!
最后我回去思考了30分钟左右,想到,他的view的添加是这样的
new Callback {
void call(Object o) {
remove all
add o
}
}
我这里是
new Callback {
void call(Object o) {
if child == 0
add o
}
}
这是我们唯一的区别,那问题肯定就出在这里。
后来沟通了下 很快解决了
自己后来为了探究原理做了测试
oncreate中
View v = findViewById(R.id.v); v.setAnimation(BreathAnimalFactory.createBreathAnimal(null)); v.setVisibility(View.GONE);
另一种写法
final View v = findViewById(R.id.v); v.setAnimation(BreathAnimalFactory.createBreathAnimal(null)); getWindow().getDecorView().post(new Runnable() { @Override public void run() { v.setVisibility(View.GONE); } });
log了下,v是gone了,但是宽高还在,就证明没有走到measure或者measure操作被某些标记位阻止了真正的measure,绕过了真正要measure的代码块
想了下,没有走到measure的可能性更大。可能是一直在invalidate,把request layout给无限延后了
final View v = findViewById(R.id.v); v.setAnimation(BreathAnimalFactory.createBreathAnimal(null)); getWindow().getDecorView().post(new Runnable() { @Override public void run() { v.setVisibility(View.GONE); v.post(new Runnable() { @Override public void run() { v.setAnimation(null); } }); } });
这样写,就又没了
防止setAnimation可能引起重绘,反射清除动画字段
final View v = findViewById(R.id.v); v.setAnimation(BreathAnimalFactory.createBreathAnimal(null)); getWindow().getDecorView().post(new Runnable() { @Override public void run() { v.setVisibility(View.GONE); v.post(new Runnable() { @Override public void run() { Field field = null; try { field = View.class.getDeclaredField("mCurrentAnimation"); field.setAccessible(true); field.set(v, null); } catch (NoSuchFieldException e) { e.printStackTrace(); } catch (IllegalAccessException e) { e.printStackTrace(); } } }); } });
果然也没了,所以绘制的时候,会检测mCurrentAnimation这个字段
最后了解到了Choreographer
这里讨论VSync mode,VSync大概16ms打一次信号,Choreographer中维护3条队列,事件,动画,正常绘制,post_draw,每16s这个代码块都会被执行
doCallbacks(Choreographer.CALLBACK_INPUT, frameTimeNanos); mFrameInfo.markAnimationsStart(); doCallbacks(Choreographer.CALLBACK_ANIMATION, frameTimeNanos); mFrameInfo.markPerformTraversalsStart(); doCallbacks(Choreographer.CALLBACK_TRAVERSAL, frameTimeNanos); doCallbacks(Choreographer.CALLBACK_COMMIT, frameTimeNanos);
那么我们的动画信号,在哪个地方不断地打给Choreographer呢?
if (more && hardwareAcceleratedCanvas) { if (a.hasAlpha() && (mPrivateFlags & PFLAG_ALPHA_SET) == PFLAG_ALPHA_SET) { // alpha animations should cause the child to recreate its display list invalidate(true); } }
这个是view的某个draw方法,被父类调用,用来绘制child自身。这里的代码的意思是,如果是alpha动画,那就调用invalidate。
而这个代码之前的工作就是applyTransformation-进行计算值,然后绘制。
所以Animation类之所以可以一直运作,是因为他在不断地invalidate。
先考虑下这个问题,view一开始就gone或者后期转变为gone的时候,为什么就没有宽没有高了?
看一看view group的方法
protected void measureChildren(int widthMeasureSpec, int heightMeasureSpec) { final int size = mChildrenCount; final View[] children = mChildren; for (int i = 0; i < size; ++i) { final View child = children[i]; if ((child.mViewFlags & VISIBILITY_MASK) != GONE) { measureChild(child, widthMeasureSpec, heightMeasureSpec); } } }
显然,如果是GONE,child就不会被measure
1.一开始就是gone,那么自然不会被测绘
2.前期visible,后期设置为gone,那么会request layout,强行重新进行measure、layout。draw是自然进入的。然后在measure、layout中会分别设置measure height/width,height/width。当然了,measure、layout gone掉的view不会进入,所以可以猜到在这些步骤之前,有一个reset的操作
关键在于找到这个reset的操作
然后我发现被我自己坑了,因为我翻遍了源码,也只找到了
if(child visibility!= GONE) {
child.measure
}
那岂不是在第一次未gone,后来gone的情况下,measure height/width,height/width都是脏数据了?
我打印了日志发现,果然真的都是脏数据。
剩下最后一条线索,mCurrentAnimation。
不管是set animation(null)
还是反射改掉这个标记位
都会成功gone掉
所以需要找到这个判断这个标记位的地方,或者这个标记位改变了父的属性,的判断的地方
最后发现在view group的dispatch draw里
if ((transientChild.mViewFlags & VISIBILITY_MASK) == VISIBLE || transientChild.getAnimation() != null) { more |= drawChild(canvas, transientChild, drawingTime); }
可见或者有动画,view的上一层的父容器view group会允许draw这个孩子。
这样一来都理清了。dispatch draw是view group用来绘制孩子的方法。
虽然我们gone了,但是有动画,所以自身这个处在动画中的view的绘制依然被调用了,所以依然是可见的!依然被绘制出来了!这就是无法gone掉的秘密。
同时我之前还有疑虑,到底是在哪个地方clear原来所有的像素点呢?我只能猜想,在child确定了draw之后,正式draw之前,child区域的像素点会被clear,或者统一计算和父容器重合的地方clear。这就是gone的实现原理。上面这个判断就是动画的时候gone不掉的原因。
之所以动画的时候gone不掉,还有measure、layout的松懈。measure根本不处理gone情况,导致后来才设置的gone,会留下脏的measured width/height数据。layout也差不多,直接不处理gone,留下脏的width/height数据。
至于我上面瞎猜的Choreographer、sync barrier更是无稽之谈。因为Animation这个类在Choreographer对应的callback是traversal而不是animate;sync barrier很快就释放了,几乎无影响;postcallback里invalidate、requestLayout post的是同一个callback traversal。