Fragman MyFragment Etkinliğe eklenmedi


393

Sorunumu temsil eden küçük bir test uygulaması oluşturdum. (Sherlock) parçaları ile sekmeleri uygulamak için ActionBarSherlock kullanıyorum.

Kodum: TestActivity.java

public class TestActivity extends SherlockFragmentActivity {
    private ActionBar actionBar;

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setupTabs(savedInstanceState);
    }

    private void setupTabs(Bundle savedInstanceState) {
        actionBar = getSupportActionBar();
        actionBar.setNavigationMode(ActionBar.NAVIGATION_MODE_TABS);

        addTab1();
        addTab2();
    }

    private void addTab1() {
        Tab tab1 = actionBar.newTab();
        tab1.setTag("1");
        String tabText = "1";
        tab1.setText(tabText);
        tab1.setTabListener(new TabListener<MyFragment>(TestActivity.this, "1", MyFragment.class));

        actionBar.addTab(tab1);
    }

    private void addTab2() {
        Tab tab1 = actionBar.newTab();
        tab1.setTag("2");
        String tabText = "2";
        tab1.setText(tabText);
        tab1.setTabListener(new TabListener<MyFragment>(TestActivity.this, "2", MyFragment.class));

        actionBar.addTab(tab1);
    }
}

TabListener.java

public class TabListener<T extends SherlockFragment> implements com.actionbarsherlock.app.ActionBar.TabListener {
    private final SherlockFragmentActivity mActivity;
    private final String mTag;
    private final Class<T> mClass;

    public TabListener(SherlockFragmentActivity activity, String tag, Class<T> clz) {
        mActivity = activity;
        mTag = tag;
        mClass = clz;
    }

    /* The following are each of the ActionBar.TabListener callbacks */

    public void onTabSelected(Tab tab, FragmentTransaction ft) {
        SherlockFragment preInitializedFragment = (SherlockFragment) mActivity.getSupportFragmentManager().findFragmentByTag(mTag);

        // Check if the fragment is already initialized
        if (preInitializedFragment == null) {
            // If not, instantiate and add it to the activity
            SherlockFragment mFragment = (SherlockFragment) SherlockFragment.instantiate(mActivity, mClass.getName());
            ft.add(android.R.id.content, mFragment, mTag);
        } else {
            ft.attach(preInitializedFragment);
        }
    }

    public void onTabUnselected(Tab tab, FragmentTransaction ft) {
        SherlockFragment preInitializedFragment = (SherlockFragment) mActivity.getSupportFragmentManager().findFragmentByTag(mTag);

        if (preInitializedFragment != null) {
            // Detach the fragment, because another one is being attached
            ft.detach(preInitializedFragment);
        }
    }

    public void onTabReselected(Tab tab, FragmentTransaction ft) {
        // User selected the already selected tab. Usually do nothing.
    }
}

MyFragment.java

public class MyFragment extends SherlockFragment {

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

        new AsyncTask<Void, Void, Void>() {

            @Override
            protected Void doInBackground(Void... params) {
                try {
                    Thread.sleep(2000);
                } catch (InterruptedException ex) {
                }
                return null;
            }

            @Override
            protected void onPostExecute(Void result){
                getResources().getString(R.string.app_name);
            }

        }.execute();
    }
}

Thread.sleepİndirme verilerini simüle etmek için parçayı ekledim . İçindeki kod onPostExecutekullanımı simüle etmektir Fragment.

Ekranı yatay ve dikey arasında çok hızlı döndürdüğümde, onPostExecutekodda bir İstisna alıyorum :

java.lang.IllegalStateException: Fragman MyFragment {410f6060} Etkinliğe eklenmedi

Bence bu MyFragmentarada yeni bir yaratıldı ve bitmeden önce Faaliyete bağlıydı AsyncTask. onPostExecuteBir bağlanmamış çağrı üzerine kodu MyFragment.

Ama bunu nasıl düzeltebilirim?


1
Parça inflater görünümünden kullanmalısınız. mView = inflater.inflate(R.layout.my_layout, container, false) Eğer kaynakları elde etmek istediğinizde Ve şimdi bu görünümü kullanın: mView.getResources().***. Bu hatayı düzeltmeme yardım ediyor.
foxis

@foxis mView'inize Contextbağlı olanı sızdırıyor.
nhaarman

Belki henüz kontrol etmiyorum. Sızıntıyı önlemek için mViewonDestroy'da boş kalmaya ne dersiniz?
foxis

Yanıtlar:


774

Çok basit bir cevap buldum isAdded():

trueParça şu anda etkinliğine eklenmişse geri dönün .

@Override
protected void onPostExecute(Void result){
    if(isAdded()){
        getResources().getString(R.string.app_name);
    }
}

A bağlı değilken onPostExecuteçağrılmasını önlemek için duraklatma veya durdurma sırasında iptal etmektir . O zaman artık gerekli olmazdı. Ancak, bu kontrolü yerinde tutmanız tavsiye edilir.FragmentActivityAsyncTaskFragmentisAdded()


Benim durumumda Başka bir uygulama Niyeti başlattığımda ... o zaman aynı hatayı alıyorum ... herhangi bir öneri?
CoDe

1
developer.android.com/reference/android/app/… ... isDetached()API 13'e eklenen de var
Lucas Jota

5
API <11 olduğunda, geliştiricinin çalışacağı geliştirici.android.com/ reference/android/support/v4/app/… adresini kullanıyorsunuz .
nhaarman

DialogFragment kullandığımda bu sorunla karşılaştım. DialogFragment'ı kapattıktan sonra başka bir etkinlik başlatmaya çalıştım. Sonra bu hata oluştu. StartActivity'den sonra dismiss () öğesini çağırarak bu hatayı önledim. Sorun, parçanın zaten Etkinlik'ten ayrılmış olmasıydı.
Ataru

28

Sorun kaynaklardan (bu durumda, dizeleri) getResources (). GetString () kullanarak kaynaklara erişmeye çalışmanızdır; Fragment sınıfının bu kaynak koduna bakın:

 /**
  * Return <code>getActivity().getResources()</code>.
  */
 final public Resources getResources() {
     if (mHost == null) {
         throw new IllegalStateException("Fragment " + this + " not attached to Activity");
     }
     return mHost.getContext().getResources();
 }

mHost Etkinliğinizi tutan nesnedir.

Etkinlik eklenmemiş olabileceğinden, getResources () çağrınız bir İstisna atar.

Kabul edilen çözüm IMHO, sorunu sadece gizlediğiniz için gitmenin yolu değildir. Doğru yol, kaynakları uygulama bağlamında olduğu gibi her zaman var olması garanti edilen başka bir yerden almaktır:

youApplicationObject.getResources().getString(...)

Bu çözümü kullandım çünkü getString()parçam duraklatıldığında yürütmem gerekiyordu. Teşekkürler
Geekarist

24

Burada iki farklı senaryo ile karşılaştım:

1) Eşzamansız görevin yine de bitmesini istediğimde: onPostExecute'imin alınan verileri depoladığını ve daha sonra görünümleri güncellemek için bir dinleyiciyi çağırdığını, böylece daha verimli olması için, görev yine de bitmesini istiyorum, böylece kullanıcı topladığında verileri hazır hale getiriyorum geri. Bu durumda genellikle bunu yaparım:

@Override
protected void onPostExecute(void result) {
    // do whatever you do to save data
    if (this.getView() != null) {
        // update views
    }
}

2) Eşzamansız görevin yalnızca görünümler güncellendiğinde bitmesini istediğimde: burada önerdiğiniz durumda, görev yalnızca görünümleri güncelleştirir, veri depolaması gerekmez, bu nedenle görünümler tamamlandığında görevin tamamlanması için hiçbir ipucu yoktur artık gösterilmiyor. Bunu yapıyorum:

@Override
protected void onStop() {
    // notice here that I keep a reference to the task being executed as a class member:
    if (this.myTask != null && this.myTask.getStatus() == Status.RUNNING) this.myTask.cancel(true);
    super.onStop();
}

Bununla ilgili hiçbir sorun bulamadım, ancak parçaları yerine etkinlikten görevleri başlatma işlemini içeren (belki) daha karmaşık bir yol kullanıyorum.

Bunun birine yardım etmesini diliyorum! :)


18

Kodunuzdaki sorun, AsyncTask'ı kullanma şeklinizdir, çünkü uyku diziniz sırasında ekranı döndürdüğünüzde:

Thread.sleep(2000) 

AsyncTask hala çalışıyorsa, parçayı yeniden oluşturmadan önce (döndürdüğünüzde) AsyncTask örneğini onDestroy () 'da düzgün bir şekilde iptal etmediyseniz ve bu aynı AsyncTask örneği (döndürdükten sonra) onPostExecute ()' de çalışıyorsa, eski fragman örneğiyle (geçersiz bir örnek) getResources () içeren kaynaklar:

getResources().getString(R.string.app_name)

eşdeğer:

MyFragment.this.getResources().getString(R.string.app_name)

Son çözüm, ekranı döndürdüğünüzde parça yeniden oluşturulmadan önce AsyncTask örneğini yönetmektir (bu hala çalışıyorsa iptal etmek için) ve geçiş sırasında iptal edilirse, boole bayrağı yardımıyla yeniden oluşturulduktan sonra AsyncTask'ı yeniden başlatın:

public class MyFragment extends SherlockFragment {

    private MyAsyncTask myAsyncTask = null;
    private boolean myAsyncTaskIsRunning = true;

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        if(savedInstanceState!=null) {
            myAsyncTaskIsRunning = savedInstanceState.getBoolean("myAsyncTaskIsRunning");
        }
        if(myAsyncTaskIsRunning) {
            myAsyncTask = new MyAsyncTask();
            myAsyncTask.execute();
        }
    }

    @Override
    public void onSaveInstanceState(Bundle outState) {
        super.onSaveInstanceState(outState);
        outState.putBoolean("myAsyncTaskIsRunning",myAsyncTaskIsRunning);
    }

    @Override
    public void onDestroy() {
        super.onDestroy();
        if(myAsyncTask!=null) myAsyncTask.cancel(true);
        myAsyncTask = null;

    }

    public class MyAsyncTask extends AsyncTask<Void, Void, Void>() {

        public MyAsyncTask(){}

        @Override
        protected void onPreExecute() {
            super.onPreExecute();
            myAsyncTaskIsRunning = true;
        }
        @Override
        protected Void doInBackground(Void... params) {
            try {
                Thread.sleep(2000);
            } catch (InterruptedException ex) {}
            return null;
        }

        @Override
        protected void onPostExecute(Void result){
            getResources().getString(R.string.app_name);
            myAsyncTaskIsRunning = false;
            myAsyncTask = null;
        }

    }
}

yerine eğer getResources().***kullanarak Fragments.this.getResource().***yardımcı
Prabs

17

Bunlar bunun için oldukça hileli bir çözümdür ve etkinlikten parça sızıntısıdır.

Bu nedenle getResource veya Fragment'tan erişen etkinlik bağlamına bağlı olan herhangi bir şey durumunda, her zaman etkinlik durumunu ve parça durumunu aşağıdaki gibi kontrol edin

 Activity activity = getActivity(); 
    if(activity != null && isAdded())

         getResources().getString(R.string.no_internet_error_msg);
//Or any other depends on activity context to be live like dailog


        }
    }

7
is Added () yeterlidir çünkü: nihai genel boolean is Added () {return mHost! = null && m Added; }
NguyenDat

Benim durumumda bu kontroller yeterli değil, yine de bunları eklememe rağmen çöküyor.
David

@David, isAddedyeter. getString()Eğer çarptığında hiç bir durum görmedim isAdded == true. Bir etkinliğin gösterildiğinden ve bir parçanın eklendiğinden emin misiniz?
CoolMind

14
if (getActivity() == null) return;

bazı durumlarda da çalışır. Sadece kod yürütme işlemini sonlandırın ve uygulamanın çökmediğinden emin olun


10

Ben aynı sorunla karşı karşıya sadece Erick tarafından atıfta bulunulan kaynak almak için tek ton örneği ekleyin

MainFragmentActivity.defaultInstance().getResources().getString(R.string.app_name);

Ayrıca kullanabilirsiniz

getActivity().getResources().getString(R.string.app_name);

Umarım bu yardımcı olacak.


2

Yüklenen tercihlerle uygulama ayarları etkinliği göründüğünde benzer sorunlarla karşılaştım. Tercihlerden birini değiştirir ve sonra görüntü içeriğini döndürür ve tercihi tekrar değiştirirsem, parçanın (Tercihler sınıfım) bir etkinliğe eklenmediğini belirten bir mesajla kilitlenir.

Hata ayıklama sırasında, görüntü içeriği döndürüldüğünde PreferencesFragment öğesinin onCreate () Yöntemi iki kez çağrıldı. Bu zaten yeterince garipti. Sonra bloğun dışında is Added () kontrolünü ekledim ve kilitlenmeyi gösterecekti ve sorunu çözdü.

İşte, yeni girişi göstermek için tercihler özetini güncelleyen dinleyicinin kodu. PreferenceFragment sınıfını genişleten Tercihler sınıfımın onCreate () yönteminde bulunur:

public static class Preferences extends PreferenceFragment {
    SharedPreferences.OnSharedPreferenceChangeListener listener;

    @Override
    public void onCreate(Bundle savedInstanceState) {
        // ...
        listener = new SharedPreferences.OnSharedPreferenceChangeListener() {
            @Override
            public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, String key) {
                // check if the fragment has been added to the activity yet (necessary to avoid crashes)
                if (isAdded()) {
                    // for the preferences of type "list" set the summary to be the entry of the selected item
                    if (key.equals(getString(R.string.pref_fileviewer_textsize))) {
                        ListPreference listPref = (ListPreference) findPreference(key);
                        listPref.setSummary("Display file content with a text size of " + listPref.getEntry());
                    } else if (key.equals(getString(R.string.pref_fileviewer_segmentsize))) {
                        ListPreference listPref = (ListPreference) findPreference(key);
                        listPref.setSummary("Show " + listPref.getEntry() + " bytes of a file at once");
                    }
                }
            }
        };
        // ...
    }

Umarım bu başkalarına yardımcı olur!


0

ApplicationSınıfı genişletir ve aşağıdaki gibi statik bir 'global' Bağlam nesnesini tutarsanız, bir String kaynağı yüklemek için etkinlik yerine bunu kullanabilirsiniz.

public class MyApplication extends Application {
    public static Context GLOBAL_APP_CONTEXT;

    @Override
    public void onCreate() {
        super.onCreate();
        GLOBAL_APP_CONTEXT = this;
    }
}

Bunu kullanırsanız, Toastyaşam döngüleri konusunda endişelenmeden kaynak yüklemesinden kurtulabilirsiniz .


5
Aşağı düşülüyorum ama kimse nedenini açıklamadı. Statik bağlamlar genellikle kötüdür, ancak statik bir Uygulama başvurunuz varsa bunun bir bellek sızıntısı olmadığı fikrindeydim.
Anthony Chuinard

Cevabınız aşağıya indirildi çünkü bu sadece uygun bir çözüm değil. @Nhaarman
Vivek Kumar Srivastava

0

Benim durumumda fragman yöntemleri

getActivity().onBackPressed();

0

Eski bir gönderi, ama en çok oylanan cevaptan şaşırdım.

Bunun için uygun çözüm, asSctask'i onStop'ta (veya parçanızda uygun olan yerlerde) iptal etmek olmalıdır. Bu şekilde bir bellek sızıntısı (yok edilen parçanıza referans tutan bir asenktaş) tanımazsınız ve parçanızda neler olup bittiğini daha iyi kontrol edebilirsiniz.

@Override
public void onStop() {
    super.onStop();
    mYourAsyncTask.cancel(true);
}

1
En çok oylanan cevap buna dahildir. Ayrıca, çağrılmasını cancelengelleyemeyebilir onPostExecute.
nhaarman

PostExecute üzerinde iptal çağrısı asla çağrılmaz, her iki çağrı da aynı iş parçacığında yürütülür, bu nedenle iptal çağrıldıktan sonra çağrılmayacağı garanti edilir
Raz
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.