Android SurfaceView canvas drawing with a thread

2019-03-29 05:51发布

问题:

I am experimenting with drawing on a canvas using a thread to create a simple game engine but I'm having some weird issues I cannot explain. The purpose of this "game" is to draw a circle every second on the canvas. This works, but not the way I want it to work, it seems the app is switching between two canvasses and adding a circle to each canvas so you get a switch between two canvasses every second with the same number of circles but in a different place on the canvas.

I don't know what I'm doing wrong, but I'm not that familiar with Treadding, has it something to do with how many cores my android device has or something like that?

My code is shown below, so I just use a launchthread which uses a layoutfile that links to the animationthread which starts a thread and draws a circle on the canvas every second. (You can ignore the touchevent, it isn't uses yet).

The project exists out of a main launchthread:

public class MainActivity extends Activity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.main);
    }
}

which uses this layout file:

<?xml version="1.0" encoding="utf-8"?>
<FrameLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="fill_parent"
    android:layout_height="fill_parent">     
        <com.androidtesting.AnimationView
            android:id="@+id/aview"
            android:layout_width="fill_parent"
            android:layout_height="fill_parent"/>
</FrameLayout>

And my Surfaceview class with an inner Thread class:

class AnimationView extends SurfaceView implements SurfaceHolder.Callback {
    private boolean touched = false;
    private float touched_x, touched_y = 0;
    private Paint paint;
    private Canvas c;
    private Random random;
    private AnimationThread thread;

    public AnimationView(Context context, AttributeSet attrs) {
        super(context, attrs);

        SurfaceHolder holder = getHolder();
        holder.addCallback(this);

        thread = new AnimationThread(holder);
    }

    class AnimationThread extends Thread {
        private boolean mRun;       
        private SurfaceHolder mSurfaceHolder;        

        public AnimationThread(SurfaceHolder surfaceHolder) {
            mSurfaceHolder = surfaceHolder;
            paint = new Paint();
            paint.setARGB(255,255,255,255);
            paint.setTextSize(32);
        }

        @Override
        public void run() {
            while (mRun) {
                c = null;
                try {
                    c = mSurfaceHolder.lockCanvas(null);
                    synchronized (mSurfaceHolder) {                 
                        doDraw(c);
                        sleep(1000);
                    }
                } catch (Exception e) {                 
                    e.printStackTrace();
                }finally {
                    if (c != null) {
                        mSurfaceHolder.unlockCanvasAndPost(c);
                    }
                }
            }
        }

        private void doDraw(Canvas canvas) {
            //clear the canvas
            //canvas.drawColor(Color.BLACK);                        

            random = new Random();
            int w = canvas.getWidth();
            int h = canvas.getHeight();
            int x = random.nextInt(w-50); 
            int y = random.nextInt(h-50);
            int r = random.nextInt(255);
            int g = random.nextInt(255);
            int b = random.nextInt(255);
            int size = 20;
            canvas.drawCircle(x,y,size,paint);            
            canvas.restore();
        }
        public void setRunning(boolean b) {
            mRun = b;
        }
    }

    public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {

    }

    @Override
    public boolean onTouchEvent(MotionEvent event) {
           touched_x = event.getX();
           touched_y = event.getY();

           int action = event.getAction();

           switch(action){
                case MotionEvent.ACTION_DOWN:           
                    touched = true;
                    break;
                case MotionEvent.ACTION_MOVE:
                    touched = true;
                    break;        
                default:
                    touched = false;
                    break;
           }

           return true;
    }

    public void surfaceCreated(SurfaceHolder holder) {
        thread.setRunning(true);
        thread.start();
    }

    public void surfaceDestroyed(SurfaceHolder holder) {
        boolean retry = true;
        thread.setRunning(false);
        while (retry) {
            try {
                thread.join();
                retry = false;
            } catch (InterruptedException e) {
            }
        }
    }
}

回答1:

it seems the app is switching between two canvasses

Yes, this is how it works. It is called double buffering and you need to redraw all the frame each time:

The content of the Surface is never preserved between unlockCanvas() and lockCanvas(), for this reason, every pixel within the Surface area must be written.

So you need this line canvas.drawColor(Color.BLACK) to be uncommented in your code.

And you shouldn't call Thread.sleep(1000) while canvas is locked, it will cause starvation issue.



回答2:

It sounds like you have this working, but I did just notice a small error that I should point out.

You called canvas.restore() without calling canvas.save() beforehand. From the Android developer reference for Canvas: "It is an error to call restore() more times than save() was called."

I don't see any reason for you to call canvas.save() in your case, therefore you should remove the call to canvas.restore().