2013-04-25 20 views
5

Moja aplikacja gromadzi wiele instancji, które nie mogą zostać odebrane przez GC. Ten wyciek pamięci powoduje awarię aplikacji na dłuższą metę.Dlaczego moje wątki nie umierają i nie powodują wycieku pamięci?

Nie jestem 100% pewien, skąd pochodzą, ale mam wrażenie, następujące potęgę być kod w pytaniu:

public class UraHostHttpConnection extends AbstractUraHostConnection { 
    private Handler uiThreadHandler = new Handler(Looper.getMainLooper()); 
    private Executor taskExecutor = new Executor() { 
     public void execute(Runnable command) { 
      new Thread(command).start(); 
     } 
    }; 
    private ConnectionTask task = null; 

    @Override 
    public void sendRequest(final HttpUriRequest request) { 
     this.task = new ConnectionTask(); 
     this.uiThreadHandler.post(new Runnable() { 
      public void run() { 
       task.executeOnExecutor(taskExecutor, request); 
      } 
     }); 
    } 

    @Override 
    public void cancel() { 
     if (this.task != null) 
      this.task.cancel(true); 
    } 
} 

Ten kod pozwala mi uruchomić kilka HTTP połączenia równoległe, które nie będą blokować się nawzajem na domyślnej AsyncTask(która jest tylko pojedynczą kolejką wątków).

Sprawdziłem, czy AsyncTask s faktycznie osiągają swoje metody onPostExecute() i nie działają po prostu na zawsze. Po sprawdzeniu niektórych zrzutów pamięci podejrzewam, że opakowanie nie przestaje działać po zakończeniu AsyncTask s.

Czy to możliwe, że powyższy kod jest nadal odpowiedzialny za wyciek pamięci, czy powinienem zacząć szukać gdzie indziej?

Każda pomoc jest doceniana.

Edytuj: Należy zauważyć, że sendRequest jest tylko raz wywoływana. Inne części kodu, które nie znajdują się w powyższym przykładzie, zapewniają to.

Edit 2: super klasy wygląda następująco:

public abstract class AbstractUraHostConnection { 
    protected IUraHostConnectionListener listener = null; 

    public void setListener(IUraHostConnectionListener listener) { 
     this.listener = listener; 
    } 
    public abstract void sendRequest(HttpUriRequest request); 
    public abstract void cancel(); 
} 

AsyncTask wygląda następująco:

private class ConnectionTask extends AsyncTask<HttpUriRequest, Object, Void> { 
    final byte[] buffer = new byte[2048]; 
    private ByteArrayBuffer receivedDataBuffer = new ByteArrayBuffer(524288); 

    @Override 
    protected Void doInBackground(HttpUriRequest... arg0) { 
     UraHostHttpConnection.taskCounter++; 
     AndroidHttpClient httpClient = AndroidHttpClient.newInstance("IVU.realtime.app"); 
     try { 
      // Get response and notify listener 
      HttpResponse response = httpClient.execute(arg0[0]); 
      this.publishProgress(response); 

      // Check status code OK before proceeding 
      if (response.getStatusLine().getStatusCode() == 200) { 
       HttpEntity entity = response.getEntity(); 
       InputStream inputStream = entity.getContent(); 
       int readCount = 0; 

       // Read one kB of data and hand it over to the listener 
       while ((readCount = inputStream.read(buffer)) != -1 && !this.isCancelled()) { 
        this.receivedDataBuffer.append(buffer, 0, readCount); 
        if (this.receivedDataBuffer.length() >= 524288 - 2048) { 
         this.publishProgress(receivedDataBuffer.toByteArray()); 
         this.receivedDataBuffer.clear(); 
        } 
       } 

       if (this.isCancelled()) { 
        if (arg0[0] != null && !arg0[0].isAborted()) { 
         arg0[0].abort(); 
        } 
       } 
      } 
     } catch (IOException e) { 
      // forward any errors to listener 
      e.printStackTrace(); 
      this.publishProgress(e); 
     } finally { 
      if (httpClient != null) 
       httpClient.close(); 
     } 

     return null; 
    } 

    @Override 
    protected void onProgressUpdate(Object... payload) { 
     // forward response 
     if (payload[0] instanceof HttpResponse) 
      listener.onReceiveResponse((HttpResponse) payload[0]); 
     // forward error 
     else if (payload[0] instanceof Exception) 
      listener.onFailWithException((Exception) payload[0]); 
     // forward data 
     else if (payload[0] instanceof byte[]) 
      listener.onReceiveData((byte[]) payload[0]); 
    } 

    @Override 
    protected void onPostExecute(Void result) { 
     listener.onReceiveData(this.receivedDataBuffer.toByteArray()); 
     listener.onFinishLoading(); 
     UraHostHttpConnection.taskCounter--; 
     Log.d(TAG, "There are " + UraHostHttpConnection.taskCounter + " running ConnectionTasks."); 
    } 
} 
+0

Nie bardzo wiadomo, ale może to pomóc? http://www.androiddesignpatterns.com/2013/04/activitys-threads-memory-leaks.html – dumazy

+1

Coś w konstruktorach super-klas AbstractUraHostConnection, które mogą być przerażające? Jak wygląda wygląd ConnectionTask? – ddmps

+0

Dodano kod obu klas. – Chris

Odpowiedz

1

zastąpić ThreadPoolExecutor dla Wykonawcy tak, że masz kontrolę nad wielkość basenu. Jeśli ThreadPoolExecutor jest w zasadzie Executorem z odsłoniętymi metodami, może to być przypadek, w którym domyślny maksymalny rozmiar puli jest ustawiony bardzo wysoko.

Oficjalny dokument here.

Spójrz w szczególności:

setCorePoolSize(int corePoolSize) 
//Sets the core number of threads. 

setKeepAliveTime(long time, TimeUnit unit) 
//Sets the time limit for which threads may remain idle before being terminated. 

setMaximumPoolSize(int maximumPoolSize) 
//Sets the maximum allowed number of threads. 

Jest także alternatywą, jeśli chcesz zakodować mniejszy (lepszy pomysł, w zależności od tego, ile kontrola naprawdę chcesz i ile kod będziesz handlować je zdobyć).

Executor taskExecutor = Executors.newFixedThreadPool(x); 

gdzie x = wielkość puli

+0

Dziękuję. Tak, to rozwiązuje problem. Ale z ciekawości zastanawiam się, co jest nie tak w moim kodzie. Zabijanie nieodpowiednich wątków po pewnym czasie nie jest szczególnie eleganckim rozwiązaniem :-) – Chris

+1

Odpowiedź edytowana jako alternatywa. Wątki * powinny * być GCed automagicznie, ale jak wiemy, GC robi to, co robi GC, kiedy chce, nawet gdy pytamy bezpośrednio.Żałuję, że nie wiem więcej o podstawowym mechanizmie, ale oni nie nazywają mnie Kludge za nic ... Ograniczenie puli powinno powstrzymać cię od samodzielnego polowania na zombie. – MarsAtomic