Где остановить / уничтожить потоки в классе Android Service? - PullRequest
36 голосов
/ 25 марта 2009

Я создал многопоточный сервис следующим образом:

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);
         }
    }
}

...
}

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

Кто-нибудь знает, каков наилучший способ завершить все потоки перед завершением приложения?

Ответы [ 2 ]

89 голосов
/ 26 марта 2009

Приложение : платформа Android предоставляет множество помощников для одноразовой работы, фоновой работы и т. Д., Что может быть предпочтительнее, чем попытка прокрутить собственный поток во многих случаях. Как упомянуто в посте ниже, 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) Вы создаете новый сокет каждую итерацию отправляющего потока. Это довольно тяжелая операция, и она действительно имеет смысл, если вы ожидаете, что измерения будут происходить крайне редко (скажем, один час или меньше). Либо вам нужен постоянный сокет, который не воссоздается в каждом цикле потока, либо вы хотите запустить один выстрел, который вы можете «запустить и забыть», который создает сокет, отправляет все соответствующие данные и завершает работу. (Краткое примечание об использовании постоянного сокета, методы сокетов, блокирование которых, например чтение, не может быть прервано 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) после создания и перед запуском потока. Это помечает поток как поток демона, что означает, что виртуальная машина будет гарантировать его автоматическое уничтожение, если не запущены потоки, не являющиеся демонами (например, если ваше приложение закрывается).

Соблюдение передовых методов работы с потоками должно гарантировать, что ваше приложение не зависает и не замедляет работу телефона, хотя они могут быть довольно сложными:)

6 голосов
/ 10 мая 2009

На самом деле вам не нужна переменная "runner", как описано выше, что-то вроде:

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

Но в общем случае сидеть в цикле Thread.sleep () - очень плохая идея.

Посмотрите на API AsyncTask в новом 1.5 API. Это, вероятно, решит вашу проблему более элегантно, чем использование сервиса. Ваш телефон работает медленно, потому что служба никогда не отключается - нет ничего, что могло бы заставить службу убить себя.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...