2013-03-14 3 views
2

Я просто сделал операцию, которая использует в представлении «setContentView» вид из класса, который расширяет SurfaceView. Проблема в том, что: Он работает нормально, но когда я выхожу из него (кнопка BACK), он падает. Код:Пользовательский SurfaceView с авариями потоков Приложение, когда действие завершено

package ro.etrandafir.mate.appCreator; 

import android.app.Activity; 
import android.os.Bundle; 
import android.content.Context; 
import android.view.View; 
import android.view.SurfaceView; 
import android.graphics.Canvas; 
import android.view.MotionEvent; 
import android.graphics.Color; 
import android.graphics.Paint; 

public class Sample2 extends Activity implements View.OnTouchListener { 

    float x = 0, y = 0; 
    SampleTwoView theView; 

    public boolean onTouch(View v, MotionEvent event) { 
     // TODO: Implement this method 
     x = event.getX(); 
     y = event.getY(); 
     return true; 
    } 

    @Override 
    protected void onPause() { 
     super.onPause(); 
     finish(); 
    } 

    @Override 
    protected void onCreate(Bundle b) { 
     super.onCreate(b); 
    theView = new SampleTwoView(this); 
     theView.setOnTouchListener(this); 
     setContentView(theView); 
    } 

    public class SampleTwoView extends SurfaceView implements Runnable { 

     Paint p = new Paint(); 

     public SampleTwoView(Context context) { 
      super(context); 
      p.setColor(Color.RED); 
      Thread theThread = new Thread(this); 
      theThread.start(); 
     } 

     public void run() { 
      while (true) { 
       if (!getHolder().getSurface().isValid()) continue; 
       Canvas canvas; 
       canvas = getHolder().lockCanvas(); 
       canvas.drawColor(Color.BLUE); 
       if ((x != 0) && (y != 0)) canvas.drawCircle(x, y, 40, p); 
       getHolder().unlockCanvasAndPost(canvas); 
      } 
     } 
    } 
} 

Что мне делать? Должен ли я добавить onDestroy или что?

Спасибо заранее, Матей

+2

отправьте свою трассировку стека logcat. –

+0

Какая ошибка вы получаете? –

+0

Так что я должен установить while (boolean var) и изменить var на false в onPause? – Chaoz

ответ

2

вопрос вы получаете связано с этим кодом:

Canvas canvas; 
canvas = getHolder().lockCanvas(); 
canvas.drawColor(Color.BLUE); 

Когда заканчивается ваша деятельность, ваш поток по-прежнему работает, но пользовательский SurfaceView больше не доступно, поэтому вы получите исключение null ptr. Ваш существующий код может быть легко исправлена ​​путем добавления булево, который получает значение ЛОЖЬ, как только п вызывается:

public void run() { 
    while (booleanThatGetsSetToFalseWhenActivityPauses) { 
     if (!getHolder().getSurface().isValid()) continue; 
     Canvas canvas; 
     canvas = getHolder().lockCanvas(); 
     canvas.drawColor(Color.BLUE); 
     if ((x != 0) && (y != 0)) canvas.drawCircle(x, y, 40, p); 
     getHolder().unlockCanvasAndPost(canvas); 
    } 
} 

Однако, я хотел бы предложить изменения структуры приложения в целом. Это может быть просто для практики, но я думаю, что более эффективный и простой способ достижения вашей цели состоит в том, чтобы просто использовать стандарт SurfaceView и полностью отделить логику рисования от любого пользовательского представления.


Моя Переработанная активность ниже, но он использует Ball класс, который используется для поддержания логики шара, который, в текущем коде отдельно в сочетании как с actvity (координат) и представление (или Paint). В этом новом шаровом классе мяч имеет место (заданное PointF), Paint и диаметр. Он также имеет методы для получения большинства этих переменных в дополнение к настройке некоторых.

public class Ball { 

    private Paint mPaint; 
    private PointF mCoordinates; 
    private int mDiameter; 

    public Ball (int color, int diameter) { 
     mPaint = new Paint(); 
     mPaint.setColor(color); 
     mCoordinates = new PointF(); 
     mCoordinates.x = 0; 
     mCoordinates.y = 0; 
     mDiameter = diameter; 
    } 

    public void setCoordinates (float x, float y) { 
     mCoordinates.x = x; 
     mCoordinates.y = y; 
    } 

    public PointF getCoordinates() { 
     return mCoordinates; 
    } 

    public Paint getPaint() { 
     return mPaint; 
    } 

    public int getDiameter() { 
     return mDiameter; 
    } 

    /* You did not want to draw the uninitialized ball, so this method checks that */ 
    public boolean hasNonZeroLocation() { 
     return (mCoordinates.x != 0 && mCoordinates.y != 0); 
    } 
} 

Я использую Ball класс в деятельности, как показано ниже. Обратите внимание, что перерисовка на холст теперь возникает только тогда, когда пользователь касается холста, а не бесконечного цикла while. Это связано с использованием класса Handler, который отправляет действия для запуска в поток пользовательского интерфейса. Кроме того, теперь нам не нужен пользовательский вид, и логика нашего шара была отделена от активности и представления.

public class RedBallActivity extends Activity { 

Handler mDrawingHandler; 
SurfaceView mDrawingSurfaceView; 
Ball mBall; 

private final Runnable drawRedBallOnBlueSurface = new Runnable() { 
    @Override 
    public void run() { 
     if (!mDrawingSurfaceView.getHolder().getSurface().isValid()) return; 

     Canvas canvas = mDrawingSurfaceView.getHolder().lockCanvas(); 
     canvas.drawColor(Color.BLUE); 
     if (mBall.hasNonZeroLocation()) 
      canvas.drawCircle(mBall.getCoordinates().x, mBall.getCoordinates().y, mBall.getDiameter(), mBall.getPaint()); 

     mDrawingSurfaceView.getHolder().unlockCanvasAndPost(canvas); 
    } 
}; 

private final OnTouchListener mCanvasTouchListener = new OnTouchListener() { 

    @Override 
    public boolean onTouch(View v, MotionEvent event) { 
     mBall.setCoordinates(event.getX(), event.getY()); 
     mDrawingHandler.post(drawRedBallOnBlueSurface); 
     return true; 
    } 
}; 

@Override 
protected void onCreate(Bundle b) { 
    super.onCreate(b); 
    mDrawingSurfaceView = new SurfaceView(this); 
    mDrawingSurfaceView.setOnTouchListener(mCanvasTouchListener); 
    setContentView(mDrawingSurfaceView); 
    mBall = new Ball(Color.RED, 40); 
    mDrawingHandler = new Handler(); 
} 
} 

Теперь, если вы действительно запустите этот код, вы заметите, что изначально экран не нарисован синим фоном. У вас может возникнуть соблазн просто позвонить mDrawingHandler.post(drawRedBallOnBlueSurface); в конце метода onCreate, но не гарантируется, что SurfaceView будет готов к рисованию (see the documentation on this lockCanvas method).Если вы хотите, чтобы поверхность первоначально была синей, вам необходимо реализовать [SurfaceHolder.Callback][2], который необходимо подключить к SurfaceHolder SurfaceView, и по вызываемому методу surfaceCreated мы знаем, что поверхность готова, поэтому мы можем позвонить mDrawingHandler.post(drawRedBallOnBlueSurface);

Теперь с этим добавил, изменить активность реализовать [SurfaceHolder.Callback][2] следующим образом:

public class FriendManagerActivity extends Activity implements SurfaceHolder.Callback { 

и добавьте эту строку в конструктор:

mDrawingSurfaceView.getHolder().addCallback(this); 

и реализовать интерфейс:

@Override 
public void surfaceCreated(SurfaceHolder holder) { 
    mDrawingHandler.post(drawRedBallOnBlueSurface); 
} 

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

@Override 
public void surfaceDestroyed(SurfaceHolder holder) { 
} 

Не стесняйтесь задавать любые вопросы о моей небольшой редизайне! Хотя ваша проблема может быть легко исправлена, я чувствовал, что так, как вы связываете логику с представлениями, было немного некорректно, и подумал немного больше информации о кодировании SurfaceView.

2

Как кто-то упомянул об этом выше, когда ваша деятельность заканчивается, ваш поток все еще работает, но ваш пользовательский SurfaceView больше не доступен, поэтому вы получите исключение Null Point Exception. Ваш существующий код может быть легко исправлен путем добавления логического значения, которое получает значение false, как только называется onPause fn: у меня была та же проблема. Чтобы решить эту проблему, я добавил следующее OnPause() к классу SampleTwoView:

// pause method will destroy the Thread 
    public void pause() { 
     isRunning = false; 
     while (true) { 
      try { 
       myThread.join(); 
      } catch (InterruptedException e) { 
       // TODO Auto-generated catch block 
       e.printStackTrace(); 
      } 
      break; 
     } 
     myThread = null; 
    } 

Затем вызовите этот метод OnPause() в OnPause() метод вашего класса Sample2 следующим образом:

@Override 
protected void onPause() { 
    super.onPause(); 
    SampleTwoView.onPause(); 
    finish(); 
} 

Так каждый раз, когда метод onPause() вашего основного класса Activity называется Thread, будет уничтожен. Надеюсь, это поможет вам. Приветствия!

Смежные вопросы