Android高效绘图-平移缩放卡顿

Android高效绘图-平移缩放卡顿

本人新手一枚,第一次写博客,写的不好请勿喷,困扰小弟很久的问题,今天终于解决,这里奉上,供小伙伴借鉴,后面我会奉上从基础手指绘图到平移缩放所有的实现过程以及走过的坑,言归正传,下面开始列出我寻找卡顿元凶之路.

  • 采用双重画布的方式进行缓存—-无明显效果

  • 修改绘图框架,减少绘图中的大量计算,优化了数据结构—并无作用

  • 对象池的使用,避免重绘方法OnDraw中创建大量的对象(Path)—貌似没什么改变

  • 各种分析过后知道了应该是内存抖动,垃圾回收机制造成的卡顿,开始尝试对象的软引用—然而也无作用

  • 崩溃了的同时,放弃一段时间,但不管是坐车还是吃饭还是睡觉前,都会在想是什么鬼东西呢,边想着边做着其他功能,但是越做越觉得这个卡顿问题一天不解决,一天心情就不好了

  • 最终在百谷了很久无果后,开始觉得对APP内存的使用检测,开始学习,应该会找到答案

  • 功夫不负有心人,解决了~

  • 不卖关子了,开始奉上解决方式,其实很简单,我只是新手,觉得很low的请左上角返回,不要喷我.


使用工具Eclipse DDMS


  • Heap分析

  • Allocation Tracker 分析


onDraw()代码块

所有绘制的对象,例如:画直线,弧线,矩形,填充,等等.需要绘制的不可预知多少的的对象,所以这里不可避免需要重绘,循环遍历对象的绘制方法,这里也是出现问题的根源,重绘的时候会调用每个对象的绘图方法,方法中一定会创建一些对象,比如:new Path();当然卡顿他不是最终凶手,不过后面还需要对他进行使用对象池,避免创建过多的path对象.
    @Override
    protected void onDraw(Canvas canvas){
        super.onDraw(canvas);
        mCanvas = canvas;
        try {
            lFHouse.setBacgroundColor(this, paint_region_bag);

            for(LFWall wall : lFHouse.houseWalls){
               wall.doDraw(this, mCanvas)
            }
            for(LFWall wall : lFHouse.houseWalls){
                for (LFDoor door : wall.walldoors) {
                    door.onDraw(this, mCanvas);
               }
            }   
        } catch (Exception e) {
            e.printStackTrace();
        } 
    }

第一步

  运行程序,切换到DDMS视图 如题所示操作

这里写图片描述


第二步

 操作程序有卡顿的地方几次,手动点击Cause GC 如图

这里写图片描述


第三步

分析一下,多次操作,多次Cause GC,注意看free中的Total Size 大小,如果抖动的比较大,但是每次都能降低,反复增大,减少,说明存在了内存的抖动,即当前操作存在短时间内创建大量的对象,占用的内存,唤起了GC短时间内快速去回收无用的对象,这个回收的时间内,程序的其他操作是需要等待GC完成才可以继续工作的,所有就出现了卡慢,对象太多,gc的遍历回收工作占用的时间就过长了.可以通过data object的数量可以清楚的看到内存的剧增和剧减的情况.
通过以上分析,我们知道了程序卡顿的具体原因,下面就需要找出是哪些对象占用了呢,找出他必须弄死他.

第四步

打开Allocation Tracker视图,这个家伙很重要,能追踪查看内存到底让哪个小崽子占用了,还可以定位到包名,类名,代码行数,厉害了,我才会使用..,忏愧啊.勿喷小伙伴门.如图所示:
这里写图片描述


第五步

这里写图片描述
这里写图片描述
这里说明一下 ,由于我优化了程序,查找元凶的现场,已被我清理,所以这里看不到那些真正的元凶.下面我列出元凶,以供小伙伴借鉴.

揭露卡顿元凶

元凶1:
//这个方法是用来做精度转换的 保留几位小数
public static float round(float v, int scale) {
        if (scale < 0) {
            throw new IllegalArgumentException("The scale must be a positive integer or zero");
        }
        //重点是下面这段代码
        BigDecimal b = new BigDecimal(Float.toString(v));
        BigDecimal one = new BigDecimal("1");
        return b.divide(one, scale, BigDecimal.ROUND_HALF_UP).floatValue();
    }
  1.BigDecimal 对象占用的资源BigInteger通过追踪内存占用显示,这里就不贴图了.
这里解释下为什么会,由于我这个方法,在画线的时候对于每个点的x,y都进行了精度转换处理,又因为在平移缩放的时候我们是需要不断重绘的,也就是不断执行onDraw()这个系统方法,那么在这里我循环调用重绘线条,每一个点的x,y都需要重新计算并转换,造成了不断new BigDecimal(). 这个内部机制我还需要去底层源码去看下,现在只知道是这个东西占用了不少的内存,每new 一个好像是占用了144
  2. Float.toString(v)这个转换也是占用了很大资源StringBuilder 具体底层小弟也是不能深入解释了
  3. .floatValue() 这个也是占用了很多 char[] 数组.
元凶2:
/*
     * 屏幕的宽
     */
    public static int getScreenWidth(Activity activity){
        DisplayMetrics dm = new DisplayMetrics();
        dm = activity.getResources().getDisplayMetrics();
        activity.getWindowManager().getDefaultDisplay().getMetrics(dm);
       return dm.widthPixels ;
   }

    /*
     * 屏幕的高
     */
   public static float getScreenHeigth(Activity activity){
        DisplayMetrics dm = new DisplayMetrics();
        dm = activity.getResources().getDisplayMetrics();
        activity.getWindowManager().getDefaultDisplay().getMetrics(dm);
       return dm.heightPixels ;
   }

这里贴出的获取屏幕的宽高代码是 很正常的方法,看起来没什么特别.在看下面:

//该方法是我用于真实坐标点与屏幕坐标点的一个转换 
public LFPoint GetScreenPoint(LFPoint Point) {
        float X = (float) (Zoom * DotsPerMeter * Point.x + Origin.x);
        float Y = (float) (DisplayUtil.getScreenHeigth(drawActivity) - Zoom * DotsPerMeter * Point.y + Origin.y);
        //X = DataUtils.round(X, 2);
        //Y = DataUtils.round(Y, 2);
        LFPoint p = new LFPoint(X, Y, Point.bulge);
        return p;
    }
重点就是 重绘的时候每个点都要不断的进行转换,也就是在onDraw中不断的随手指的移动和缩放进行转换...看到了吗 下面有调用到获取屏幕的静态方法.就是它,不断的在获取,获取......导致了占用大量的资源 貌似也是每个144.我们继续...找

到这里,我们的程序的卡顿情况,被我们清理的差不多了,终于不会卡了,瞬间感觉整个人都飘了,原来自己给自己挖了那么多的坑,小弟真实惭愧,网上的代码段固然方便,但是不能一味的直接拿来用,其中很多不知道的东西,你往往忽略了的地方就是问题存在的地方,当你无计可施的时候,还需要坚持 一步步探索,找寻答案.
顺便再啰嗦一句,onDraw中不可避免的是创建大量的path对象,这里根据网上搜寻的结果,总结了下还是用对象池比较好,但是网上有两个版本,一个是可以任何对象的,一个是只有自定义的类对象才可以,系统Path对象好像不行,所以简单的做了一下改动,
对,就是自定义path, 我啥都没改 仅仅是extend了一下,然后套用了下面的对象池的初始化方法,类似Handler的获取Message的时候的方式一样,不知道对不对,贴出代码,参考一下,反正我试过,可用.

public class MyPath extends Path {

    private static final SynchronizedPool<MyPath> sPool = new SynchronizedPool<MyPath>(10);

    public static MyPath obtain() {
        MyPath instance = sPool.acquire();
        return (instance != null) ? instance : new MyPath();
    }

    public void recycle() {
        sPool.release(this);
    }
}

好了,以上的寻找卡顿元凶之路,终于告一段落,还有很多地方需要优化的,后面再继续奉上,感谢看完的小伙伴,忍不住发一篇,希望能帮到你,后面我会继续更新出绘图相关的操作,比如画布的,比如view的,还有缩放,平移的实现,这些你是百谷不到的,那些基础的东西我看过很多,真的是千篇一律的.针对的平移缩放都是图片的操作而不是坐标点.
下次再见~有高见欢迎点评,虚心接受.

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值