Note that there are some explanatory texts on larger screens.

plurals
  1. POSurfaceView not drawing bitmaps anymore in Android 4.1+
    text
    copied!<p>In order to display a view with moving objects (from bitmaps) and touch events, I've been using the following code for a <code>SurfaceView</code> in Android. It has alwas worked fine on my development devices, but it turned out that lots of users just see a black box in place of that <code>View</code>. After quite a long time of (unsuccessful) debugging, I've come to the conclusion that it must be Android 4.1 which causes the <code>SurfaceView</code> to stop working correctly.</p> <p>My development devices are Android 4.0 but users complaining about the black-only <code>SurfaceView</code> have Android 4.1. Checked that with a Android 4.1 emulator - and it's not working there, either.</p> <p>Can you see what is wrong with the code? Is it caused by the "Project Butter" things in Android 4.1, perhaps?</p> <p>Of course, I've checked that the <code>Bitmap</code> objects are valid (saved them to SD card in appropriate lines) and all methods for drawing are periodically called as well - everything's normal there.</p> <pre><code>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&lt;Card&gt; 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&lt;Card&gt;(); 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 &gt; 0) { mCurrentSpacing = Math.min(mScreenWidth/mGameObjectsCount, mGameObjectWidth); for (int c = 0; c &lt; mGameObjectsCount; c++) { if (c != mHighlightedObject) { try { drawGameObject(canvas, mGameObjects.get(c).getDrawableID(), false, c*mCurrentSpacing, c*mCurrentSpacing+mGameObjectWidth); } catch (Exception e) { } } } if (mHighlightedObject &gt; -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 &amp;&amp; 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() &gt;= 0 &amp;&amp; event.getY() &lt; mScreenHeight) { mTouchEventObject = (int) event.getX()/mCurrentSpacing; if (mTouchEventObject &gt; -1 &amp;&amp; mTouchEventObject &lt; mGameObjectsCount) { mHighlightedObject = mTouchEventObject; } else { mHighlightedObject = -1; } } else { mHighlightedObject = -1; } } else if (mTouchEventAction == MotionEvent.ACTION_UP) { if (mActivityCallback != null &amp;&amp; mHighlightedObject &gt; -1 &amp;&amp; mHighlightedObject &lt; mGameObjectsCount) { try { mActivityCallback.placeObject(mGameObjects.get(mHighlightedObject)); } catch (Exception e) { } } mHighlightedObject = -1; } } return true; } // ... } </code></pre> <p>And this is the code for the thread that periodically calls the <code>SurfaceView</code>'s <code>onDraw()</code>:</p> <pre><code>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); } } } } } </code></pre> <p>The <code>SurfaceView</code> is included in my <code>Activity</code>'s layout XML:</p> <pre><code>&lt;com.my.package.util.MySurface android:id="@+id/my_surface" android:layout_width="fill_parent" android:layout_height="@dimen/my_surface_height" /&gt; </code></pre> <p>Then in code it is accessed like this:</p> <pre><code>MySurface mySurface = (MySurface) findViewById(R.id.my_surface); </code></pre>
 

Querying!

 
Guidance

SQuiL has stopped working due to an internal error.

If you are curious you may find further information in the browser console, which is accessible through the devtools (F12).

Reload