2013-04-25 10 views
5

Un'app mia sta accumulando molte istanze di Thread che il GC non può raccogliere e cancellare. Questa perdita di memoria arresta l'app a lungo termine.Perché i miei thread non muoiono e causano una perdita di memoria?

Io non sono al 100% sicuro da dove vengono, ma ho una sensazione distinta seguente potrebbe essere il codice in questione:

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

Questo codice mi permette di eseguire diversi HTTP connessioni in parallelo che non si bloccheranno reciprocamente sul valore predefinito AsyncTaskExecutor (che è solo una singola coda threaded).

Ho verificato che i AsyncTask raggiungono effettivamente i loro metodi onPostExecute() e non funzionano solo all'infinito. Dopo aver esaminato alcuni dump di memoria, sospetto che l'avvolgimento Thread -Oggetti non smetta di funzionare dopo che gli AsyncTask sono stati completati.

È possibile che il codice sopra sia ancora responsabile della perdita di memoria o dovrei iniziare a cercare altrove?

Qualsiasi aiuto è apprezzato.

Modifica: Si noti che lo sendRequest viene chiamato solo una volta. Altre parti del codice non presenti nel campione qui sopra sono sicure di questo.

Edit 2: il super-classe assomiglia a questo:

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

L'AsyncTask assomiglia a questo:

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

Non proprio sicuro, ma può questo aiuto? http://www.androiddesignpatterns.com/2013/04/activitys-threads-memory-leaks.html – dumazy

+1

Qualcosa nei costruttori delle super classi di AbstractUraHostConnection che potrebbero essere spaventose? Inoltre, come appare ConnectionTask? – ddmps

+0

Codice aggiunto di entrambe le classi. – Chris

risposta

1

sostituire un ThreadPoolExecutor per il vostro esecutore in modo da avere il controllo sulla dimensione della piscina Se ThreadPoolExecutor è fondamentalmente un Executor con metodi esposti, potrebbe essere solo un caso in cui la dimensione massima predefinita del pool è impostata su un valore molto alto.

Doc ufficiale here.

Date un'occhiata in particolare a:

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. 

C'è anche un'alternativa se si vuole codificare minore (migliore idea a seconda di quanto il controllo si vuole veramente e quanto il codice si scambi per ottenerlo).

Executor taskExecutor = Executors.newFixedThreadPool(x); 

dove x = la dimensione della vostra piscina

+0

Grazie. Sì, questo risolve il problema. Ma per curiosità mi chiedo cosa non funzioni nel mio codice. Uccidere fili non rispondenti dopo un po 'non è una soluzione particolarmente elegante :-) – Chris

+1

Risposta modificata per un'alternativa. I thread * dovrebbero * essere sottoposti a GC automaticamente, ma, come sappiamo, GC fa ciò che fa GC, quando lo desidera, anche quando lo chiediamo direttamente.Vorrei sapere di più sul meccanismo di base, ma non mi chiamano Kludge per niente ... Inoltre, limitare la piscina dovrebbe impedirti di dare la caccia agli zombi da solo. – MarsAtomic

Problemi correlati