美文网首页
浮点数引发的Canvas绘制血案

浮点数引发的Canvas绘制血案

作者: everlastxgb | 来源:发表于2017-08-12 18:39 被阅读56次

    浮点数引发的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绘制血案

          本文链接:https://www.haomeiwen.com/subject/ckyjrxtx.html