【问题标题】:Multiple threads updating a surfaceview canvas多个线程更新表面视图画布
【发布时间】:2014-12-17 21:52:34
【问题描述】:

从事咨询项目。最后一分钟的要求是球在屏幕上弹跳(不要问为什么......叹息

无论如何...这些球是按值分组的。 10 个球是 RED,价值 100 分。 5 个球是蓝色的,价值 50 分。 5 个球是绿色的,价值 25 分。 5 个球是黄色的,值 10 分。

在这种背景下,我采用的方法是扩展 SurfaceView 并定义 5 个线程,每个线程管理一组特定的球。

每个线程从 SurfaceView 接收相同的 SurfaceHolder。

我之所以选择多个线程而不是一个线程,是因为管理屏幕上所有球的性能并不是最好的。

OpenGL 现在不是一个真正的选择。

这是其中一个线程类的示例。当线程运行时,它会创建一定数量的球。每个球都是随机创建并添加到列表中的。

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();
    }
}

这是它们都扩展的基类:

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;
        }
    }
}

发生的情况是,如果它只是一个线程......它工作正常。一旦我引入了第二个线程,混合......说一个 HUNDRED_BALLS_THREAD 和一个 FIFTY_BALLS_THREAD 那就是一切都变得疯狂的时候。

如果你想这样称呼它,线程“工作”......但屏幕不断闪烁。

我知道原因对你们中的一些人来说可能是显而易见的,但不幸的是我不明白为什么。

我会假设因为每个线程都在锁定画布......它会等待。

有什么办法可以解决这个闪烁的问题吗?我的设计决定完全错误吗?我确定这是因为每个线程都在访问同一个画布……但我认为这会导致它像那样闪烁。

【问题讨论】:

  • 一个问题肯定是 canvas.SETCOLOR 调用......这导致闪烁......但仍然无法按照我想要的方式工作。现在我无法清除之前的球位置。

标签: java android multithreading android-canvas surfaceview


【解决方案1】:

SurfaceView 的 Surface 是双缓冲或三缓冲的。每次调用unlockCanvasAndPost() 都会向合成器提交一个新缓冲区。如果您每次只渲染场景的 1/5(我们称之为 A-B-C-D-E),那么您将得到一个只有“A”球的帧,然后是一个只有“B”球的帧,依此类推。这假设您的线程被安排得相当循环,它们通常不在 Android/Linux 上。我怀疑您看到闪烁是因为您基本上以 50 fps 运行,一次只显示一组对象。

如果您不每次都清除 Canvas,故障将不那么明显,因为 Android 不会为您清除 Canvas。所以你从前一个前缓冲区的内容开始,这可能是一组不同的球。

系统在画布被锁定时提供独占访问。您可以尝试将 SurfaceHolder 的(应该是不必要的)锁定移到画布锁定/解锁之外,看看是否有区别。

有关完整说明,请参阅 Android System-Level Graphics Architecture 文档。

就您的情况而言,虽然您可以让多个线程更新状态(例如球的位置),但很难让多个线程共享一个 Canvas 进行渲染。如果你真的想在软件中完成这一切,试试这个:创建一个位图,然后使用尽可能多的线程自己渲染圆圈(使用 Bresenham 或位图)。定期让一个线程冻结位图,锁定画布,然后将位图blit到它。

如果您想要一些简单的 2D GLES 渲染示例,请参阅 GrafikaAndroid Breakout(后者使用 Bresenham 生成圆形球纹理)。

【讨论】:

  • 感谢您的深入解释。我要去看看你的链接。如果这最终导致我找到解决方案,我会在跟进您的建议后接受。感谢您花时间发布。
  • 嘿@fadden 因此,如果 SurfaceView 只有一个表面(画布指向此表面缓冲区以绘制形状),并且只有一个线程可以通过 lockCanvas() 和 unlockCanvas 访问要绘制的表面,为什么要这样做我们需要多线程?也伤不只是正常的查看工作在这里?我错过了什么?
  • (我不确定SurfaceView的surface和View的surface view是不是一样)
  • @solti Views 只能从主 UI 线程更新,而 SurfaceView 的 Surface 没有这个限制。因此,您可以使用昂贵的操作来占用渲染器线程,而不会停止用户输入或其他框架操作。您也不太容易让您的动画因 UI 线程活动而停止。是的,自定义 View 可能是比 SurfaceView 更好的选择。有关为什么让多个线程同时访问同一内存很棘手的信息,请参阅developer.android.com/training/articles/smp.html
  • @fadden 是的,在 SurfaceView 的情况下,我们可以绑定渲染器线程来执行昂贵的绘图操作,而 UI 线程处理触摸事件、onMeasure、onLayout 的东西,这很有意义。如果是这种情况,为什么您再次说“自定义 View 可能是比 SurfaceView 更好的选择。”再次?自定义视图显然不是一个好主意。我在想 SurfaceView 有 2-3 个线程而不是 5 个线程(不确定可能需要收集数据并做一些研究,但我不是在这里争论线程数)
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2011-06-05
  • 1970-01-01
相关资源
最近更新 更多