Fragmen MyFragment tidak dilampirkan ke Aktivitas

393

Saya telah membuat aplikasi uji kecil yang mewakili masalah saya. Saya menggunakan ActionBarSherlock untuk mengimplementasikan tab dengan (Sherlock) Fragments.

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

Saya telah menambahkan Thread.sleepbagian untuk mensimulasikan mengunduh data. Kode dalam onPostExecuteadalah untuk mensimulasikan penggunaan Fragment.

Ketika saya memutar layar dengan sangat cepat antara landscape dan portrait, saya mendapatkan Exception pada onPostExecutekode:

java.lang.IllegalStateException: Fragment MyFragment {410f6060} tidak dilampirkan ke Activity

Saya pikir itu karena yang baru MyFragmenttelah dibuat sementara itu, dan dilampirkan pada Kegiatan sebelum AsyncTaskselesai. Kode dalam onPostExecutepanggilan pada yang tidak terikat MyFragment.

Tetapi bagaimana saya bisa memperbaikinya?

nhaarman
sumber
1
Anda harus menggunakan tampilan dari inflater fragmen. mView = inflater.inflate(R.layout.my_layout, container, false) Dan sekarang menggunakan tampilan ini bila Anda ingin mendapatkan sumber: mView.getResources().***. Ini membantu saya untuk memperbaiki bug ini.
foxis
@foxis Itu bocor Contextyang melekat pada `mView` Anda.
nhaarman
Mungkin saya belum memeriksanya. Untuk menghindari kebocoran bagaimana cara mendapatkan null mViewdi onDestroy?
foxis

Jawaban:

774

Saya telah menemukan jawaban yang sangat sederhana isAdded()::

Kembali truejika fragmen saat ini ditambahkan ke aktivitasnya.

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

Untuk menghindari onPostExecutedipanggil saat Fragmenttidak terpasang keActivity adalah untuk membatalkan AsyncTaskketika menjeda atau menghentikan Fragment. Maka isAdded()tidak perlu lagi. Namun, disarankan untuk tetap memeriksa ini.

nhaarman
sumber
Dalam kasus saya ketika saya meluncurkan aplikasi lain Intent from ... maka saya mendapatkan error yang sama ... suggetion?
CoDe
1
developer.android.com/reference/android/app/ ... ... ada juga isDetached(), yang ditambahkan pada API level 13
Lucas Jota
5
Saat menggunakan API <11, Anda menggunakan developer.android.com/reference/android/support/v4/app/… di mana ia akan berfungsi.
nhaarman
Saya menghadapi masalah ini ketika saya menggunakan DialogFragment. Setelah mengabaikan dialogFragment, saya mencoba memulai aktivitas lain. Kemudian kesalahan ini terjadi. Saya menghindari kesalahan ini dengan menelepon dismiss () setelah startActivity. Masalahnya adalah fragmen sudah terlepas dari Activity.
Ataru
28

Masalahnya adalah Anda mencoba mengakses sumber daya (dalam hal ini, string) menggunakan getResources (). GetString (), yang akan mencoba untuk mendapatkan sumber daya dari Aktivitas. Lihat kode sumber kelas Fragmen ini:

 /**
  * 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 adalah objek yang menyimpan Aktivitas Anda.

Karena Aktivitas mungkin tidak dilampirkan, panggilan getResources () Anda akan mengeluarkan Pengecualian.

IMHO solusi yang diterima bukan cara untuk pergi karena Anda hanya menyembunyikan masalah. Cara yang benar adalah hanya untuk mendapatkan sumber daya dari tempat lain yang selalu dijamin ada, seperti konteks aplikasi:

youApplicationObject.getResources().getString(...)
Tiago
sumber
Saya menggunakan solusi ini karena saya perlu mengeksekusi getString()ketika fragmen saya dijeda. Terima kasih
Geekarist
24

Saya telah menghadapi dua skenario berbeda di sini:

1) Ketika saya ingin menyelesaikan tugas asinkron: bayangkan onPostExecute saya menyimpan data yang diterima dan kemudian memanggil pendengar untuk memperbarui tampilan, agar lebih efisien, saya tetap ingin menyelesaikan tugas jadi saya memiliki data yang siap ketika pengguna memanggil kembali. Dalam hal ini saya biasanya melakukan ini:

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

2) Ketika saya ingin tugas asinkron hanya selesai ketika tampilan dapat diperbarui: kasus yang Anda ajukan di sini, tugas hanya memperbarui tampilan, tidak perlu penyimpanan data, sehingga tidak memiliki petunjuk untuk menyelesaikan tugas jika tampilan tidak lagi ditampilkan. Saya melakukan ini:

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

Saya tidak menemukan masalah dengan ini, meskipun saya juga menggunakan cara (mungkin) yang lebih kompleks yang mencakup memulai tugas dari aktivitas alih-alih fragmen.

Semoga ini bisa membantu seseorang! :)

luixal
sumber
18

Masalah dengan kode Anda adalah cara Anda menggunakan AsyncTask, karena ketika Anda memutar layar selama utas tidur Anda:

Thread.sleep(2000) 

AsyncTask masih berfungsi, itu karena Anda tidak membatalkan instance AsyncTask dengan benar di onDestroy () sebelum fragmen dibangun kembali (ketika Anda memutar) dan ketika instance AsyncTask yang sama (setelah diputar) berjalan diPostExecute (), ini mencoba mencari sumber daya dengan getResources () dengan instance fragmen lama (instance tidak valid):

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

yang setara dengan:

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

Jadi solusi terakhir adalah mengelola instance AsyncTask (untuk membatalkan jika ini masih berfungsi) sebelum fragmen dibangun kembali ketika Anda memutar layar, dan jika dibatalkan selama transisi, restart AsyncTask setelah rekonstruksi dengan bantuan bendera boolean:

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

    }
}
Erick Reátegui Diaz
sumber
bukan jika getResources().***menggunakan Fragments.this.getResource().***membantu
Prabs
17

Mereka cukup solusi trik untuk ini dan kebocoran fragmen dari aktivitas.

Jadi dalam kasus getResource atau apapun yang tergantung pada konteks aktivitas mengakses dari Fragment selalu memeriksa status aktivitas dan status fragmen sebagai berikut

 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


        }
    }
Vinayak
sumber
7
isAdded () sudah cukup karena: boolean publik akhir isAdded () {return mHost! = null && mAdded; }
NguyenDat
Dalam kasus saya cek ini tidak enoug, masih mendapatkan crash meskipun saya menambahkan ini.
David
@ David, isAddedsudah cukup. Saya tidak pernah melihat situasi ketika getString()jatuh jika isAdded == true. Apakah Anda yakin suatu kegiatan ditampilkan dan sebuah fragmen dilampirkan?
CoolMind
14
if (getActivity() == null) return;

berfungsi juga dalam beberapa kasus. Hancurkan saja eksekusi kode dan pastikan aplikasi tidak macet

pengguna super
sumber
10

Saya menghadapi masalah yang sama saya hanya menambahkan instance singletone untuk mendapatkan sumber daya sebagaimana dimaksud oleh Erick

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

Anda juga bisa menggunakan

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

Saya harap ini akan membantu.

Aristo Michael
sumber
2

Saya menghadapi masalah serupa ketika aktivitas pengaturan aplikasi dengan preferensi yang dimuat terlihat. Jika saya akan mengubah salah satu preferensi dan kemudian membuat konten tampilan diputar dan mengubah preferensi lagi, itu akan crash dengan pesan bahwa fragmen (kelas Preferensi saya) tidak dilampirkan ke suatu kegiatan.

Ketika debugging itu tampak seperti metode onCreate () dari PreferencesFragment dipanggil dua kali ketika konten tampilan diputar. Itu sudah cukup aneh. Kemudian saya menambahkan isAdded () periksa di luar blok di mana itu akan menunjukkan crash dan itu memecahkan masalah.

Berikut adalah kode pendengar yang memperbarui ringkasan preferensi untuk menampilkan entri baru. Itu terletak di metode onCreate () dari kelas Preferensi saya yang memperluas kelas PreferenceFragment:

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");
                    }
                }
            }
        };
        // ...
    }

Saya harap ini akan membantu orang lain!

ohgodnotanotherone
sumber
0

Jika Anda memperluas Applicationkelas dan mempertahankan objek Konteks 'global' statis, seperti berikut, maka Anda dapat menggunakannya sebagai ganti aktivitas untuk memuat sumber daya String.

public class MyApplication extends Application {
    public static Context GLOBAL_APP_CONTEXT;

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

Jika Anda menggunakan ini, Anda dapat pergi Toastdan memuat sumber daya tanpa khawatir tentang siklus hidup.

Anthony Chuinard
sumber
5
Saya sedang downvoted tetapi tidak ada yang menjelaskan mengapa. Konteks statis biasanya buruk tetapi saya berpendapat bahwa ini bukan kebocoran memori jika Anda memiliki referensi Aplikasi statis.
Anthony Chuinard
Jawaban Anda dibatalkan karena ini hanya solusi tidak tepat hack. Periksa solusi yang dibagikan oleh @nhaarman
Vivek Kumar Srivastava
0

Dalam kasus saya, metode fragmen telah dipanggil

getActivity().onBackPressed();
CoolMind
sumber
0

Posting lama, tetapi saya terkejut dengan jawaban yang paling banyak dipilih.

Solusi yang tepat untuk ini harus membatalkan asynctask di onStop (atau di mana pun sesuai fragmen Anda). Dengan cara ini Anda tidak memperkenalkan kebocoran memori (asynctask menyimpan referensi untuk fragmen Anda yang hancur) dan Anda memiliki kontrol yang lebih baik dari apa yang terjadi di fragmen Anda.

@Override
public void onStop() {
    super.onStop();
    mYourAsyncTask.cancel(true);
}
Raz
sumber
1
Jawaban yang paling banyak dipilih termasuk ini. Juga, cancelmungkin tidak mencegah onPostExecutedipanggil.
nhaarman
Membatalkan panggilan tidak menjamin onPostExecute tidak akan pernah dipanggil, kedua panggilan dieksekusi di utas yang sama maka Anda dijamin tidak akan dipanggil setelah membatalkan panggilan
Raz