문제

I am using a SurfaceView to draw to a part of the screen, whereby I update the drawing with a background thread. The surfaceview implements a SurfaceView.callback.

On the onDraw method I am using canvas.drawpont(x,y,paint) and draw to pretty much the entire canvas pixel by pixel, so it can be quite intensive on large screens. Anyway, I get an illegalArgumentException if I rotate the phone quickly between landscape and reverse landscape. As Follows:

06-18 15:03:05.853: E/AndroidRuntime(29969): FATAL EXCEPTION: Thread-45
06-18 15:03:05.853: E/AndroidRuntime(29969): java.lang.IllegalArgumentException
06-18 15:03:05.853: E/AndroidRuntime(29969):    at Android.view.Surface.unlockCanvasAndPost(Native Method)
06-18 15:03:05.853: E/AndroidRuntime(29969):    at android.view.SurfaceView$3.unlockCanvasAndPost(SurfaceView.java:793)
06-18 15:03:05.853: E/AndroidRuntime(29969):    at com.enhancement.SpecGuageUpdate.run(SpecGuageUpdate.java:313)

And on SpecGugaeupdate at 313, I have:

mHolder.unlockCanvasAndPost(canvas);

Where mHolder is my SurfaceHolder. And of course I have also locked the canvas prior to calling onDraw.

This method works flawlessly if I rotate slowly in every direction, however, the moment I quickly rotate 180degs between the two landscape states it triggers this illegalArgumentException. Upon monitoring the width and height in surfaceChanged, I noticed that when I rotate quickly, the width of landscape get paired with the height of portrait. For example, on my phone (Motorola atrix) the dimensions of the SurfaceView for portrait are 540x307 (WidthxHeight), and for landscape are 960x172. This is what happens when I rotate quickly between landscape and reverse landscape:

Rotate to landscape slowly:

960x172

Rotate quickly to reverse landscape, surface changed gets called twice.

960x307 - 1st
960x172 - 2nd

I have tried overriding onMeasure and the width and Height are identical to that of SurfaceChanged. So it seems I am trying to draw on a canvas which is not quite ready, but ready enough to be able to lock it to my Holder. Any idea's would be great, thanks for your time.

Additionally, in my surfacedestroyed I am handling my thread like this:

boolean retry = true;

            thread.setRunning(false);
              while (retry) {
                    try {
                        specupdate.join();
                        retry = false;
                    } catch (InterruptedException e) {
                    }
                }

Which stops the thread from executing onDraw.

도움이 되었습니까?

해결책 2

In my late night troubleshooting I managed to put together a workaround. The result being that nothing is drawn until the surfacechanged has settled to it's last state, so the user is unaware of what is happening.

Basically I check to see if the returned height is malformed, if it is I do nothing, and when it is what I expect I draw to the surface. What I needed was a reference value to what my height and width should be, I did this by placing the following code before SetContentView() in my main activity:

WindowManager w = getWindowManager();
   Display d = w.getDefaultDisplay();
   int Meausredwidth = d.getWidth();
   int Measuredheight = d.getHeight(); 

From my measured height and width, I worked out how much I needed surfaceView, and passed those values to my SurfaceView as global variables (these are now my desired dimenstions). Ensuring that I am calling the above code in onconfigurationchanged() in my main activity as well so those values get recalculated each time. On my SurfaceView which implements SurfaceHolder.Callback, I changed my surfacechanged() to look like this:

@Override
public void surfaceChanged(SurfaceHolder mholder, int format, int width,
        int height) {

    if (width != mWidth) {
        MalFormed = true;
        thread.setRunning(false);
        count++;

    } else if (width == mWidth) {

        if (MalFormed) {
            MalFormed = false;

            Log.d("ROTATE", "ReDraw");

            thread = new DrawingThread(this, holder, mHandler);
            thread.setRunning(true);
            thread.start();

        }

    }

}

Where mWidth is the desired width. And MalFormed is a global boolean in my surfaceview. From experiementation I worked out that SurfaceChanged() gets called before SurfaceCreated, which means we can easily modify any code which attempts to interact with the thread to suit our above changes.

So anywhere where else I was attempting to join or start that background drawing thread I needed to check first if the surface is MalFormed. E.g.

if (thread.getState() == State.TERMINATED){

            if(!MalFormed){
               holder.removeCallback(this);
               holder = getHolder();
               holder.addCallback(this);


               thread = new DrawingThread(this, holder,mHandler);
               thread.setRunning(true);

               thread.start();
            }



        }else{
            if(!MalFormed){
                thread.setRunning(true);

                try {
                    thread.join();
                } catch (InterruptedException e) {

                    e.printStackTrace();
                }
            }


        }

Anyway this seems to do the trick, perhaps someone else knows something better. But I put my answer here for anyone else who has the same issue. Thanks.

다른 팁

I was having this same issue while working on a Live Wallpaper. I solved this by forcing my canvas to be the screen size. On my onSurfaceChanged I create a global rectangle which I use to pain on my canvas. If I do not do this I experience the same issues as the original author.

Code that solved it:

On surface Changed I initialize all the variables I am using for drawing. Note me creating the _screenRectangle. These variables are global and used throughout the application.

@Override
public void onSurfaceChanged(SurfaceHolder holder, int format, int width, int height) {
    super.onSurfaceChanged(holder, format, width, height);

    System.out.println("===============================================");
    System.out.println("SURFACE CHANGED");
    System.out.println("===============================================");

    //Get Screen Sizes
    _centerX = width/2.0f;
    _centerY = height/2.0f;

    _screenRectangle = new Rect(0, 0, width, height);
    _screenOrient = getResources().getConfiguration().orientation; 

    System.out.println("===============================================");
    System.out.println("WIDTH: " + width + " HEIGHT: " + height + " ORIEN: " + _screenOrient);
    System.out.println("===============================================");

    _resetSurface = true;
    _redrawStatic = true; 
} 

Then in my paint method, I paint the entire screen with background color using:

try
{            
    canvas = holder.lockCanvas(_screenRectangle);


    System.out.println("===============================================");
    System.out.println("PAINT BACKGROUND: " + canvas.getWidth() + " " + canvas.getHeight());
    System.out.println("===============================================");

    if (canvas != null) 
    {
        canvas.drawColor(PreferencesHelper.ScreenColor);
        _resetSurface = false;
    }
}
finally 
{
    System.out.println("===============================================");
    System.out.println("BACKGROUND PAINTED");
    System.out.println("===============================================");
    if (canvas != null) holder.unlockCanvasAndPost(canvas);
}

Before using _screenRectangle to lock canvas, at random times, it would fail to paint the entire screen. Debug window would show that canvas sizes did not match the current screen, but would have same problem as pointed out by the OP This seems to have solved the issue. This forces the locked canvas to be the same size as reported by the onSurfaceChanged method.

라이센스 : CC-BY-SA ~와 함께 속성
제휴하지 않습니다 StackOverflow
scroll top