web-dev-qa-db-ja.com

surfaceViewスレッドを一時停止および再開する方法

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

簡単な解決策は、スレッドを強制終了して再起動することです。 Createメソッドresume()-スレッドオブジェクトを作成して開始します-そしてpause()-スレッドを強制終了します(Lunarlanderの例を参照)-SurfaceViewクラスでこれらをsurfaceCreatedとsurfaceDestroyedから呼び出して、スレッドを開始および停止します。

これで、SurfaceViewを実行するアクティビティで、アクティビティ(またはフラグメント)のonResume()およびonPause()からSurfaceViewのresume()メソッドとpause()メソッドを呼び出す必要があります。これは洗練されたソリューションではありませんが、機能します。

11
Michael A.

このバグは、非常に有名な月着陸船のバグに関連しているようです(Google検索を実行してください)。この間ずっと、そしていくつかのAndroidバージョンのリリースの後、バグはまだ存在し、誰もそれを更新することを気にしませんでした。私はこれが最小のコードクラッターで動作することを発見しました:

  public void surfaceCreated(SurfaceHolder holder) {     
          if (thread.getState==Thread.State.TERMINATED) { 
               thread = new MainThread(getHolder(),this);
          }
          thread.setRunning(true);
          thread.start();
  }
4
Androidcoder
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...
            }
        }
    }
2
AZ_

私が見つけた最善の方法は、サーフェスビューを制御するアクティビティの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
tjb

上記の受け入れられた答えにコメントしようとしましたが、これは初めてでした。 SurfaceViewとActivityの両方からstart/stopスレッドメソッドを呼び出すべきではないと思います。これにより、スレッドが二重に開始/停止され、スレッドを複数回開始することはできません。アクティビティのonPauseとonResumeからメソッドを呼び出すだけです。これらはアプリを終了して再入力するときに呼び出されるため、状態が適切に処理されます。 surfaceDestroyedが常に呼び出されるとは限らないため、しばらくの間混乱しました。

このメソッドを使用する場合は、キャンバスを操作する前に、実行コードで有効なサーフェスを確認してください。これは、サーフェスが使用可能になる前にアクティビティがonResumeでスレッドを開始するためです。

        while (_run) {
            if (_surfaceHolder.getSurface().isValid()) {
                ...
            }
        } //end _run
1
Matt K

これは私が使用したものです。アプリはクラッシュしません。

クラスの表示:

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
Danish Sajwani

アクティビティのonPause()メソッドとonResume()メソッドを使用する必要があります。

まず、surfaceCreated()でスレッドを開始します。また、onResume()で、スレッドがまだ開始されていないことを確認します(スレッド内に変数を保持するなど)。次に、実行されていない場合は、再度実行するように設定します。 onPause()で、スレッドを一時停止します。 surfaceDestroyedで、スレッドを再度一時停止します。

0
Moncader

このよく知られた問題の別の解決策。悲しいことに、なぜそれが機能するのかわかりません-それは偶然に出てきました。しかし、それは私にとってはうまく機能し、実装も簡単です。ActivityonPause()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) {  
        }               
    }
}

上記の2つのコードブロックは完全に記述されたクラスではなく、どのコマンドでどのメソッドが必要かという単なる概念です。また、アプリに戻るたびにsurfaceChanged()が呼び出されることにも注意してください。

そのようなスペースを消費する答えを申し訳ありません。私はそれが適切に機能し、役立つことを願っています。

0
fyodorananiev