SurfaceView appears empty, nothing being rendered

2019-06-09 14:41发布

问题:

I'm trying to draw some shapes onto a SurfaceView from within a thread, but nothing is being rendered to the screen. I've looked at similar questions by people having the same problem, but none of the answers have led me to a solution, suggesting a different cause in my particular case.

I've created a simplified version of my code to demonstrate the problem. Rendering is handled by the RenderingTestView class, which implements a custom view derived from SurfaceView. The rendering thread is implemented as a Runnable inside RenderingTestView:

package com.example.renderingtest.app;

import android.content.Context;
import android.graphics.*;
import android.os.Build;
import android.util.AttributeSet;
import android.view.SurfaceHolder;
import android.view.SurfaceView;

public class RenderingTestView extends SurfaceView {
    private SurfaceHolder holder;
    private Paint paint;
    private boolean surfaceCreated = false;
    private Thread videoThread;

    public RenderingTestView(Context context) {
        super(context);
        init_view(context);
    }

    public RenderingTestView(Context context, AttributeSet attrs) {
        super(context, attrs);
        init_view(context);
    }

    public RenderingTestView(Context context, AttributeSet attrs, int defStyle) {
        super(context, attrs, defStyle);
        init_view(context);
    }

    private void init_view(Context context)
    {
        if (Build.VERSION.SDK_INT >= 11)
            setLayerType(android.view.View.LAYER_TYPE_SOFTWARE, null);

        paint = new Paint();
        paint.setColor(Color.RED);

        holder = getHolder();

        holder.addCallback(new SurfaceHolder.Callback() {
            @Override
            public void surfaceCreated(SurfaceHolder holder) {
                surfaceCreated = true;

                videoThread = new Thread(videoRunnable);
                videoThread.start();
            }

            @Override
            public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {
                // do nothing
            }

            @Override
            public void surfaceDestroyed(SurfaceHolder holder) {
                surfaceCreated = false;
            }
        });
    }

    private Runnable videoRunnable = new Runnable() {
        @Override
        public void run() {
            Thread.currentThread().setPriority(Thread.NORM_PRIORITY);

            while (true) {
                if (!surfaceCreated || !holder.getSurface().isValid())
                    continue;

                Canvas c = holder.lockCanvas(null);
                try {
                    synchronized (holder) {
                        if (c != null)
                            Draw(c);
                    }
                }
                finally {
                    if (c != null)
                        holder.unlockCanvasAndPost(c);
                }
            }
        }
    };

    protected void Draw(Canvas canvas)
    {
        canvas.drawCircle(0, 0, 100, paint);
    }
}

Placing a breakpoint inside Draw() confirms that it's being called successfully.

The layout file looks like this:

<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:paddingLeft="@dimen/activity_horizontal_margin"
    android:paddingRight="@dimen/activity_horizontal_margin"
    android:paddingTop="@dimen/activity_vertical_margin"
    android:paddingBottom="@dimen/activity_vertical_margin"
    tools:context="com.example.renderingtest.app.RenderingTest" android:background="#000000">

    <com.example.renderingtest.app.RenderingTest
            android:id="@+id/renderingTestView"
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:layout_alignParentRight="true"
            android:layout_alignParentEnd="true" android:layout_alignParentTop="true"/>

</RelativeLayout>

Overriding onDraw() in RenderingTestView, like this:

@Override
public void onDraw(Canvas canvas)
{
     super.onDraw(canvas);
     Draw(canvas);
}

... and calling setWillNotDraw(false) inside init_view() does in fact produce the desired output, but I want to render from inside the Runnable rather than wait for invalidate() to produce a call to onDraw().

回答1:

After further testing, I've discovered the problem is caused by the following code:

if (Build.VERSION.SDK_INT >= 11)
    setLayerType(android.view.View.LAYER_TYPE_SOFTWARE, null);

It turns out calling setLayerType() as above somehow prevents SurfaceView from rendering anything onto the Canvas, and is in any case unnecessary since SurfaceView rendering is always performed in software. When first testing my draw calls I was using a regular View rather than a SurfaceView, and the offending lines were carried over from that.