android custom - supporto FragmentPagerAdapter detiene il riferimento ai vecchi frammenti




6 Answers

Stai incontrando un problema perché stai PagerAdapter.getItem un'istanza e mantenendo i riferimenti ai tuoi frammenti al di fuori di PagerAdapter.getItem e stai tentando di utilizzare tali riferimenti indipendentemente dal ViewPager. Come dice Seraph, hai la garanzia che un frammento è stato istanziato / aggiunto in un ViewPager in un determinato momento - questo dovrebbe essere considerato un dettaglio di implementazione. Un ViewPager fa il lazy caricamento delle sue pagine; per impostazione predefinita carica solo la pagina corrente e quella a sinistra ea destra.

Se metti la tua app in background, i frammenti che sono stati aggiunti al gestore dei frammenti vengono salvati automaticamente. Anche se la tua app viene uccisa, queste informazioni vengono ripristinate al riavvio della tua app.

Ora considera di aver visto alcune pagine, Frammenti A, B e C. Sai che questi sono stati aggiunti al gestore dei frammenti. Poiché stai utilizzando FragmentPagerAdapter e non FragmentStatePagerAdapter , questi frammenti verranno comunque aggiunti (ma potenzialmente distaccati) quando scorri verso altre pagine.

Considera che quindi esegui lo sfondo della tua applicazione e poi viene ucciso. Quando torni, Android ricorderà che hai usato Frammenti A, B e C nel gestore dei frammenti e quindi li ricrea per te e poi li aggiunge. Tuttavia, quelli che vengono aggiunti al gestore dei frammenti ora NON sono quelli presenti nell'elenco dei frammenti nella tua attività.

FragmentPagerAdapter non tenterà di chiamare getPosition se esiste già un frammento aggiunto per quella particolare posizione della pagina. Infatti, poiché il frammento ricreato da Android non verrà mai rimosso, non hai alcuna speranza di sostituirlo con una chiamata a getPosition . Ottenere un controllo su di esso è anche piuttosto difficile da ottenere un riferimento perché è stato aggiunto con un tag sconosciuto. Questo è di design; sei scoraggiato dall'avere a che fare con i frammenti gestiti dal visualizzatore di pagine. Dovresti eseguire tutte le azioni all'interno di un frammento, comunicare con l'attività e richiedere di passare a una determinata pagina, se necessario.

Ora, torna al tuo problema con l'attività mancante. Chiamando pagerAdapter.getItem(1)).update(id, name) dopo che tutto ciò è accaduto, si restituisce il frammento nella lista, che deve ancora essere aggiunto al gestore dei frammenti , e quindi non avrà un riferimento di attività. Vorrei suggerire che il tuo metodo di aggiornamento dovrebbe modificare alcune strutture di dati condivise (eventualmente gestite dall'attività), quindi quando ti sposti su una determinata pagina può disegnare se stesso sulla base di questi dati aggiornati.

instantiateitem viewpager

ULTIME INFO:

Ho ridotto il mio problema a un problema con frammentManager che conserva istanze di vecchi frammenti e il mio viewpager non è sincronizzato con il mio FragmentManager. Vedi questo problema ... http://code.google.com/p/android/issues/detail?id=19211#makechanges . Non ho ancora idea di come risolvere questo problema. Eventuali suggerimenti...

Ho provato a eseguire il debug di questo per molto tempo e qualsiasi aiuto sarebbe molto apprezzato. Sto usando un FragmentPagerAdapter che accetta un elenco di frammenti in questo modo:

List<Fragment> fragments = new Vector<Fragment>();
fragments.add(Fragment.instantiate(this, Fragment1.class.getName())); 
...
new PagerAdapter(getSupportFragmentManager(), fragments);

L'implementazione è standard. Sto usando ActionBarSherlock e la libreria di computabilità v4 per Fragments.

Il mio problema è che dopo aver abbandonato l'app e aver aperto diverse altre applicazioni e aver fatto ritorno, i frammenti perdono il loro riferimento a FragmentActivity (ad esempio getActivity() == null ). Non riesco a capire perché questo sta accadendo. Ho provato a impostare manualmente setRetainInstance(true); ma questo non aiuta. Ho capito che questo succede quando il mio FragmentActivity viene distrutto, tuttavia questo succede ancora se apro l'app prima di ricevere il messaggio di log. Ci sono idee?

@Override
protected void onDestroy(){
    Log.w(TAG, "DESTROYDESTROYDESTROYDESTROYDESTROYDESTROYDESTROY");
    super.onDestroy();
}

L'adattatore:

public class PagerAdapter extends FragmentPagerAdapter {
    private List<Fragment> fragments;

    public PagerAdapter(FragmentManager fm, List<Fragment> fragments) {
        super(fm);

        this.fragments = fragments;

    }

    @Override
    public Fragment getItem(int position) {

        return this.fragments.get(position);

    }

    @Override
    public int getCount() {

        return this.fragments.size();

    }

}

Uno dei miei frammenti è stato rimosso, ma ho commesso tutto ciò che è stato rimosso e ancora non funziona ...

public class MyFragment extends Fragment implements MyFragmentInterface, OnScrollListener {
...

@Override
public void onCreate(Bundle savedInstanceState){
    super.onCreate(savedInstanceState);
    handler = new Handler();    
    setHasOptionsMenu(true);
}

@Override
public void onAttach(Activity activity) {
    super.onAttach(activity);
    Log.w(TAG,"ATTACHATTACHATTACHATTACHATTACH");
    context = activity;
    if(context== null){
        Log.e("IS NULL", "NULLNULLNULLNULLNULLNULLNULLNULLNULLNULLNULL");
    }else{
        Log.d("IS NOT NULL", "NOTNOTNOTNOTNOTNOTNOTNOT");
    }

}

@Override
public void onActivityCreated(Bundle savedState) {
    super.onActivityCreated(savedState);
}

@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
    View v = inflater.inflate(R.layout.my_fragment,container, false);

    return v;
}


@Override
public void onResume(){
    super.onResume();
}

private void callService(){
    // do not call another service is already running
    if(startLoad || !canSet) return;
    // set flag
    startLoad = true;
    canSet = false;
    // show the bottom spinner
    addFooter();
    Intent intent = new Intent(context, MyService.class);
    intent.putExtra(MyService.STATUS_RECEIVER, resultReceiver);
    context.startService(intent);
}

private ResultReceiver resultReceiver = new ResultReceiver(null) {
    @Override
    protected void onReceiveResult(int resultCode, final Bundle resultData) {
        boolean isSet = false;
        if(resultData!=null)
        if(resultData.containsKey(MyService.STATUS_FINISHED_GET)){
            if(resultData.getBoolean(MyService.STATUS_FINISHED_GET)){
                removeFooter();
                startLoad = false;
                isSet = true;
            }
        }

        switch(resultCode){
        case MyService.STATUS_FINISHED: 
            stopSpinning();
            break;
        case SyncService.STATUS_RUNNING:
            break;
        case SyncService.STATUS_ERROR:
            break;
        }
    }
};

public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
    menu.clear();
    inflater.inflate(R.menu.activity, menu);
}

@Override
public void onPause(){
    super.onPause();
}

public void onScroll(AbsListView arg0, int firstVisible, int visibleCount, int totalCount) {
    boolean loadMore = /* maybe add a padding */
        firstVisible + visibleCount >= totalCount;

    boolean away = firstVisible+ visibleCount <= totalCount - visibleCount;

    if(away){
        // startLoad can now be set again
        canSet = true;
    }

    if(loadMore) 

}

public void onScrollStateChanged(AbsListView arg0, int state) {
    switch(state){
    case OnScrollListener.SCROLL_STATE_FLING: 
        adapter.setLoad(false); 
        lastState = OnScrollListener.SCROLL_STATE_FLING;
        break;
    case OnScrollListener.SCROLL_STATE_IDLE: 
        adapter.setLoad(true);
        if(lastState == SCROLL_STATE_FLING){
            // load the images on screen
        }

        lastState = OnScrollListener.SCROLL_STATE_IDLE;
        break;
    case OnScrollListener.SCROLL_STATE_TOUCH_SCROLL:
        adapter.setLoad(true);
        if(lastState == SCROLL_STATE_FLING){
            // load the images on screen
        }

        lastState = OnScrollListener.SCROLL_STATE_TOUCH_SCROLL;
        break;
    }
}

@Override
public void onDetach(){
    super.onDetach();
    if(this.adapter!=null)
        this.adapter.clearContext();

    Log.w(TAG, "DETACHEDDETACHEDDETACHEDDETACHEDDETACHEDDETACHED");
}

public void update(final int id, String name) {
    if(name!=null){
        getActivity().getSupportActionBar().setTitle(name);
    }

}

}

Il metodo di aggiornamento viene chiamato quando un utente interagisce con un diverso frammento e getActivity restituisce null. Ecco il metodo che l'altro frammento sta chiamando ...

((MyFragment) pagerAdapter.getItem(1)).update(id, name);

Credo che quando l'app viene distrutta e poi creata di nuovo invece di avviare l'applicazione fino al frammento predefinito, l'app si avvia e quindi viewpager naviga fino all'ultima pagina conosciuta. Sembra strano, l'app non dovrebbe caricare solo il frammento predefinito?




Ho risolto questo problema accedendo ai miei frammenti direttamente attraverso FragmentManager invece che tramite FragmentPagerAdapter in questo modo. Per prima cosa ho bisogno di capire il tag del frammento generato automaticamente da FragmentPagerAdapter ...

private String getFragmentTag(int pos){
    return "android:switcher:"+R.id.viewpager+":"+pos;
}

Quindi ottengo semplicemente un riferimento a quel frammento e faccio quello che mi serve in questo modo ...

Fragment f = this.getSupportFragmentManager().findFragmentByTag(getFragmentTag(1));
((MyFragmentInterface) f).update(id, name);
viewPager.setCurrentItem(1, true);

All'interno dei miei frammenti ho impostato setRetainInstance(false); in modo che io possa aggiungere manualmente valori al pacchetto savedInstanceState.

@Override
public void onSaveInstanceState(Bundle outState) {
    if(this.my !=null)
        outState.putInt("myId", this.my.getId());

    super.onSaveInstanceState(outState);
}

e poi nell'OnCreate afferro quella chiave e ripristino lo stato del frammento secondo necessità. Una soluzione facile che era difficile (almeno per me) capire.




Non provare a interagire tra i frammenti in ViewPager. Non è possibile garantire che un altro frammento sia allegato o addirittura esistente. Prima di cambiare il titolo della barra delle azioni da un frammento, puoi farlo dalla tua attività. Usa uno schema di interfaccia standard per questo:

public interface UpdateCallback
{
    void update(String name);
}

public class MyActivity extends FragmentActivity implements UpdateCallback
{
    @Override
    public void update(String name)
    {
        getSupportActionBar().setTitle(name);
    }

}

public class MyFragment extends Fragment
{
    private UpdateCallback callback;

    @Override
    public void onAttach(SupportActivity activity)
    {
        super.onAttach(activity);
        callback = (UpdateCallback) activity;
    }

    @Override
    public void onDetach()
    {
        super.onDetach();
        callback = null;
    }

    public void updateActionbar(String name)
    {
        if(callback != null)
            callback.update(name);
    }
}



Puoi rimuovere i frammenti quando distruggi il viewpager, nel mio caso li ho rimossi su onDestroyView() del mio frammento:

@Override
public void onDestroyView() {

    if (getChildFragmentManager().getFragments() != null) {
        for (Fragment fragment : getChildFragmentManager().getFragments()) {
            getChildFragmentManager().beginTransaction().remove(fragment).commitAllowingStateLoss();
        }
    }

    super.onDestroyView();
}



La mia soluzione: ho impostato quasi tutte le viste come static . Ora la mia app interagisce perfettamente. Essere in grado di chiamare i metodi statici da ovunque non è forse un buon stile, ma perché giocare con il codice che non funziona? Ho letto un sacco di domande e le loro risposte qui su SO e nessuna soluzione ha portato il successo (per me).

So che può perdere la memoria e sprecare il mucchio, e il mio codice non sarà adatto ad altri progetti, ma non mi sento spaventato da questo: ho provato l'app su diversi dispositivi e condizioni, nessun problema, l'Android La piattaforma sembra essere in grado di gestirlo. L'interfaccia utente viene aggiornata ogni secondo e anche su un dispositivo S2 ICS (4.0.3) l'app è in grado di gestire migliaia di indicatori geografici.




Ho risolto il problema salvando i frammenti in SparceArray:

public abstract class SaveFragmentsPagerAdapter extends FragmentPagerAdapter {

    SparseArray<Fragment> fragments = new SparseArray<>();

    public SaveFragmentsPagerAdapter(FragmentManager fm) {
        super(fm);
    }

    @Override
    public Object instantiateItem(ViewGroup container, int position) {
        Fragment fragment = (Fragment) super.instantiateItem(container, position);
        fragments.append(position, fragment);
        return fragment;
    }

    @Nullable
    public Fragment getFragmentByPosition(int position){
        return fragments.get(position);
    }

}



Related

android android-fragments