3

動くオブジェクト (ビットマップから) とタッチ イベントを含むビューを表示するためにSurfaceView、Android で次のコードを使用しています。私の開発用デバイスでは常に問題なく動作していましたが、多くのユーザーはその代わりにブラック ボックスが表示されることが判明しましたViewSurfaceViewかなり長い間 (失敗した) デバッグを行った後、Android 4.1 が原因で が正しく動作しなくなるという結論に達しました。

私の開発用デバイスは Android 4.0 ですが、黒のみについて不満を言うユーザーSurfaceViewは Android 4.1 を使用しています。Android 4.1エミュレーターで確認しましたが、そこでも動作していません。

コードの何が問題なのか分かりますか? おそらくAndroid 4.1の「Project Butter」が原因でしょうか?

もちろん、Bitmapオブジェクトが有効であること (適切な行で SD カードに保存されていること) を確認しており、描画のためのすべてのメソッドも定期的に呼び出されています。すべて正常です。

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

    // ...

}

そして、これは定期的に を呼び出すスレッドのコード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、私Activityのレイアウト XMLに含まれています。

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

次に、コードでは次のようにアクセスします。

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

1 に答える 1

-1

draw メソッドの名前を onDraw2() に変更します。onDraw2 を呼び出すようにスレッド コードを変更します。このようにして、基本クラスの ondraw をオーバーライドしません。onDraw で 2 ヒットを取得している可能性があると思います。1 つは基本クラスのオーバーライドから、もう 1 つはスレッドからです。

これにより、z オーダーの設定が役立つ理由が説明されます。2 つのウィンドウの描画順序を逆にして、問題を回避します。質問の「なぜ今」の部分について。onDraw への 2 つの経路があるため、これはサポートされていない Android の動作であると思われるため、何が起こるかわかりません。

また、setDrawingCache が有効になっていると呼ばれるのを見ました。私はそれがあなたを助けているとは思わない。通常、ある時点で getDrawingCache を呼び出します。重要でない場合は削除してみてください。

私が見る唯一の他のことは、スレッドを作成し、作成されたサーフェスにホルダーを渡すことです。surfaceChanged が発生したときにアクションを実行するか、少なくとも重要な変更がないことを確認する必要がある場合があります。

于 2013-02-02T16:44:14.143 に答える