简体   繁体   中英

Multiple threads updating a surfaceview canvas

Working on a consulting project. A last minute requirement is balls bouncing around on the screen (don't ask why... sigh )

Anyways...these balls are grouped with values. 10 balls are RED worth 100 points. 5 balls are BLUE worth 50 points. 5 balls are GREEN worth 25 points. 5 balls are YELLOW worth 10 points.

With that background the approach that I've taken is to extend a SurfaceView and define 5 threads each of which manages a particular groups of balls.

Each thread receives the same SurfaceHolder from the SurfaceView.

The reason I've chosen multiple threads instead of just one is because the performance of managing all of the balls onscreen is not the greatest.

OpenGL is not really an option right now.

Here's an example of one of the thread classes. When the thread is run, it creates a certain number of balls. Each ball is randomly created and added to a list.

public class hundred_balls_thread extends base_balls_thread {
    public hundred_balls_thread(SurfaceHolder holder, Context ctext, int radius) {
        super(holder, ctext, radius);
    }

    @Override
    public void run() {
        int x, y, radius;

        while (Calorie_balls.size() <= 21) {

            x = 100 + (int) (Math.random() * (mCanvasWidth - 200));
            y = 100 + (int) (Math.random() * (mCanvasHeight) - 200);
            radius = mRadius;

            if ((x - mRadius) < 0) {
                x = x + mRadius;
            }

            if ((x + mRadius) > mCanvasWidth) {
                x = x - mRadius;
            }

            if ((y + mRadius) > mCanvasHeight)
                y = y - mRadius;

            if ((y - mRadius) < 0)
                y = y + mRadius;

            calorie_ball ball = new calorie_ball(x, y, radius, context.getResources().getColor(R.color.red100ball), "100");

            boolean addit = true;

            Calorie_balls.add(ball);
        }

        super.run();
    }
}

Here's the base class that they all extend:

public class base_balls_thread extends Thread {
    protected int mCanvasWidth;
    protected int mCanvasHeight;
    protected int mRadius;
    protected Context context;

    public ArrayList<calorie_ball> Calorie_balls = new ArrayList<calorie_ball>(); // Dynamic array with dots

    private SurfaceHolder holder;
    private boolean running = false;
    private final Paint paint = new Paint(Paint.ANTI_ALIAS_FLAG);
    private final Paint text_paint = new Paint(Paint.ANTI_ALIAS_FLAG);
    private final int refresh_rate = 100;      // How often we update the screen, in ms

    public base_balls_thread(SurfaceHolder holder, Context ctext, int radius) {
        this.holder = holder;
        context = ctext;
        mRadius = radius;
    }

    @Override
    public void run() {
        long previousTime, currentTime;
        previousTime = System.currentTimeMillis();
        Canvas canvas = null;

        while (running) {
            // Look if time has past
            currentTime = System.currentTimeMillis();
            while ((currentTime - previousTime) < refresh_rate) {
                currentTime = System.currentTimeMillis();
            }

            previousTime = currentTime;

            try {

                // PAINT
                try {
                    canvas = holder.lockCanvas();
                    synchronized (holder) {
                        draw(canvas);
                    }
                } finally {
                    if (canvas != null) {
                        holder.unlockCanvasAndPost(canvas);
                    }
                }
                // WAIT
                try {
                    Thread.sleep(refresh_rate); // Wait some time till I need to display again
                } catch (InterruptedException e) {
                    // TODO Auto-generated catch block
                    e.printStackTrace();
                }
            } catch (Exception eal) {
                String msg = eal.getMessage();
                if (msg == null)
                    msg = "Blahbla";
            }
        }

    }

    // The actual drawing in the Canvas (not the update to the screen).
    private void draw(Canvas canvas) {

//        dot temp_dot;
        canvas.drawColor(Color.BLACK);
        paint.setStyle(Paint.Style.FILL_AND_STROKE);
        paint.setStrokeWidth(4);

        text_paint.setColor(Color.BLACK);
        text_paint.setTextSize(40);

        try {
            for (calorie_ball crcl : Calorie_balls) {
                paint.setColor(crcl.color);
                paint.setShader(new RadialGradient(crcl.x + 10, crcl.y, crcl.radius * 2, crcl.color, Color.BLACK, Shader.TileMode.CLAMP));
                if (crcl.x + crcl.radius < 0 && crcl.y + crcl.radius < 0) {
                    crcl.x = canvas.getWidth() / 2;
                    crcl.y = canvas.getHeight() / 2;
                } else {
                    crcl.x += crcl.xVelocity;
                    crcl.y += crcl.yVelocity;

                    if ((crcl.x > canvas.getWidth() - crcl.radius) || (crcl.x - crcl.radius < 0)) {
                        crcl.xVelocity = crcl.xVelocity * -1;
                    }
                    if ((crcl.y > canvas.getHeight() - crcl.radius) || (crcl.y - crcl.radius < 0)) {
                        crcl.yVelocity = crcl.yVelocity * -1;
                    }
                }

                String calval = crcl.get_calorie_value();
                int x = crcl.x + 5;
                int y = crcl.y + 5;

                canvas.drawCircle(crcl.x, crcl.y, crcl.radius, paint);
                canvas.drawText(calval, x, y, text_paint);
            }

        } catch (Exception ep) {
            String b = ep.getMessage();
            if (b == null)
                b = "blah";
        }
    }

    public void setRunning(boolean b) {
        running = b;
    }

    protected Canvas myCanvas;
    protected Bitmap cvsBmp;
    protected Matrix identityMatrix;

    public void setSurfaceSize(int width, int height) {
        synchronized (holder) {
            mCanvasWidth = width;
            mCanvasHeight = height;
        }
    }
}

What's happening is that if it's just ONE thread...it works fine. Once I introduce a second thread the mix...say a HUNDRED_BALLS_THREAD and a FIFTY_BALLS_THREAD that's when everything goes crazy.

The threading "works" if you want to call it that...but the screen flickers constantly.

I know the reasoning is probably obvious to some of you but unfortunately I don't understand why.

I would assume that because each thread is locking the canvas...it would wait.

Any way around this flickering? Is my design decision just completely wrong? I'm sure it's because each thread is accessing the same canvas...but I would think that would cause it flicker like that.

The SurfaceView's Surface is double- or triple-buffered. Every call to unlockCanvasAndPost() submits a new buffer to the compositor. If you're only rendering 1/5th of the scene each time (let's call then ABCDE), then you'll get a frame with just the 'A' balls, then one with just the 'B' balls, and so on. That assumes that your threads are scheduled fairly round-robin, which they are generally not on Android/Linux. I suspect you're seeing flicker because you're essentially running at 50fps, showing only one set of objects at a time.

If you don't clear the Canvas each time, the glitches will be less obvious, because Android doesn't erase the Canvas for you. So you start with the contents of the previous front buffer, which will probably be a different set of balls.

The system provides exclusive access while the canvas is locked. You can try moving your (should-be-unnecessary) locking of the SurfaceHolder outside the canvas lock/unlock to see if it makes a difference.

For a full explanation, see the Android System-Level Graphics Architecture doc.

As far as your situation goes, while you can have multiple threads updating state like the position of the balls, it's difficult to have multiple threads sharing a single Canvas for rendering. If you really want to do it all in software, try this: create a bitmap, and render the circles yourself (with Bresenham or bitmaps) using as many threads as you like. Periodically have one thread freeze the bitmap, lock the canvas, and blit your bitmap to it.

If you'd like some examples of simple 2D GLES rendering, see Grafika or Android Breakout (the latter of which uses Bresenham to generate a circular ball texture).

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM