浮点数引发的Canvas绘制血案

Posted everlastxgb

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了浮点数引发的Canvas绘制血案相关的知识,希望对你有一定的参考价值。

浮点数引发的Canvas绘制血案


今天在android项目开发中遇到一个比较有趣的奔溃问题,感觉也好久没有写文章了,觉得可以跟大家分享一下的。这个问题涉及到浮点数计算、View绘制流程和机制,理清楚后发现问题其实很简单。

1.案发现场回顾

1.1 问题描述

某同学通过外部跳转直接进入WindowA(底部4个tab)的第4个tab的时候打开了WindowB,在WindowB中进行了横竖屏切换,此时返回了WindowA,切换到第1个tab后,发现app卡死之后闪退。

1.2 问题分析

1.2.1 下面简单拆解一下其实现:

  • WindowA中4个tab对应的View通过设置visibility(GONE/VISIBLE)切换。
  • WindowA针对横竖屏切换做了监听,更改了Tab1中某些View的大小和位置并触发重绘制。
  • WindowA中初始时候四个Tab都是GONE,直接进入Tab4的时候这时候只有Tab4是VISIBLE。
  • 从WindowB回来后只有点击Tab1才会触发奔溃。
  • 点击Tab1之后只做了一个处理,那就是切换其Visibility为VISIBLE。

??????为什么仅仅设置了一个View的Visibility就会导致闪退呢??????
??????为什么闪退的时候看不到有奔溃日志??????

1.2.2 部分关键代码简要回顾:

  • WindowA中Tab1针对横竖的监听处理代码如下(仅示例):
protected void onConfigurationChanged(Configuration newConfig) 
    //...
    int width = mRecycleViewPager.getWidth();//mRecycleViewPager为Tab1中的View
    float scaleRateLeft = SCALE_RATE * (1.0f - Math.abs(leftScrollX * 2f / width));
    mCurrentView.setScaleRate(scaleRateCenter);//mCurrentView为Tab1中的View
    //...
  • Tab1中mCurrentView.setScaleRate的实现代码如下(仅示例):

    public void setScaleRate(float scaleRate) 
        mScaleRate = scaleRate;
        invalidate();
    
    // ...
    @Override
    protected void dispatchDraw(Canvas canvas) 
        if (mIsNeedTranslate) 
            canvas.save();
            canvas.translate(mDeltaScrollX, 0);
            if (mScaleRate != 1.0f) 
                canvas.scale(mScaleRate, mScaleRate, getWidth()/2f, getHeight()/2f);
            
            super.dispatchDraw(canvas);
            canvas.restore();
            if (mDeltaScrollX == 0) 
                mIsNeedTranslate = false;
            
         else 
            super.dispatchDraw(canvas);
        
    

2.问题分析和定位

或许很多人可能一看代码就能很清楚明了发现问题了,不过下面还是容我分析一般。

2.1 首先,从onConfigurationChanged出发看代码:

  • mRecycleViewPager.getWidth(); //Tab1初始化为GONE,这里直接进入Tab4,此处getWidth为0。

  • leftScrollX * 2f / width; //这里除以width,0的时候抛异常?

那么,问题是否是因为getWidth()==0导致除的时候抛异常能?
答案肯定是否定的,如果除的时候抛异常,那么横竖屏切换的时候就奔溃了,而不是等到Tab1的setVisibility才奔溃。

这里就牵扯出一个关于浮点数计算的问题了:浮点数计算的时候,此处除以0,事实上得到的结果是一个正无穷或者负无穷。

所以,并不是除以0导致的异常。(其实虽然不会异常但得到一个正无穷或者负无穷的值,之后在使用的时候肯定也会有问题)

2.2 接着,从mCurrentView.setScaleRate出发看代码:

  • setScaleRate会触发invalidata
  • dispatchDraw中canvas.scale(mScaleRate, mScaleRate, getWidth()/2f, getHeight()/2f);

其实,可以发现,当mScaleRate为无穷的时候,这个语句在canvas绘制的肯定会出问题。
但是,为什么横竖屏切换的时候明明就已经触发了invalidate但是并没有卡死奔溃?

这里就牵扯出View绘制机制的问题了:当视图不可见(GONE)的时候调用invalidate是不会触发Draw的。

所以,等到Tab1切回了可视(VISIBLE)重绘的时候才会跑到dispatchDraw,这个时候canvas.scale处理一个无穷大的值,你说会不会有问题?

3. 问题总结

1.横竖屏切换的时候给View设置了一个非法数值(无穷大)。
2.切tab触发View的Draw的时候使用了这个非法数值进行了canvas绘制。

4. 问题解决方法

  1. Tab1不可见的时候不监听处理onConfigurationChanged。
  2. 当getWidth为0的时候不应该做下一步处理。
  3. dispatchDraw中对mScaleRate做非法值校验。

5. 结语

其实应该也算是一个低级问题,不过这个低级问题下面也牵扯到一些高级知识。虽然这边文章写得云里雾里,不过总结一句话:问题都是可以解决,解决问题的同时要深究根源并从中总结知识。

最后,如果觉得我阐述的不够详细的,欢迎补充。

以上是关于浮点数引发的Canvas绘制血案的主要内容,如果未能解决你的问题,请参考以下文章

浮点数的那点事

转换为双精度浮点数或浮点数是不是保留无穷大和 NaN?

异常处理动手动脑

[疯狂Java讲义] 数据类型

HTML5之canvas 6 绘制渐变图形

浮点数剖析