SurfaceView flickering/tearing

9.8k views Asked by At

I'm trying to figure out how to work around my problem. I've read http://groups.google.com/group/android-developers/browse_thread/thread/a2aac88a08cb56c2/b7dff4ba388cd664?lnk=gst&q=SurfaceView#b7dff4ba388cd664 which sort of answers my question but as far as I can tell, it's a sort of "tough luck" answer. So here's my problem:

I'm using SurfaceView in the normal way (lock/unlockAndPost) to draw a bitmap of my game background whenever the surface is changed (e.g. orientation etc) and I'm rendering a series of moving circles (up to 30 with a radius of about 25.f). The x,y data for the positions of these circles is coming from a server and that all runs fine. All circle objects are stored in a list and their position is updated taking care to ensure this update is synchronized. However, when I draw these circles to the screen (during canvas.lock() ), most of the time they render fine but occasionally (e.g. once every few seconds) some of the circles seem to tear or flicker briefly for a frame. The number of circles is always constant so that's not the problem and there's no concurrent modifications to the list of circles (as I said, it is synchronized). I have even tried drawing all these circles to a Bitmap on each render loop and drawing that bitmap to the main canvas. This just seems to affect the perfomance even more (~13FPS as opposed to ~30FPS when drawing circles directly to main canvas). Sorry if the information is a little vague guys, (trying to keep the company happy :p ) but I was just wondering if anyone could give me a clue? Or am I just out of luck. I should note that the positioning data coming from the server is realtime data and it is vitally important that the rendering reflects these realtime positions.

Thanks for any help! Chris

EDIT:

Fair enough. Here is the run() from the render thread.

@Override
    public void run() {
        Canvas c;
        while (mRun) {
            c = null;
            try {
                c = mSurfaceHolder.lockCanvas(null);
                synchronized (mSurfaceHolder) {
                    panel.onDraw(c);
                }
            } finally {
                if (c != null) {
                    mSurfaceHolder.unlockCanvasAndPost(c);
                }
            }
        }
    }

Pretty standard stuff (almost a carbon copy of lunar lander :p)

@Override
public void surfaceCreated(SurfaceHolder holder) {
    mBackground= Bitmap.createBitmap(this.getWidth(), this.getHeight(), Bitmap.Config.RGB_565);
    screenOrientation = getResources().getConfiguration().orientation;
    if(thread.getState()== Thread.State.TERMINATED){
        thread = new RenderThread(holder, this);
        thread.setRunning(true);
        thread.start();
    }else {
        thread.setRunning(true);
        thread.start();
    }
}



@Override
public void surfaceChanged(SurfaceHolder holder, int format, int width,
        int height) {
    Canvas c = new Canvas(mField);
    c.drawARGB(255,0,144,0);
    backgroundDetails.renderOnPanel(c, this);
    screenOrientation = getResources().getConfiguration().orientation;
}

Pretty easy to follow, just redraw the background bitmap if the orientation changes and start the rendering thread.

public void onDraw(Canvas canvas) {
    canvas.drawBitmap(mField,0,0,null);
    drawPositionBeans(canvas, this);
}

And finally:

    public void onDraw(Canvas canvas, RadarView radarView) {
    float beanX=0, beanY=0;
    float radius = 25.0f;
    final List<PositionBean> copyOfList = Collections.synchronizedList(positionBeans.getPositionBeans());
    synchronized(copyOfList){
        try {
            for (final PositionBean pb : copyOfList)
            {
                if (panel.getOrientation() == Configuration.ORIENTATION_PORTRAIT) {
                    beanX = (float)pb.getY()*(-1);
                    beanY = (float)pb.getX();
                } else {
                    beanX = (float)pb.getY()*(-1);
                    beanY = (float)pb.getX()*(-1);
                }
                mPaint.setARGB(255,pb.getRgbColor().getR(), pb.getRgbColor().getG(), pb.getRgbColor().getB());
                panel.drawSpot(canvas, beanX, beanY, radius, mPaint);

                mPaint.setStyle(Paint.Style.STROKE);
                mPaint.setARGB(255,255,222,1);
                for (int j = 0; j < selectedBean.size(); ++j)
                {
                    if(pb.getTrack()==selectedBean.get(j)) {
                        panel.drawSpot(canvas, beanX, beanY, radius+1, mPaint);
                    }
                }
                mPaint.setStyle(Paint.Style.FILL);
            }

        } catch(Exception e) {
            Log.e("render", "Exception Drawing Beans: " + e);
        }
    }
}

Thanks again guys. Chris

4

There are 4 answers

0
David On

I found after reading a bit about surface view that if you call

getHolder().lockCanvas();

You need to draw everything again.

Unless you provide your dirty area.

getHolder().lockCanvas(Rect dirty);

If you only want to redraw the area defined by the Rect

0
HighFlyer On

I had that problem. Solution suitable for me is synchronization of onDraw method and method that updates transformation matrix. Because there was frames when matrix is identical (initial value of new Matrix()) and in next frame it had right values.

0
missaghi On

It's happening becasue the android graphics has a buffer surface and a visible surface and to make sure that they are always available they simply alternate. Which means that if you draw on one frame it won't be there the next unless you draw it on every frame.

http://groups.google.com/group/android-developers/msg/8d1243c33f9b7b6e?pli=1

0
j2emanue On