web-dev-qa-db-ja.com

Androidサービスクラスでスレッドを停止/破棄する場所は?

次の方法でスレッドサービスを作成しました。

public class TCPClientService extends Service{  
...

@Override
public void onCreate() {
    ...
    Measurements = new LinkedList<String>();
    enableDataSending();    
}

@Override
public IBinder onBind(Intent intent) {
    //TODO: Replace with service binding implementation
    return null;
}

@Override
public void onLowMemory() {
    Measurements.clear();
    super.onLowMemory();
}

@Override
public void onDestroy() {
    Measurements.clear();
    super.onDestroy();
    try {
        SendDataThread.stop();
    } catch(Exception e){
        ...     
    }

}

private Runnable backgrounSendData = new Runnable() {

    public void run() {
        doSendData();
    }
};

private void enableDataSending() {
    SendDataThread = new Thread(null, backgrounSendData, "send_data");
    SendDataThread.start();
}

 private void addMeasurementToQueue() {
     if(Measurements.size() <= 100) {
         String measurement = packData();
         Measurements.add(measurement);
     }
 }

 private void doSendData() {
     while(true) {
         try {      
             if(Measurements.isEmpty()) {
                 Thread.sleep(1000);
                 continue;
             }
             //Log.d("TCP", "C: Connecting...");
             Socket socket = new Socket();
             socket.setTcpNoDelay(true);
             socket.connect(new InetSocketAddress(serverAddress, portNumber), 3000);
             //socket.connect(new InetSocketAddress(serverAddress, portNumber));
             if(!socket.isConnected()) {
                 throw new Exception("Server Unavailable!");
             }
             try {
                 //Log.d("TCP", "C: Sending: '" + message + "'");
                 PrintWriter out = new PrintWriter( new BufferedWriter( new OutputStreamWriter(socket.getOutputStream())),true);
                 String message = Measurements.remove();
                 out.println(message);
                 Thread.sleep(200);
                 Log.d("TCP", "C: Sent.");
                 Log.d("TCP", "C: Done.");
                 connectionAvailable = true;              
             } catch(Exception e) {
                 Log.e("TCP", "S: Error", e);
                 connectionAvailable = false;
             } finally {
                 socket.close();
                 announceNetworkAvailability(connectionAvailable);
             }
         } catch (Exception e) {
             Log.e("TCP", "C: Error", e);
             connectionAvailable = false;
             announceNetworkAvailability(connectionAvailable);
         }
    }
}

...
}

アプリケーションを閉じた後、電話の動作が非常に遅くなります。これは、スレッド終了エラーが原因であると思われます。

アプリケーションを終了する前にすべてのスレッドを終了する最良の方法は何ですか?

37
Niko Gamulin

補遺:Androidフレームワークは、1回限りの作業、バックグラウンド作業などに役立つ多くのヘルパーを提供します。以下の投稿で述べたように、AsyncTaskは検討するのに適した出発点です。読者は、独自のスレッド化を検討する前に、まずフレームワークの規定を検討することをお勧めします。

投稿したコードサンプルにはいくつかの問題があり、順番に対処します。

1)Thread.stop()は、状況によっては従属変数を一貫性のない状態のままにしてしまう可能性があるため、かなり長い間廃止されています。詳細については このSunの回答ページ を参照してください(編集:そのリンクは現在無効です。 Thread.stop()を使用しない理由についてはこのページを参照 )。スレッドを停止および開始する好ましい方法は次のとおりです(スレッドがある程度無期限に実行されると仮定します)。

private volatile Thread runner;

public synchronized void startThread(){
  if(runner == null){
    runner = new Thread(this);
    runner.start();
  }
}

public synchronized void stopThread(){
  if(runner != null){
    Thread moribund = runner;
    runner = null;
    moribund.interrupt();
  }
}

public void run(){
  while(Thread.currentThread() == runner){
    //do stuff which can be interrupted if necessary
  }
}

これはスレッドを停止する方法の一例にすぎませんが、重要なことは、他のメソッドと同じようにスレッドを終了する責任があるということです。クロススレッド通信の方法を維持し(この場合、揮発性変数はミューテックスなどを通じても可能です)、スレッドロジック内で、その通信方法を使用して、早期終了、クリーンアップなどを行うかどうかを確認します。

2)同期せずに、複数のスレッド(イベントスレッドとユーザースレッド)が同時に測定リストにアクセスします。独自の同期を実行する必要はないようです。 BlockingQueue を使用できます。

3)送信スレッドの反復ごとに新しいソケットを作成しています。これはかなりヘビーウェイトな操作であり、測定が非常にまれ(1時間に1回以下)になると予想される場合にのみ、本当に意味があります。スレッドのループごとに再作成されない永続的なソケットが必要な場合、またはソケットを作成し、関連するすべてのデータを送信して終了する「発射して忘れられる」ワンショット実行可能ファイルが必要な場合。 (永続的なソケットの使用に関する簡単なメモ、読み取りなどをブロックするソケットメソッドはThread.interrupt()によって中断されないため、スレッドを停止する場合は、ソケットを閉じて割り込みを呼び出す必要があります)

4)他の場所でキャッチする予定がない限り、スレッド内から独自の例外をスローしてもほとんど意味がありません。より良い解決策は、エラーをログに記録し、回復できない場合はスレッドを停止することです。スレッドは(上記と同じコンテキストで)次のようなコードで自身を停止できます。

public void run(){
    while(Thread.currentThread() == runner){
      //do stuff which can be interrupted if necessary

      if(/*fatal error*/){
        stopThread();
        return; //optional in this case since the loop will exit anyways
      }
    }
  }

最後に、アプリケーションの残りの部分でスレッドが終了することを確認したい場合は、作成後、スレッドを開始する前にThread.setDaemon(true)を呼び出すことをお勧めします。これは、スレッドをデーモンスレッドとしてフラグを立てます。つまり、VMは、実行中の非デーモンスレッドがない場合(アプリが終了した場合など)に自動的に破棄されることを保証します。

スレッドに関するベストプラクティスに従うことで、アプリがハングアップしたり電話が遅くなったりすることはありませんが、非常に複雑になる可能性があります:)

89
sooniln

実際、上記のような「runner」変数は必要ありません。次のようなものです。

while (!interrupted()) {
    try {
        Thread.sleep(1000);
    } catch (InterruptedException ex) {
        break;
    }
}

しかし、一般的に、Thread.sleep()ループに座っているのは本当に悪い考えです。

新しい1.5 APIのAsyncTask APIを見てください。おそらく、サービスを使用するよりもエレガントに問題を解決するでしょう。サービスがシャットダウンすることはないため、電話の速度が低下しています。サービス自体が停止することはありません。

6
Mike Hearn