OnCreateView ve onActivityCreated parçası iki kez çağrıldı


101

Android 4.0 ICS ve parçalarını kullanarak bir uygulama geliştiriyorum.

ICS 4.0.3 (API seviyesi 15) API'nin demo örnek uygulamasındaki bu değiştirilmiş örneği düşünün:

public class FragmentTabs extends Activity {

private static final String TAG = FragmentTabs.class.getSimpleName();

@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);

    final ActionBar bar = getActionBar();
    bar.setNavigationMode(ActionBar.NAVIGATION_MODE_TABS);
    bar.setDisplayOptions(0, ActionBar.DISPLAY_SHOW_TITLE);

    bar.addTab(bar.newTab()
            .setText("Simple")
            .setTabListener(new TabListener<SimpleFragment>(
                    this, "mysimple", SimpleFragment.class)));

    if (savedInstanceState != null) {
        bar.setSelectedNavigationItem(savedInstanceState.getInt("tab", 0));
        Log.d(TAG, "FragmentTabs.onCreate tab: " + savedInstanceState.getInt("tab"));
        Log.d(TAG, "FragmentTabs.onCreate number: " + savedInstanceState.getInt("number"));
    }

}

@Override
protected void onSaveInstanceState(Bundle outState) {
    super.onSaveInstanceState(outState);
    outState.putInt("tab", getActionBar().getSelectedNavigationIndex());
}

public static class TabListener<T extends Fragment> implements ActionBar.TabListener {
    private final Activity mActivity;
    private final String mTag;
    private final Class<T> mClass;
    private final Bundle mArgs;
    private Fragment mFragment;

    public TabListener(Activity activity, String tag, Class<T> clz) {
        this(activity, tag, clz, null);
    }

    public TabListener(Activity activity, String tag, Class<T> clz, Bundle args) {
        mActivity = activity;
        mTag = tag;
        mClass = clz;
        mArgs = args;

        // Check to see if we already have a fragment for this tab, probably
        // from a previously saved state.  If so, deactivate it, because our
        // initial state is that a tab isn't shown.
        mFragment = mActivity.getFragmentManager().findFragmentByTag(mTag);
        if (mFragment != null && !mFragment.isDetached()) {
            Log.d(TAG, "constructor: detaching fragment " + mTag);
            FragmentTransaction ft = mActivity.getFragmentManager().beginTransaction();
            ft.detach(mFragment);
            ft.commit();
        }
    }

    public void onTabSelected(Tab tab, FragmentTransaction ft) {
        if (mFragment == null) {
            mFragment = Fragment.instantiate(mActivity, mClass.getName(), mArgs);
            Log.d(TAG, "onTabSelected adding fragment " + mTag);
            ft.add(android.R.id.content, mFragment, mTag);
        } else {
            Log.d(TAG, "onTabSelected attaching fragment " + mTag);
            ft.attach(mFragment);
        }
    }

    public void onTabUnselected(Tab tab, FragmentTransaction ft) {
        if (mFragment != null) {
            Log.d(TAG, "onTabUnselected detaching fragment " + mTag);
            ft.detach(mFragment);
        }
    }

    public void onTabReselected(Tab tab, FragmentTransaction ft) {
        Toast.makeText(mActivity, "Reselected!", Toast.LENGTH_SHORT).show();
    }
}

public static class SimpleFragment extends Fragment {
    TextView textView;
    int mNum;

    /**
     * When creating, retrieve this instance's number from its arguments.
     */
    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        Log.d(FragmentTabs.TAG, "onCreate " + (savedInstanceState != null ? ("state " + savedInstanceState.getInt("number")) : "no state"));
        if(savedInstanceState != null) {
            mNum = savedInstanceState.getInt("number");
        } else {
            mNum = 25;
        }
    }

    @Override
    public void onActivityCreated(Bundle savedInstanceState) {
        Log.d(TAG, "onActivityCreated");
        if(savedInstanceState != null) {
            Log.d(TAG, "saved variable number: " + savedInstanceState.getInt("number"));
        }
        super.onActivityCreated(savedInstanceState);
    }

    @Override
    public void onSaveInstanceState(Bundle outState) {
        Log.d(TAG, "onSaveInstanceState saving: " + mNum);
        outState.putInt("number", mNum);
        super.onSaveInstanceState(outState);
    }

    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
        Log.d(FragmentTabs.TAG, "onCreateView " + (savedInstanceState != null ? ("state: " + savedInstanceState.getInt("number")) : "no state"));
        textView = new TextView(getActivity());
        textView.setText("Hello world: " + mNum);
        textView.setBackgroundDrawable(getResources().getDrawable(android.R.drawable.gallery_thumb));
        return textView;
    }
}

}

İşte bu örneği çalıştırıp ardından telefonu döndürerek elde edilen çıktı:

06-11 11:31:42.559: D/FragmentTabs(10726): onTabSelected adding fragment mysimple
06-11 11:31:42.559: D/FragmentTabs(10726): onCreate no state
06-11 11:31:42.559: D/FragmentTabs(10726): onCreateView no state
06-11 11:31:42.567: D/FragmentTabs(10726): onActivityCreated
06-11 11:31:45.286: D/FragmentTabs(10726): onSaveInstanceState saving: 25
06-11 11:31:45.325: D/FragmentTabs(10726): onCreate state 25
06-11 11:31:45.340: D/FragmentTabs(10726): constructor: detaching fragment mysimple
06-11 11:31:45.340: D/FragmentTabs(10726): onTabSelected attaching fragment mysimple
06-11 11:31:45.348: D/FragmentTabs(10726): FragmentTabs.onCreate tab: 0
06-11 11:31:45.348: D/FragmentTabs(10726): FragmentTabs.onCreate number: 0
06-11 11:31:45.348: D/FragmentTabs(10726): onCreateView state: 25
06-11 11:31:45.348: D/FragmentTabs(10726): onActivityCreated
06-11 11:31:45.348: D/FragmentTabs(10726): saved variable number: 25
06-11 11:31:45.348: D/FragmentTabs(10726): onCreateView no state
06-11 11:31:45.348: D/FragmentTabs(10726): onActivityCreated

Sorum şu, onCreateView ve onActivityCreated neden iki kez çağrılıyor? Kaydedilmiş duruma sahip bir Bundle ile ilk kez ve null kaydedilmiş bir InstanceState ile ikinci kez?

Bu, dönme sırasında parçanın durumunu korumada sorunlara neden oluyor.


Yanıtlar:


45

Bir süredir bunun hakkında kafamı karıştırıyordum ve Dave'in açıklamasını anlamak biraz zor olduğundan (görünüşe göre çalışıyor) kodumu göndereceğim:

private class TabListener<T extends Fragment> implements ActionBar.TabListener {
    private Fragment mFragment;
    private Activity mActivity;
    private final String mTag;
    private final Class<T> mClass;

    public TabListener(Activity activity, String tag, Class<T> clz) {
        mActivity = activity;
        mTag = tag;
        mClass = clz;
        mFragment=mActivity.getFragmentManager().findFragmentByTag(mTag);
    }

    public void onTabSelected(Tab tab, FragmentTransaction ft) {
        if (mFragment == null) {
            mFragment = Fragment.instantiate(mActivity, mClass.getName());
            ft.replace(android.R.id.content, mFragment, mTag);
        } else {
            if (mFragment.isDetached()) {
                ft.attach(mFragment);
            }
        }
    }

    public void onTabUnselected(Tab tab, FragmentTransaction ft) {
        if (mFragment != null) {
            ft.detach(mFragment);
        }
    }

    public void onTabReselected(Tab tab, FragmentTransaction ft) {
    }
}

Gördüğünüz gibi, yapıcıda ayrılmamak ve eklemek yerine değiştir kullanmak dışında, Android örneğine çok benziyor .

Çok fazla kafa tarama ve deneme-yanılma işleminden sonra, yapıcıdaki parçayı bulmanın, onCreateView çiftini sihirli bir şekilde ortadan kaldırdığı görülüyor durumu kaydetme / geri yükleme).


Mükemmel çalışıyor! Gece uykumu kurtardın! Teşekkür ederim :)
jaibatrik

sınıf değişkenini ve çağrıdan bir parametreyi kaldırmak istiyorsanız fragment.getClass (). getName () öğesini de kullanabilirsiniz
Ben Sewards

"Önceki referans TabListener" Android örneği - tnx ile mükemmel çalışır. En yeni Android "TabListener referans örneği" [4 ix 2013'teki gibi] gerçekten çok yanlış.
Grzegorz Dev

ft.commit () yöntemi çağrısı nerede?
MSaudi

1
@MuhammadBabar, bkz. Stackoverflow.com/questions/23248789/… . Ekran addyerine kullanır replaceve döndürürseniz, birçok parçanız olacaktır ' onCreateView().
CoolMind

26

Tamam, işte öğrendiklerim.

Anlayamadığım şey, bir yapılandırma değişikliği olduğunda (telefon döndüğünde) bir etkinliğe eklenen tüm parçaların yeniden oluşturulup etkinliğe geri eklendiğidir. (bu mantıklı)

TabListener yapıcısında olan şey, bulunup etkinliğe eklenmişse sekmenin ayrılmasıydı. Aşağıya bakınız:

mFragment = mActivity.getFragmentManager().findFragmentByTag(mTag);
    if (mFragment != null && !mFragment.isDetached()) {
        Log.d(TAG, "constructor: detaching fragment " + mTag);
        FragmentTransaction ft = mActivity.getFragmentManager().beginTransaction();
        ft.detach(mFragment);
        ft.commit();
    }

Daha sonra onCreate etkinliğinde önceden seçilen sekme kaydedilmiş örnek durumundan seçildi. Aşağıya bakınız:

if (savedInstanceState != null) {
    bar.setSelectedNavigationItem(savedInstanceState.getInt("tab", 0));
    Log.d(TAG, "FragmentTabs.onCreate tab: " + savedInstanceState.getInt("tab"));
    Log.d(TAG, "FragmentTabs.onCreate number: " + savedInstanceState.getInt("number"));
}

Sekme seçildiğinde, onTabSelected geri aramasında yeniden eklenir.

public void onTabSelected(Tab tab, FragmentTransaction ft) {
    if (mFragment == null) {
        mFragment = Fragment.instantiate(mActivity, mClass.getName(), mArgs);
        Log.d(TAG, "onTabSelected adding fragment " + mTag);
        ft.add(android.R.id.content, mFragment, mTag);
    } else {
        Log.d(TAG, "onTabSelected attaching fragment " + mTag);
        ft.attach(mFragment);
    }
}

Eklenen parça, onCreateView ve onActivityCreated yöntemlerine yapılan ikinci çağrıdır. (İlki, sistemin etkinliği ve tüm ekli parçaları yeniden oluşturduğu zamandır) onSavedInstanceState Bundle ilk kez verileri kaydederdi, ancak ikinci seferde değil.

Çözüm, parçayı TabListener yapıcısında ayırmak değil, onu bağlı bırakmaktır. (FragmentManager'da hala etiketiyle bulmanız gerekiyor) Ayrıca, onTabSelected yönteminde, onu eklemeden önce parçanın ayrılıp ayrılmadığını kontrol ediyorum. Bunun gibi bir şey:

public void onTabSelected(Tab tab, FragmentTransaction ft) {
            if (mFragment == null) {
                mFragment = Fragment.instantiate(mActivity, mClass.getName(), mArgs);
                Log.d(TAG, "onTabSelected adding fragment " + mTag);
                ft.add(android.R.id.content, mFragment, mTag);
            } else {

                if(mFragment.isDetached()) {
                    Log.d(TAG, "onTabSelected attaching fragment " + mTag);
                    ft.attach(mFragment);
                } else {
                    Log.d(TAG, "onTabSelected fragment already attached " + mTag);
                }
            }
        }

4
Bahsedilen "TabListener yapıcısında parçanın ayrılmaması" çözümleri, sekme parçalarının birbiriyle örtüşmesine neden olur. Diğer parçaların içeriğini görebiliyorum. Benim için çalışmıyor.
Aksel Fatih

@ flock.dux Örtüşmekle ne demek istediğinizden emin değilim. Android, nasıl yerleştirildiklerine bakar, bu yüzden sadece ekleme veya çıkarma belirtiyoruz. Daha fazlası olmalı. Belki örnek kodlu yeni bir soru sorarsanız, sizin için neler olup bittiğini anlayabiliriz.
Dave

1
Aynı sorunu yaşadım (Android'den çoklu parça oluşturucu çağrıları). Bulmanız sorunumu çözdü: Anlamadığım şey, bir yapılandırma değişikliği meydana geldiğinde (telefon döndüğünde) bir etkinliğe eklenen tüm parçaların yeniden oluşturulup etkinliğe geri eklendiğidir. (mantıklı)
eugene

26

Aynı problemi sadece bir parça taşıyan (bazen değiştirilen) basit bir Aktivite ile yaşadım. Daha sonra onSaveInstanceState'i etkinlik içinde değil, yalnızca parçada (ve kaydedilmişInstanceState'i kontrol etmek için onCreateView) kullandığımı fark ettim.

Cihaz dönüşünde parçaları içeren aktivite yeniden başlatılır ve onCreated çağrılır. Orada gerekli parçayı ekledim (ilk başlangıçta doğru olan).

Cihazda, Android ilk önce görünür olan parçayı yeniden oluşturdu ve ardından parçamın eklendiği etkinliğin oluşturulmasını çağırdı, böylece orijinal görünür olanı değiştirdi.

Bundan kaçınmak için, SavedInstanceState'i kontrol etmek için etkinliğimi değiştirdim:

protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);

if (savedInstanceState != null) {
/**making sure you are not attaching the fragments again as they have 
 been 
 *already added
 **/
 return; 
 }
 else{
  // following code to attach fragment initially
 }

 }

Hatta etkinliğin onSaveInstanceState üzerine Yazmadım.


Teşekkür ederim. Oryantasyon değişikliğinden sonra tercih bölümünde diyalogları gösterirken AppCompatActivity + PreferenceFragmentCompat ve çökme ile bana yardımcı oldu, çünkü parça yöneticisi ikinci parça oluşturmada boştu.
RoK

12

Buradaki iki olumlu oy, navigasyon modlu bir Aktivite için çözümleri gösterir NAVIGATION_MODE_TABS, ancak aynı sorunu bir NAVIGATION_MODE_LIST. Fragments'ımın ekran yönü değiştiğinde durumlarını açıklanamaz bir şekilde kaybetmesine neden oldu, bu gerçekten can sıkıcıydı. Neyse ki, yararlı kodları nedeniyle bunu çözmeyi başardım.

Temel olarak, bir liste navigasyonu kullanırken, `` onNavigationItemSelected () is automatically called when your activity is created/re-created, whether you like it or not. To prevent your Fragment'sonCreateView () from being called twice, this initial automatic call toonNavigationItemSelected () should check whether the Fragment is already in existence inside your Activity. If it is, return immediately, because there is nothing to do; if it isn't, then simply construct the Fragment and add it to the Activity like you normally would. Performing this check prevents your Fragment from needlessly being created again, which is what causesonCreateView () '' iki kez çağrılacak!

Benim Bkz onNavigationItemSelected()aşağıda uygulanmasını.

public class MyActivity extends FragmentActivity implements ActionBar.OnNavigationListener
{
    private static final String STATE_SELECTED_NAVIGATION_ITEM = "selected_navigation_item";

    private boolean mIsUserInitiatedNavItemSelection;

    // ... constructor code, etc.

    @Override
    public void onRestoreInstanceState(Bundle savedInstanceState)
    {
        super.onRestoreInstanceState(savedInstanceState);

        if (savedInstanceState.containsKey(STATE_SELECTED_NAVIGATION_ITEM))
        {
            getActionBar().setSelectedNavigationItem(savedInstanceState.getInt(STATE_SELECTED_NAVIGATION_ITEM));
        }
    }

    @Override
    public void onSaveInstanceState(Bundle outState)
    {
        outState.putInt(STATE_SELECTED_NAVIGATION_ITEM, getActionBar().getSelectedNavigationIndex());

        super.onSaveInstanceState(outState);
    }

    @Override
    public boolean onNavigationItemSelected(int position, long id)
    {    
        Fragment fragment;
        switch (position)
        {
            // ... choose and construct fragment here
        }

        // is this the automatic (non-user initiated) call to onNavigationItemSelected()
        // that occurs when the activity is created/re-created?
        if (!mIsUserInitiatedNavItemSelection)
        {
            // all subsequent calls to onNavigationItemSelected() won't be automatic
            mIsUserInitiatedNavItemSelection = true;

            // has the same fragment already replaced the container and assumed its id?
            Fragment existingFragment = getSupportFragmentManager().findFragmentById(R.id.container);
            if (existingFragment != null && existingFragment.getClass().equals(fragment.getClass()))
            {
                return true; //nothing to do, because the fragment is already there 
            }
        }

        getSupportFragmentManager().beginTransaction().replace(R.id.container, fragment).commit();
        return true;
    }
}

Bu çözüm için buradan ilham aldım .


Bu çözüm, bir gezinti çekmecesi ile benzer sorunum için çalışıyor. Mevcut parçayı kimliğe göre buluyorum ve yeniden oluşturmadan önce yeni parçayla aynı sınıfa sahip olup olmadığını kontrol ediyorum.
William

8

Bana öyle geliyor ki, TabListener'ınızı her seferinde somutlaştırıyorsunuz ... bu yüzden sistem, parçanızı SavedInstanceState'den yeniden oluşturuyor ve ardından onCreate'inizde tekrar yapıyorsunuz.

Bunu bir if(savedInstanceState == null)içine sarmalısınız, böylece yalnızca kaydedilmişInstanceState yoksa ateşler.


Bunun doğru olduğunu sanmıyorum. AddTab kodumu if bloğuna sardığımda, parça etkinliğe ekleniyor ancak sekme yok. OnCreate yönteminde her seferinde sekmeleri eklemeniz gerekiyor gibi görünüyor. Bunu araştırmaya devam edeceğim ve daha iyi anladıkça daha fazlasını göndereceğim.
Dave
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.