как приостановить и возобновить поток SurfaceView - PullRequest
14 голосов
/ 20 августа 2010

У меня есть настройка SurfaceView, но когда я возобновляю его, я получаю ошибку, что поток уже запущен.Как правильно обращаться, когда приложение переходит в фоновый режим, а затем возвращается на передний план?Я повозился и смог заставить приложение вернуться без сбоев ... но SurfaceView больше ничего не рисует.Мой код:

    @Override
    public void surfaceCreated(SurfaceHolder holder) {
           Log.e("sys","surfaceCreated was called.");
           if(systemState==BACKGROUND){
                  thread.setRunning(true);

           }
           else {
        thread.setRunning(true);
               thread.start();
               Log.e("sys","started thread");
               systemState=READY;
           }



    }
    @Override
    public void surfaceDestroyed(SurfaceHolder holder) {
           Log.e("sys","surfaceDestroyed was called.");
           thread.setRunning(false);
           systemState=BACKGROUND;
    }

Ответы [ 8 ]

11 голосов
/ 30 августа 2010

Простое решение - просто убить и перезапустить поток. Создайте методы resume () - создайте объект потока и запустите его - и pause () - убьете поток (см. Пример Lunarlander) - в своем классе SurfaceView и вызовите их из surfaceCreated и surfaceDestroyed для запуска и остановки потока.

Теперь в Activity, которая запускает SurfaceView, вам также потребуется вызывать методы resume () и pause () в SurfaceView из onResume () и onPause () Activity (или фрагмента). Это не элегантное решение, но оно будет работать.

4 голосов
/ 04 мая 2012

Эта ошибка, по-видимому, связана с довольно известной ошибкой лунных кораблей (выполните поиск в Google). После всего этого времени, и после нескольких выпусков версий Android, ошибка все еще существует и никто не удосужился обновить его. я нашел, что это работает с наименьшим количеством беспорядка кода:

  public void surfaceCreated(SurfaceHolder holder) {     
          if (thread.getState==Thread.State.TERMINATED) { 
               thread = new MainThread(getHolder(),this);
          }
          thread.setRunning(true);
          thread.start();
  }
2 голосов
/ 17 октября 2011

Лучший способ, который я нашел, - это переопределить метод onResume действия, управляющего видом поверхности, чтобы с помощью метода он повторно создал экземпляр SurfaceView, а затем установил его с помощью setContentView. Проблема этого подхода заключается в том, что вам необходимо перезагрузить любое состояние, о котором заботился ваш SurfaceView.

public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        setContentView(new MyCustomSurfaceView(this));
    }

    @Override
    protected void onResume() {
        super.onResume();
        setContentView(new MyCustomSurfaceView(this));
    }
2 голосов
/ 25 мая 2011
public void surfaceCreated(SurfaceHolder holder) {
        if (!_thread.isAlive()) {
            _thread = new MyThread(this, contxt);
        }

public void surfaceDestroyed(SurfaceHolder holder) {            
        boolean retry = true;
        _thread.setRunning(false);
        while (retry) {
            try {
                _thread.join();
                retry = false;
            } catch (InterruptedException e) {
                // we will try it again and again...
            }
        }
    }
1 голос
/ 08 февраля 2012

Это то, что я использовал.Приложение теперь не падает.

Просмотр класса:

holder.addCallback(new Callback() {

        public void surfaceDestroyed(SurfaceHolder holder) {
            gameLoopThread.setRunning(false);
            gameLoopThread.stop();
        }

        public void surfaceCreated(SurfaceHolder holder) {
            gameLoopThread.setRunning(true);
            gameLoopThread.start();

        }

В GameLoopThread:

private boolean running = false;

public void setRunning(boolean run) {
    running = run;
}
@Override
public void run() {
    long ticksPs=1000/FPS;
    long startTime;
    long sleepTime;

while(running){
        Canvas c = null;
        startTime=System.currentTimeMillis();
        try {
            c = view.getHolder().lockCanvas();
            synchronized (view.getHolder()) {

                view.onDraw(c);

            }

        } finally {

            if (c != null) {
                view.getHolder().unlockCanvasAndPost(c);
            }

        }
        sleepTime=ticksPs-(System.currentTimeMillis()-startTime);
        try{

            if(sleepTime>0){
                sleep(sleepTime);
            }
            else
                sleep(10);
        } catch(Exception e){}
}

}

Надеюсь, это поможет.

1 голос
/ 03 августа 2011

Пытался прокомментировать принятый ответ выше, но не смог, новичок в этом. Я не думаю, что вы должны вызывать ваши методы запуска / остановки потока из вашего SurfaceView и Activity. Это приведет к двойному запуску / остановке потока, и вы не сможете запустить поток более одного раза. Просто вызовите ваши методы из onPause и onResume. Они вызываются при выходе и повторном входе в приложение, поэтому это обеспечит правильную обработку ваших состояний. Не всегда вызывается SurfaceDestroyed, что меня напутало.

Если вы используете этот метод, обязательно проверьте правильность поверхности в вашем коде выполнения, прежде чем работать с вашим холстом, потому что Activity запустит поток в onResume до того, как поверхность станет доступна.

        while (_run) {
            if (_surfaceHolder.getSurface().isValid()) {
                ...
            }
        } //end _run
0 голосов
/ 04 июня 2011

Другое решение этой известной проблемы.К сожалению, я не понимаю, почему это работает - это вышло случайно.Но это работает хорошо для меня, и его легко реализовать: нет переопределения Activity onPause(), onResume(), onStart(), onStop(), а также написание специальных потоковых методов (таких как resume(), pause()) являются обязательными.

Особое требование - поместить все изменяющиеся переменные в нечто отличное от класса потока рендеринга.

Основные моменты, которые нужно добавить в класс потока рендеринга:

class RefresherThread extends Thread {
    static SurfaceHolder threadSurfaceHolder;
    static YourAppViewClass threadView;
    static boolean running;

    public void run (){
        while(running){
            //your amazing draw/logic cycle goes here
        }
    }
}

Важнокое-что о YourAppViewClass:

class YourAppViewClass extends SurfaceView implements SurfaceHolder.Callback  {
    static RefresherThread surfaceThread;

    public YourAppViewClass(Activity inpParentActivity) {
        getHolder().addCallback(this);
        RefresherThread.threadSurfaceHolder = getHolder();
        RefresherThread.threadView = this;
    }

    @Override
    public void surfaceCreated(SurfaceHolder holder) {
        surfaceThread = new RefresherThread();
        surfaceThread.running=true;
        surfaceThread.start();
    }

    @Override
    public void surfaceDestroyed(SurfaceHolder holder) {
        surfaceThread.running=false;
        try {
            surfaceThread.join();
        } catch (InterruptedException e) {  
        }               
    }
}

Два кодовых блока выше - это не полностью написанные классы, а просто представление о том, какие команды в каких методах нужны.Также обратите внимание, что каждое возвращение в приложение вызывает surfaceChanged().

Извините за такой объемный ответ.Я надеюсь, что это будет работать должным образом и поможет.

0 голосов
/ 20 августа 2010

Вы должны использовать методы Activity onPause () и onResume ().

Сначала, в surfaceCreated (), запустите поток.Кроме того, в onResume () убедитесь, что поток еще не запущен (сохраняйте переменную внутри потока или что-то еще).Затем, если он не работает, установите его как запущенный снова.в onPause () приостановить поток.В surfaceDestroyed снова приостановите поток.

...