SurfaceView no longer draw bitmaps in Android 4.1+

To display a view with moving objects (from bitmaps) and touch events, I used the following code for SurfaceView

in Android. It worked great on my development devices, but it turned out that a lot of users just see the black box instead View

. After quite a lot of (unsuccessful) debugging, I've come to the conclusion that it must be Android 4.1, which makes it SurfaceView

stop working correctly.

My development devices are Android 4.0, but users complaining about black SurfaceView

have Android 4.1. Checked that with Android 4.1 emulator - and it doesn't work there.

Can you see what's wrong with the code? Perhaps it has something to do with the "Project Oil" in Android 4.1?

Of course, I have verified that the objects are Bitmap

valid (saved to the SD card in the appropriate lines) and all the drawing methods are called periodically as well - everything is fine there.

package com.my.package.util;

import java.util.ArrayList;
import java.util.List;
import com.my.package.Card;
import com.my.package.MyApp;
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.Rect;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.view.SurfaceHolder;
import android.view.SurfaceView;

public class MySurface extends SurfaceView implements SurfaceHolder.Callback {

    private MyRenderThread mRenderThread;
    private volatile List<Card> mGameObjects;
    private volatile int mGameObjectsCount;
    private int mScreenWidth;
    private int mScreenHeight;
    private int mGameObjectWidth;
    private int mGameObjectHeight;
    private int mHighlightedObject = -1;
    private Paint mGraphicsPaint;
    private Paint mShadowPaint;
    private Rect mDrawingRect;
    private int mTouchEventAction;
    private Bitmap bitmapToDraw;
    private int mOnDrawX1;
    private BitmapFactory.Options bitmapOptions;
    // ...

    public MySurface(Context activityContext, AttributeSet attributeSet) {
        super(activityContext, attributeSet);
        getHolder().addCallback(this);
        setFocusable(true); // touch events should be processed by this class
        mGameObjects = new ArrayList<Card>();
        mGraphicsPaint = new Paint();
        mGraphicsPaint.setAntiAlias(true);
        mGraphicsPaint.setFilterBitmap(true);
        mShadowPaint = new Paint();
        mShadowPaint.setARGB(160, 20, 20, 20);
        mShadowPaint.setAntiAlias(true);
        bitmapOptions = new BitmapFactory.Options();
        bitmapOptions.inInputShareable = true;
        bitmapOptions.inPurgeable = true;
        mDrawingRect = new Rect();
    }


    public void surfaceChanged(SurfaceHolder arg0, int arg1, int arg2, int arg3) { }

    public void surfaceCreated(SurfaceHolder arg0) {
        mScreenWidth = getWidth();
        mScreenHeight = getHeight();
        mGameObjectHeight = mScreenHeight;
        mGameObjectWidth = mGameObjectHeight*99/150;
        mCurrentSpacing = mGameObjectWidth;
        setDrawingCacheEnabled(true);
        mRenderThread = new MyRenderThread(getHolder(), this);
        mRenderThread.setRunning(true);
        mRenderThread.start();
    }

    public void surfaceDestroyed(SurfaceHolder holder) {
        boolean retry = true;
        mRenderThread.setRunning(false); // stop thread
        while (retry) { // wait for thread to close
            try {
                mRenderThread.join();
                retry = false;
            }
            catch (InterruptedException e) { }
        }
    }

    public void stopThread() {
        if (mRenderThread != null) {
            mRenderThread.setRunning(false);
        }
    }

    @Override
    public void onDraw(Canvas canvas) {
        if (canvas != null) {
            synchronized (mGameObjects) {
                mGameObjectsCount = mGameObjects.size();
                canvas.drawColor(Color.BLACK);
                if (mGameObjectsCount > 0) {
                    mCurrentSpacing = Math.min(mScreenWidth/mGameObjectsCount, mGameObjectWidth);
                    for (int c = 0; c < mGameObjectsCount; c++) {
                        if (c != mHighlightedObject) {
                            try {
                                drawGameObject(canvas, mGameObjects.get(c).getDrawableID(), false, c*mCurrentSpacing, c*mCurrentSpacing+mGameObjectWidth);
                            }
                            catch (Exception e) { }
                        }
                    }
                    if (mHighlightedObject > -1) {
                        mOnDrawX1 = Math.min(mHighlightedObject*mCurrentSpacing, mScreenWidth-mGameObjectWidth);
                        try {
                            drawGameObject(canvas, mGameObjects.get(mHighlightedObject).getDrawableID(), true, mOnDrawX1, mOnDrawX1+mGameObjectWidth);
                        }
                        catch (Exception e) { }
                    }
                }
            }
        }
    }

    private void drawGameObject(Canvas canvas, int resourceID, boolean highlighted, int xLeft, int xRight) {
        if (canvas != null && resourceID != 0) {
            try {
                if (highlighted) {
                    canvas.drawRect(0, 0, mScreenWidth, mScreenHeight, mShadowPaint);
                }
                bitmapToDraw = MyApp.gameObjectCacheGet(resourceID);
                if (bitmapToDraw == null) {
                    bitmapToDraw = BitmapFactory.decodeResource(getResources(), resourceID, bitmapOptions);
                    MyApp.gameObjectCachePut(resourceID, bitmapToDraw);
                }
                mDrawingRect.set(xLeft, 0, xRight, mGameObjectHeight);
                canvas.drawBitmap(bitmapToDraw, null, mDrawingRect, mGraphicsPaint);
            }
            catch (Exception e) { }
        }
    }

    @Override
    public boolean onTouchEvent(MotionEvent event) {
        synchronized (mRenderThread.getSurfaceHolder()) { // synchronized so that there are no concurrent accesses
            mTouchEventAction = event.getAction();
            if (mTouchEventAction == MotionEvent.ACTION_DOWN || mTouchEventAction == MotionEvent.ACTION_MOVE) {
                if (event.getY() >= 0 && event.getY() < mScreenHeight) {
                    mTouchEventObject = (int) event.getX()/mCurrentSpacing;
                    if (mTouchEventObject > -1 && mTouchEventObject < mGameObjectsCount) {
                        mHighlightedObject = mTouchEventObject;
                    }
                    else {
                        mHighlightedObject = -1;
                    }
                }
                else {
                    mHighlightedObject = -1;
                }
            }
            else if (mTouchEventAction == MotionEvent.ACTION_UP) {
                if (mActivityCallback != null && mHighlightedObject > -1 && mHighlightedObject < mGameObjectsCount) {
                    try {
                        mActivityCallback.placeObject(mGameObjects.get(mHighlightedObject));
                    }
                    catch (Exception e) { }
                }
                mHighlightedObject = -1;
            }
        }
        return true;
    }

    // ...

}

      

And this is the code for a thread that calls periodically SurfaceView

onDraw()

:

package com.my.package.util;

import android.graphics.Canvas;
import android.view.SurfaceHolder;

public class MyRenderThread extends Thread {

    private SurfaceHolder mSurfaceHolder;
    private MySurface mSurface;
    private boolean mRunning = false;

    public MyRenderThread(SurfaceHolder surfaceHolder, MySurface surface) {
        mSurfaceHolder = surfaceHolder;
        mSurface = surface;
    }

    public SurfaceHolder getSurfaceHolder() {
        return mSurfaceHolder;
    }

    public void setRunning(boolean run) {
        mRunning = run;
    }

    @Override
    public void run() {
        Canvas c;
        while (mRunning) {
            c = null;
            try {
                c = mSurfaceHolder.lockCanvas(null);
                synchronized (mSurfaceHolder) {
                    if (c != null) {
                        mSurface.onDraw(c);
                    }
                }
            }
            finally { // when exception is thrown above we may not leave the surface in an inconsistent state
                if (c != null) {
                    mSurfaceHolder.unlockCanvasAndPost(c);
                }
            }
        }
    }

}

      

SurfaceView

included in my Activity

XML layout :

<com.my.package.util.MySurface
    android:id="@+id/my_surface"
    android:layout_width="fill_parent"
    android:layout_height="@dimen/my_surface_height" />

      

Then, in code, it is available like this:

MySurface mySurface = (MySurface) findViewById(R.id.my_surface);

      

+3


source to share


1 answer


Rename the draw method to onDraw2 (). Change your thread code to call onDraw2. This way you are not bypassing the base ondraw class. I think you might have 2 hits in your onDraw. One from the base class override and one from the thread.

This explains why adjusting the z-order helps. This way you will reverse the order by 2 windows to avoid the problem. As for the "why now" part of the question. Since you have 2 paths to onDraw, I suspect this is unsupported Android behavior, so don't report what might happen.



Also I saw that you called setDrawingCache. I don't think it helps you. You usually call getDrawingCache at some point. Try deleting it if it doesn't matter.

The only thing I can see is that you are creating a stream and passing the holder to the surface. You might want to take action when surfaceChanged occurs, or rather, make sure nothing important has changed.

-1


source







All Articles