8

Il mio problema riguarda un'attività che ospita tre frammenti di supporto. Uno è un normale frammento programmatico (chiamiamolo un frammento di casa). Uno è un frammento di ritratto aggiunto sopra il frammento di casa quando il dispositivo è orientato e uno è "senza testa", per continuare un'attività asincrona indipendentemente dalle modifiche di configurazione. Molto semplice, stavo lavorando su this nice example.findFragmentByTag null per Fragment A, se setRetain (true) su Fragment B

public class HeadlessCustomerDetailFetchFragment extends Fragment{ 
private RequestCustomerDetails mRequest; 
private AsyncFetchCustomerDetails mAsyncFetchCustomerDetails; 

@Override 
public void onCreate(Bundle savedInstanceState) { 
    super.onCreate(savedInstanceState); 
    setRetainInstance(true); 

    mRequest = (RequestCustomerDetails)getActivity(); 
} 

public void startFetching(String scannedBarcode) { 
    if(mAsyncFetchCustomerDetails != null && mAsyncFetchCustomerDetails.getStatus() == AsyncTask.Status.RUNNING) return; 

    if(mAsyncFetchCustomerDetails == null || mAsyncFetchCustomerDetails.getStatus() == AsyncTask.Status.FINISHED) 
     mAsyncFetchCustomerDetails = new AsyncFetchCustomerDetails(getActivity(), mRequest, mPartner, scannedBarcode); 
} 

public void stopFetching() { 
    if(mAsyncFetchCustomerDetails != null && mAsyncFetchCustomerDetails.getStatus() != AsyncTask.Status.RUNNING) return; 
    mAsyncFetchCustomerDetails.cancel(true); 
} 

}

In onCreate di mia attività() creo e aggiungi il frammento senza testa, se necessario.

mHeadlessCustomerDetailFetchFragment = (HeadlessCustomerDetailFetchFragment)getSupportFragmentManager() 
      .findFragmentByTag(HeadlessCustomerDetailFetchFragment.class.getSimpleName()); 

if(mHeadlessCustomerDetailFetchFragment == null) { 
     mHeadlessCustomerDetailFetchFragment = HeadlessCustomerDetailFetchFragment.instantiate(this, HeadlessCustomerDetailFetchFragment.class.getName()); 
    getSupportFragmentManager().beginTransaction() 
      .add(mHeadlessCustomerDetailFetchFragment, mHeadlessCustomerDetailFetchFragment.getClass().getSimpleName()) 
      .commit(); 
    getSupportFragmentManager().executePendingTransactions(); 
     id = null; 
    } 

Allora lancio un compito asincrona (via() funzione mio startFetching) dopo un ritardo di 6 secondi (per la prova) ha dato il via nel onCreateView() del frammento ritratto che viene aggiunto quando cambia l'orientamento a ritratto . La variazione dell'orientamento viene rilevata onCreate della attività():

if (savedInstanceState == null) { 
    // Do some initial stuff for the home fragment 
} 
else { 
    getSupportFragmentManager().popBackStackImmediate(null, FragmentManager.POP_BACK_STACK_INCLUSIVE); 
    if (getResources().getConfiguration().orientation == Configuration.ORIENTATION_PORTRAIT) { 
     //Launch portrait fragment 
     FragmentLauncher.launchPortraitFragment(this); 
    } 

Quando l'operazione è terminata, ritorno all'attività e tentare di aggiornare l'interfaccia utente del frammento ritratto attivo, ma il direttore frammento non può trovare, findFragmentByTag() restituisce null.

Per essere chiari:

  • Il tag è corretta
  • Il frammento si trova se lo faccio non orientare il dispositivo, e invece dare il via l'operazione asincrona da qualche altra parte, durante onResume la propria attività () per esempio.
  • Se non dico al frammento senza testa di conservare se stesso - perdendo così il vantaggio di non ricrearlo, anche il frammento di ritratto viene trovato correttamente.
  • Debugaggio È possibile visualizzare tutti i 3 frammenti nel gestore se l'unità senza testa non è impostata per conservare se stessa. Se lo è, posso solo vedere il frammento senza testa.

Forse conservare un frammento in modo aggressivo uccide altri frammenti che non vengono conservati o qualcosa del genere?

+0

Perché non puoi semplicemente eseguire il tuo asynctask in un servizio ?. Credo che questo elimina il tuo problema di rotazione ed è anche un modo più ordinato per farlo ?. – Smashing

+0

Lo penso anch'io - non ne so molto dei servizi e mi è sembrato eccessivo per un compito, ma potrebbe essere la mia unica opzione - lo esamineremo grazie a –

+1

Coolbeans. Fammi sapere se hai bisogno di aiuto. – Smashing

risposta

4

La radice del problema è come si mantiene il riferimento all'attività all'interno del frammento senza testa.
Non è chiaro dal codice fornito come si aggiorna l'interfaccia utente dopo il completamento di AsyncTask, si assume che si utilizzi mRequest dal primo snippet di codice. Dare mRequest al costruttore quando è necessario un nuovo AsyncTask e utilizzare questo riferimento dopo il completamento di AsyncTask.
È ok, quando non si ha alcuna rotazione dello schermo tra il momento in cui viene creata l'attività e quando l'interfaccia utente viene aggiornata. È perché usi il riferimento all'attività che è ancora attiva.
Non va bene se si ruota lo schermo. Hai nuove attività ogni volta dopo la rotazione. Ma mRequest viene assegnato una sola volta quando si crea un frammento senza testa nel primo numero di attività onCreate(). Quindi contiene un riferimento alla prima istanza di attività che non è attiva dopo la rotazione. Ci sono 2 istanze di attività dopo la rotazione nel tuo caso: la prima - a cui fa riferimento mRequest e la seconda - che è visibile e attiva.È possibile confermare ciò registrando il riferimento di attività all'interno di onCreate: Log.i(TAG, "onCreate: this=" + this); e all'interno del metodo di attività che aggiorna l'interfaccia utente dopo un'attività asincrona: Log.i(TAG, "updating UI: this=" + this);
Oltre alla prima attività è in stato Distrutto. Tutti i frammenti vengono separati da questa attività e i frammenti non conservati vengono distrutti. Ecco perché findFragmentByTag restituisce null.
Se il frammento senza testa non è impostato per conservare se stesso, quindi l'attività onCreate() lo ricrea in ogni chiamata. Pertanto, mRequest fa sempre riferimento all'ultima attività creata con tutti i frammenti. In questo caso, findFragmentByTag restituisce non null.

per evitare questo problema suggerisco:

  1. Usa debole riferimento al negozio di riferimento di attività. Qualcosa di simile:
    private WeakReference<RequestCustomerDetails> mRequest;
  2. Creare un metodo in HeadlessCustomerDetailFetchFragment per aggiornare questo riferimento.
    public void updateResultProcessor(RequestCustomerDetails requestCustomerDetails) { mRequest = new WeakReference(requestCustomerDetails); // Update ui if there is stored result of AsyncTask (see p.4b) }
  3. Chiamare questo metodo dall'attività onCreate() ogni volta.
  4. Al termine di AsyncTask:
    a) se mRequest.get() non è null quindi aggiornare l'interfaccia utente.
    b) se mRequest.get() è null quindi memorizzare il risultato all'interno di un frammento senza testa e utilizzarlo in p.2.

    Un riferimento debole consentirà a GC di elaborare l'attività distrutta e impostare null all'interno del riferimento debole. Null all'interno di un riferimento debole segnalerà che non c'è UI e nulla da aggiornare. La memorizzazione del risultato di AsyncTask in un frammento senza testa consentirà di utilizzare questo risultato per l'aggiornamento dell'interfaccia utente dopo la sua ricreazione.

    Spero che questo possa essere d'aiuto. Mi scusi per il mio inglese. Se qualcosa non è chiaro cercherò di spiegare.
+0

Approfondiremo più tardi ma ecco la taglia, sembra molto una risposta corretta quindi grazie –