Android 2D动画绘图:性能不佳

时间:2012-09-29 07:09:26

标签: android-animation

我有一个应用程序,绘制一个点网格(让我们说5x5)。要求用户在该网格上绘制线条。如果用户的手指触摸网格中的一个点,则该点被着色以显示该点是所绘制的路径的一部分。此外,将在每两个触摸点之间绘制一条线。

问题 - 我的表现非常差,导致很少的事情:

  1. 应用程序变得非常慢。
  2. event.getAction()中的动作事件的粒度不佳。我的意思是enter code here例如,它不是每10个像素注册一个运动,而是每100个像素记录一次运动。反过来,这将导致应用程序不重绘用户触摸的一些点。
  3. 有时运动坐标很简单:假设用户将手指从像素100移动到像素500,读数可能会显示100 ... 200 ... 150 ... 140 ... 300 ...... 400。出于某种原因,触摸位置在某些情况下会搞砸。
  4. 看一下应用程序"如何错过"在用户触摸的点上并且没有绘制绿点:

    Showing two lines - one is missing a dot. Both lines were drawn from top to bottom, the left one was drawn first

    我尝试了一些事情:

    1. 添加Thread.sleep(100);在else if(event.getAction() == MotionEvent.ACTION_MOVE)onTouchEvent(MotionEvent event),我读到这可能会让CPU有时间赶上所有触摸事件 - 没有改变一件事
    2. this.destroyDrawingCache()添加到doDraw()的最后(我使用它代替onDraw,正如我使用的一个教程所建议的那样)。我认为这将清除所有事件/绘图缓存,这似乎正在减慢系统速度 - 没有改变一件事。
    3. 我对Android动画相当新,所以我不知道如何继续:

      1. 我知道我应该在doDraw()(我的onDraw())和onTouchEvent()中尽可能少地做。
      2. 我读了一些关于invalidate()的内容,但不知道如何以及何时使用它。如果我理解正确,每次调用doDraw()时我的视图都会被重新绘制。例如,我的网格是静态的 - 我怎么能避免重绘它呢?
      3. ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ +

        我尝试使用canvas.drawCircle(xPos, yPos, 8, mNodePaint);代替canvas.drawBitmap(mBitmap, xPos, yPos, null);。我认为,如果我使用实际的位图,这可能会提高性能。事实上 - 它没有!我有点困惑,这样一个简单的应用程序如何在设备上造成如此沉重的负担。我一定是做错了事。

        +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ +

        我考虑了@LadyWoodi建议的内容 - 我已经从循环中删除了所有变量声明 - 无论如何这是一个不好的做法,我也摆脱了所有的" System.Out"我使用的行,所以我可以记录应用程序行为,以更好地理解为什么我得到这样一个蹩脚的性能我很伤心地说,如果有性能上的变化(我不'吨实际测量,帧速率变更),它是可以忽略不计。

        还有其他想法吗?

        +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ +

        1. 由于我有一个静态网格点(请参见screenShot中的空心黑/白点),在游戏中我从未改变过,我做了以下工作:
        2. - 绘制一次网格。

          使用Bitmap.createBitmap()将图形作为位图。

          - 使用canvas.drawBitmap()绘制静态点网格的位图。

          - 当我的线程运行时,我检查它是否绘制了网格点。如果它正在运行,我将不会重新创建静态点网格。我只会从之前渲染的位图渲染它。

          令人惊讶的是,这对我的表现没有任何改变!每次重绘点网格都不会对应用程序性能产生真正的视觉效果。

          1. 我决定在绘图线程中使用canvas = mHolder.lockCanvas(new Rect(50, 50, 150, 150));。只是为了测试目的,看看我是否限制每次渲染的区域,我可以更好地获得性能。这也没有帮助。

          2. 然后我转向Eclipse中的DDMS工具尝试分析应用程序。它的结果是,canvas.drawPath(path, mPathPaint);(Canvas.native_drawPath)消耗了大约88.5%的CPU时间!!!

          3. 但为什么??!我的路径绘图相当简单,mGraphics包含一系列路径,我所做的就是弄清楚每条路径是否在游戏画面的边界内,然后我画一条路径:

            //draw path user is creating with her finger on screen
                    for (Path path : mGraphics)
                    {           
                        //get path values       
                        mPm = new PathMeasure(path, true);
            
                        mPm.getPosTan(0f, mStartCoordinates, null); 
                        //System.out.println("aStartCoordinates X:" + aStartCoordinates[0] + " aStartCoordinates Y:" + aStartCoordinates[1]);
                        mPm.getPosTan(mPm.getLength(), mEndCoordinates, null);
                        //System.out.println("aEndCoordinates X:" + aEndCoordinates[0] + " aEndCoordinates Y:" + aEndCoordinates[1]);
            
                        //coordinates are within game board boundaries
                        if((mStartCoordinates[0] >= 1  && mStartCoordinates[1] >= 1) && (mEndCoordinates[0] >= 1 && mEndCoordinates[1] >= 1))
                        {
                            canvas.drawPath(path, mPathPaint);
                        }                   
                    }
            

            在我的示例中,任何人都可以看到任何编程错误的代码行吗?

            +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ +

            我已对doDraw()方法进行了更改。基本上我所做的只是在某些事情发生变化的情况下画画。在所有其他情况下,我只是存储屏幕的缓存位图并进行渲染。请看一下:

                public void doDraw(Canvas canvas) 
            {   
                synchronized (mViewThread.getSurefaceHolder()) 
                {           
                    if(mGraphics.size() > mPathsCount)
                    {
                        mPathsCount = mGraphics.size(); 
            
                        //draw path user is creating with her finger on screen
                        for (Path path : mGraphics)
                        {           
                            //get path values       
                            mPm = new PathMeasure(path, true);
            
                            mPm.getPosTan(0f, mStartCoordinates, null); 
                            //System.out.println("aStartCoordinates X:" + aStartCoordinates[0] + " aStartCoordinates Y:" + aStartCoordinates[1]);
                            mPm.getPosTan(mPm.getLength(), mEndCoordinates, null);
                            //System.out.println("aEndCoordinates X:" + aEndCoordinates[0] + " aEndCoordinates Y:" + aEndCoordinates[1]);
            
                            //coordinates are within game board boundaries
                            if((mStartCoordinates[0] >= 1  && mStartCoordinates[1] >= 1) && (mEndCoordinates[0] >= 1 && mEndCoordinates[1] >= 1))
                            {
                                canvas.drawPath(path, mPathPaint);
                            }                   
                        }
            
                        //nodes that the path goes through, are repainted green
                        //these nodes are building the drawn pattern
                        for (ArrayList<PathPoint> nodePattern : mNodesHitPatterns) 
                        {
                            for (PathPoint nodeHit : nodePattern) 
                            {
                                canvas.drawBitmap(mDotOK, nodeHit.x - ((mDotOK.getWidth()/2) - (mNodeBitmap.getWidth()/2)), nodeHit.y - ((mDotOK.getHeight()/2) - (mNodeBitmap.getHeight()/2)), null);
                            }                   
                        }
            
                        mGameField = Bitmap.createBitmap(mGridNodesCount * mNodeGap, mGridNodesCount * mNodeGap, Bitmap.Config.ARGB_8888);
                    }
                    else
                    {
                        canvas.drawBitmap(mGameField, 0f, 0f, null);
                    }
            

            现在结果 - 只要设备不必渲染任何路径并且只是从位图绘制,东西就会非常快。但是,当我不得不使用canvas.drawPath()性能重新渲染屏幕的那一刻变得像在吗啡上的乌龟一样缓慢......我拥有的路径越多(最多6个或更多,这没有!)渲染速度越慢。这有多奇怪? - 我的路径甚至不是很弯曲 - 都是偶然转弯的直线。我的意思是这条线不是很复杂&#34;。

            我在下面添加了更多代码 - 如果您有任何改进建议。

            非常感谢, D.

            ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~&#34; Panel&#34; ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

            public class Panel extends SurfaceView implements SurfaceHolder.Callback {
            
            Bitmap mNodeBitmap; 
            int mNodeBitmapWidthCenter;
            int mNodeBitmapHeightCenter;
            
            Bitmap mDotOK;
            ViewThread mViewThread;
            ArrayList<PathPoint> mPathPoints;
            
            private ArrayList<Path> mGraphics = new ArrayList<Path>(3);
            private ArrayList<ArrayList<PathPoint>> mNodesHitPatterns = new ArrayList<ArrayList<PathPoint>>();
            
            private Paint mPathPaint;
            
            Path mPath = new Path();
            
            //private ArrayList<Point> mNodeCoordinates = new ArrayList<Point>();
            private int mGridNodesCount = 5;
            private int mNodeGap = 100;
            PathPoint mNodeCoordinates[][] = new PathPoint[mGridNodesCount][mGridNodesCount];
            
            PathMeasure mPm;
            float mStartCoordinates[] = {0f, 0f};
            float mEndCoordinates[] = {0f, 0f};
            
            PathPoint mPathPoint;
            
            Boolean mNodesGridDrawn = false;
            
            Bitmap mGameField = null;
            
            public Boolean getNodesGridDrawn() {
                return mNodesGridDrawn;
            }
            
            public Panel(Context context) {
                super(context);     
            
                mNodeBitmap = BitmapFactory.decodeResource(getResources(), R.drawable.dot);
            
                mNodeBitmapWidthCenter =  mNodeBitmap.getWidth()/2;
                mNodeBitmapHeightCenter =  mNodeBitmap.getHeight()/2;
            
                mDotOK = BitmapFactory.decodeResource(getResources(), R.drawable.dot_ok);
            
                getHolder().addCallback(this);
                mViewThread = new ViewThread(this);     
            
                mPathPaint = new Paint();
                mPathPaint.setAntiAlias(true);
                mPathPaint.setDither(true); //for better color
                mPathPaint.setColor(0xFFFFFF00);
                mPathPaint.setStyle(Paint.Style.STROKE);
                mPathPaint.setStrokeJoin(Paint.Join.ROUND);
                mPathPaint.setStrokeCap(Paint.Cap.ROUND);
                mPathPaint.setStrokeWidth(5);
            }
            
            public ArrayList<ArrayList<PathPoint>> getNodesHitPatterns()
            {
                return this.mNodesHitPatterns;
            }
            
            public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {
            
            
            }
            
            public void surfaceCreated(SurfaceHolder holder) {
            
                //setPadding(100, 100, 0, 0);       
            
                if (!mViewThread.isAlive()) {
                    mViewThread = new ViewThread(this);
                    mViewThread.setRunning(true);
                    mViewThread.start();
                }
            }
            
            public void surfaceDestroyed(SurfaceHolder holder) {
            
                if (mViewThread.isAlive()) {
                    mViewThread.setRunning(false);
                }
            }
            
            //draw the basic nodes grid that the user will use to draw the lines on
            //store as bitmap
            public void drawNodesGrid(Canvas canvas)
            {
                canvas.drawColor(Color.WHITE);
            
                for (int i = 0; i < mGridNodesCount; i++) 
                {
                    for (int j = 0; j < mGridNodesCount; j++) 
                    {
                        int xPos = j * mNodeGap;
                        int yPos = i * mNodeGap;
            
                        try 
                        {
                            //TODO - changed
                            mNodeCoordinates[i][j] = new PathPoint(xPos, yPos, null);
                        } 
                        catch (Exception e) 
                        {
                            e.printStackTrace();
                        }   
            
                        canvas.drawBitmap(mNodeBitmap, xPos, yPos, null);
                    }
                }
            
                mNodesGridDrawn = true;
            
                mGameField = Bitmap.createBitmap(mGridNodesCount * mNodeGap, mGridNodesCount * mNodeGap, Bitmap.Config.ARGB_8888);
            }
            
            public void doDraw(Canvas canvas) 
            {       
                canvas.drawBitmap(mGameField, 0f, 0f, null);
            
                synchronized (mViewThread.getSurefaceHolder()) 
                {
                    //draw path user is creating with her finger on screen
                    for (Path path : mGraphics)
                    {           
                        //get path values       
                        mPm = new PathMeasure(path, true);
            
                        mPm.getPosTan(0f, mStartCoordinates, null); 
                        //System.out.println("aStartCoordinates X:" + aStartCoordinates[0] + " aStartCoordinates Y:" + aStartCoordinates[1]);
                        mPm.getPosTan(mPm.getLength(), mEndCoordinates, null);
                        //System.out.println("aEndCoordinates X:" + aEndCoordinates[0] + " aEndCoordinates Y:" + aEndCoordinates[1]);
            
                        //coordinates are within game board boundaries
                        if((mStartCoordinates[0] >= 1  && mStartCoordinates[1] >= 1) && (mEndCoordinates[0] >= 1 && mEndCoordinates[1] >= 1))
                        {
                            canvas.drawPath(path, mPathPaint);
                        }                   
                    }
            
                    //nodes that the path goes through, are repainted green
                    //these nodes are building the drawn pattern
                    for (ArrayList<PathPoint> nodePattern : mNodesHitPatterns) 
                    {
                        for (PathPoint nodeHit : nodePattern) 
                        {
                            canvas.drawBitmap(mDotOK, nodeHit.x - ((mDotOK.getWidth()/2) - (mNodeBitmap.getWidth()/2)), nodeHit.y - ((mDotOK.getHeight()/2) - (mNodeBitmap.getHeight()/2)), null);
                        }                   
                    }
            
                    this.destroyDrawingCache();
                }
            }
            
            @Override
            public boolean onTouchEvent(MotionEvent event) {
                synchronized (mViewThread.getSurefaceHolder()) {
            
                    if(event.getAction() == MotionEvent.ACTION_DOWN)
                    {
                        //System.out.println("Action downE x: " + event.getX() + " y: " + event.getY());
            
                        for (int i = 0; i < mGridNodesCount; i++) 
                        {                   
                            for (int j = 0; j < mGridNodesCount; j++) 
                            {
                                //TODO - changed
                                //PathPoint pathPoint = mNodeCoordinates[i][j];
            
                                mPathPoint = mNodeCoordinates[i][j];
            
                                if((Math.abs((int)event.getX() - mPathPoint.x) <= 35) && (Math.abs((int)event.getY() - mPathPoint.y) <= 35))
                                {
                                    //mPath.moveTo(pathPoint.x + mBitmap.getWidth() / 2, pathPoint.y + mBitmap.getHeight() / 2);
            
                                    //System.out.println("Action down x: " + pathPoint.x + " y: " + pathPoint.y);
                                    ArrayList<PathPoint> newNodesPattern = new ArrayList<PathPoint>();
                                    mNodesHitPatterns.add(newNodesPattern);
                                    //mNodesHitPatterns.add(nh);
                                    //pathPoint.setAction("down");
                                    break;
                                }
                            }
                        }                   
                    }
                    else if(event.getAction() == MotionEvent.ACTION_MOVE)
                    {                           
                        final int historySize = event.getHistorySize();
                        //System.out.println("historySize: " + historySize);
                        //System.out.println("Action moveE x: " + event.getX() + " y: " + event.getY());
            
                        coordinateFound:
                            for (int i = 0; i < mGridNodesCount; i++) 
                            {
                                for (int j = 0; j < mGridNodesCount; j++) 
                                {
                                    //TODO - changed
                                    //PathPoint pathPoint = mNodeCoordinates[i][j]; 
                                    mPathPoint = mNodeCoordinates[i][j];
            
                                    if((Math.abs((int)event.getX() - mPathPoint.x) <= 35) && (Math.abs((int)event.getY() - mPathPoint.y) <= 35))
                                    {                               
                                        int lastPatternIndex = mNodesHitPatterns.size()-1;                  
                                        ArrayList<PathPoint> lastPattern = mNodesHitPatterns.get(lastPatternIndex);
                                        int lastPatternLastNode = lastPattern.size()-1;                             
            
                                        if(lastPatternLastNode != -1)
                                        {
                                            if(!mPathPoint.equals(lastPattern.get(lastPatternLastNode).x, lastPattern.get(lastPatternLastNode).y))
                                            {
                                                lastPattern.add(mPathPoint);        
                                                //System.out.println("Action moveC [add point] x: " + pathPoint.x + " y: " + pathPoint.y);
                                            }
                                        }
                                        else
                                        {
                                            lastPattern.add(mPathPoint);
                                            //System.out.println("Action moveC [add point] x: " + pathPoint.x + " y: " + pathPoint.y);
                                        }                                           
            
                                        break coordinateFound;
                                    }
                                    else //no current match => try historical
                                    {
                                        if(historySize > 0)
                                        {
                                            for (int k = 0; k < historySize; k++)
                                            {
                                                //System.out.println("Action moveH x: " + event.getHistoricalX(k) + " y: " + event.getHistoricalY(k));
                                                if((Math.abs((int)event.getHistoricalX(k) - mPathPoint.x) <= 35) && (Math.abs((int)event.getHistoricalY(k) - mPathPoint.y) <= 35))
                                                {                                                   
                                                    int lastPatternIndex = mNodesHitPatterns.size()-1;                  
                                                    ArrayList<PathPoint> lastPattern = mNodesHitPatterns.get(lastPatternIndex);
                                                    int lastPatternLastNode = lastPattern.size()-1;                             
            
                                                    if(lastPatternLastNode != -1)
                                                    {
                                                        if(!mPathPoint.equals(lastPattern.get(lastPatternLastNode).x, lastPattern.get(lastPatternLastNode).y))
                                                        {
                                                            lastPattern.add(mPathPoint);
                                                            //System.out.println("Action moveH [add point] x: " + pathPoint.x + " y: " + pathPoint.y);
                                                        }
                                                    }
                                                    else
                                                    {
                                                        lastPattern.add(mPathPoint);
                                                        //System.out.println("Action moveH [add point] x: " + pathPoint.x + " y: " + pathPoint.y);
                                                    }                                           
            
                                                    break coordinateFound;
                                                }
                                            }
                                        }                               
                                    }
                                }
                            }       
                    }
                    else if(event.getAction() == MotionEvent.ACTION_UP)
                    {
                        //                          for (int i = 0; i < mGridSize; i++) {
                        //          
                        //                              for (int j = 0; j < mGridSize; j++) {
                        //          
                        //                                  PathPoint pathPoint = mNodeCoordinates[i][j];
                        //          
                        //                                  if((Math.abs((int)event.getX() - pathPoint.x) <= 35) && (Math.abs((int)event.getY() - pathPoint.y) <= 35))
                        //                                  {
                        //                                      //the location of the node                      
                        //                                      //mPath.lineTo(pathPoint.x + mBitmap.getWidth() / 2, pathPoint.y + mBitmap.getHeight() / 2);
                        //          
                        //                                      //System.out.println("Action up x: " + pathPoint.x + " y: " + pathPoint.y);
                        //          
                        //                                      //mGraphics.add(mPath);
                        //                                      //                          mNodesHit.add(pathPoint);
                        //                                      //                          pathPoint.setAction("up");
                        //                                      break;
                        //                                  }                       
                        //                              }
                        //                          }               
                    }
            
                    //System.out.println(mNodesHitPatterns.toString());
            
                    //create mPath
                    for (ArrayList<PathPoint> nodePattern : mNodesHitPatterns) 
                    {
                        for (int i = 0; i < nodePattern.size(); i++) 
                        {
                            if(i == 0) //first node in pattern
                            {
                                mPath.moveTo(nodePattern.get(i).x + mNodeBitmapWidthCenter, nodePattern.get(i).y + mNodeBitmapHeightCenter);
                            }
                            else
                            {
                                mPath.lineTo(nodePattern.get(i).x + mNodeBitmapWidthCenter, nodePattern.get(i).y + mNodeBitmapWidthCenter);
                            }
            
                            //mGraphics.add(mPath);
                        }                               
                    }
            
                    mGraphics.add(mPath);
            
                    return true;
                }  
            }
            

            }

            ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Class&#34; ViewThread&#34; ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

            public class ViewThread extends Thread {
            private Panel mPanel;
            private SurfaceHolder mHolder;
            private boolean mRun = false;
            
            public ViewThread(Panel panel) {
                mPanel = panel;
                mHolder = mPanel.getHolder();
            }
            
            public void setRunning(boolean run) {
                mRun = run;
            }
            
            public SurfaceHolder getSurefaceHolder()
            {       
                return mHolder;
            }
            
            @Override
            public void run() 
            {
                Canvas canvas = null;
                while (mRun) 
                {
                    canvas = mHolder.lockCanvas();
                    //canvas = mHolder.lockCanvas(new Rect(50, 50, 150, 150));
                    if (canvas != null) 
                    {                   
                        if(!mPanel.getNodesGridDrawn())
                        {
                            mPanel.drawNodesGrid(canvas);
                        }
            
                        mPanel.doDraw(canvas);
                        mHolder.unlockCanvasAndPost(canvas);                
                    }
                }
            }
            }
            

2 个答案:

答案 0 :(得分:1)

这只是想法,但我会尝试从循环中取出所有声明。我知道将它们本地化是有用的,但它通常非常耗时,所以它可以帮助一点。我的第二个想法已经在您的更新中经过测试,所以现在我也很好奇它将如何发展;)

答案 1 :(得分:0)

您使用的是SurfaceView吗?首先,我建议你为你的游戏使用一个图形库...例如,AndEngine非常易于使用,你可以实现比使用Java画布开发更漂亮的游戏。表现也更好。

我发现你的代码没有任何问题,但是在draw方法中有很多处理,更重要的是,在onTouch事件中。你应该避免在循环中使用除法或重数学运算,并尝试在之前预先计算所有内容。

但我坚持;对于你正在做的事情,看一下this,你就可以立刻开始运行它!