แฟรกเมนต์ MyFragment ไม่ได้แนบกับกิจกรรม


393

ฉันได้สร้างแอปทดสอบขนาดเล็กซึ่งแสดงถึงปัญหาของฉัน ฉันใช้ ActionBarSherlock เพื่อใช้แท็บที่มี (Sherlock) แฟรกเมนต์

รหัสของฉัน: 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ส่วนเพื่อจำลองการดาวน์โหลดข้อมูล รหัสในการที่จะใช้จำลองของonPostExecuteFragment

เมื่อฉันหมุนหน้าจออย่างรวดเร็วระหว่างแนวนอนและแนวตั้งฉันจะได้รับการยกเว้นที่onPostExecuteโค้ด:

java.lang.IllegalStateException: แฟรกเมนต์ MyFragment {410f6060} ไม่ได้แนบกับกิจกรรม

ฉันคิดว่าเป็นเพราะMyFragmentมีการสร้างใหม่ในระหว่างนี้และถูกแนบไปกับกิจกรรมก่อนที่จะAsyncTaskเสร็จสิ้น รหัสในonPostExecuteMyFragmentการโทรบนโสด

แต่ฉันจะแก้ไขได้อย่างไร


1
คุณควรใช้มุมมองจากส่วนที่พองออก และตอนนี้ใช้มุมมองนี้เมื่อคุณต้องการที่จะได้รับทรัพยากร:mView = inflater.inflate(R.layout.my_layout, container, false) mView.getResources().***มันช่วยให้ฉันแก้ไขข้อผิดพลาดนี้
foxis

@foxis การรั่วไหลContextที่แนบมากับ 'mView' ของคุณ
นาฮา

อาจเป็นเพราะฉันยังไม่ได้ตรวจสอบ เพื่อหลีกเลี่ยงการรั่วไหลวิธีรับ null mViewใน onDestroy?
foxis

คำตอบ:


774

ฉันพบคำตอบที่ง่ายมากisAdded()::

ส่งคืนtrueถ้าแฟรกเมนต์ถูกเพิ่มในกิจกรรมในปัจจุบัน

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

เพื่อหลีกเลี่ยงonPostExecuteจากการถูกเรียกเมื่อFragmentไม่ได้แนบไปActivityคือการยกเลิกเมื่อหยุดชั่วคราวหรือหยุดAsyncTask Fragmentจากนั้นisAdded()จะไม่จำเป็นอีกต่อไป อย่างไรก็ตามขอแนะนำให้เก็บเช็คอินนี้ไว้


ในกรณีของฉันเมื่อฉันเปิดตัวแอปพลิเคชันอื่นเจตนาจาก ... จากนั้นฉันได้รับข้อผิดพลาดเดียวกัน ... มีความสุขหรือไม่?
CoDe

1
developer.android.com/reference/android/app/ ...... ... นอกจากนี้ยังisDetached()มีการเพิ่มในระดับ API 13
Lucas Jota

5
เมื่อใช้ API <11 คุณกำลังใช้developer.android.com/reference/android/support/v4/app/…ที่จะใช้งานได้ที่ไหน
nhaarman

ฉันประสบปัญหานี้เมื่อฉันใช้ DialogFragment หลังจากยกเลิกการจัดเรียงกล่องโต้ตอบฉันพยายามเริ่มกิจกรรมอื่น จากนั้นข้อผิดพลาดนี้เกิดขึ้น ฉันหลีกเลี่ยงข้อผิดพลาดนี้โดยการโทรเลิกจ้าง () หลังจาก startActivity ปัญหาคือชิ้นส่วนแยกออกจากกิจกรรมแล้ว
Ataru

28

ปัญหาคือคุณกำลังพยายามเข้าถึงทรัพยากร (ในกรณีนี้คือสตริง) โดยใช้ getResources (). getString () ซึ่งจะพยายามรับทรัพยากรจากกิจกรรม ดูซอร์สโค้ดของคลาส Fragment นี้:

 /**
  * 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 เป็นวัตถุที่เก็บกิจกรรมของคุณ

เนื่องจากอาจไม่ได้แนบกิจกรรมการเรียกใช้ getResources () ของคุณจะทำให้เกิดข้อยกเว้น

วิธีการที่ได้รับการยอมรับ IMHO ไม่ใช่วิธีที่จะไปเพราะคุณเพิ่งซ่อนปัญหา วิธีที่ถูกต้องคือการรับทรัพยากรจากที่อื่นที่รับประกันอยู่เสมอเช่นบริบทแอปพลิเคชัน:

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

ฉันใช้วิธีแก้ปัญหานี้เพราะฉันต้องดำเนินการgetString()เมื่อชิ้นส่วนของฉันหยุดชั่วคราว ขอบคุณ
Geekarist

24

ฉันได้เผชิญหน้ากับสถานการณ์ที่แตกต่างกันสองสถานการณ์ที่นี่:

1) เมื่อฉันต้องการให้งานแบบอะซิงโครนัสเสร็จแล้ว: ลองนึกภาพ onPostExecute เก็บข้อมูลที่ได้รับจากนั้นเรียกผู้ฟังเพื่ออัพเดตมุมมองดังนั้นเพื่อให้มีประสิทธิภาพมากขึ้นฉันต้องการงานให้เสร็จสิ้นดังนั้นฉันจึงมีข้อมูลพร้อมเมื่อผู้ใช้ กลับ. ในกรณีนี้ฉันมักจะทำสิ่งนี้:

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

2) เมื่อฉันต้องการให้ภารกิจแบบอะซิงโครนัสเสร็จสิ้นเมื่อสามารถอัปเดตมุมมองได้: กรณีที่คุณเสนอที่นี่งานจะอัปเดตมุมมองเท่านั้นไม่จำเป็นต้องใช้ที่เก็บข้อมูลดังนั้นจึงไม่มีเงื่อนงำสำหรับภารกิจที่จะทำให้เสร็จ ไม่ถูกแสดงอีกต่อไป ฉันทำนี่:

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

ฉันไม่พบปัญหาในเรื่องนี้ถึงแม้ว่าฉันจะใช้วิธีที่ซับซ้อนกว่านี้ (ซึ่งอาจรวมถึงการเริ่มงานจากกิจกรรมแทนที่จะเป็นชิ้นส่วน)

หวังว่านี่จะช่วยให้ใครบางคน! :)


18

ปัญหาเกี่ยวกับรหัสของคุณคือวิธีการที่คุณใช้ AsyncTask เพราะเมื่อคุณหมุนหน้าจอระหว่างเธรดสลีป:

Thread.sleep(2000) 

AsyncTask ยังคงทำงานเป็นเพราะคุณไม่ได้ยกเลิกอินสแตนซ์ AsyncTask อย่างถูกต้องใน onDestroy () ก่อนที่แฟรกเมนต์จะสร้างใหม่ (เมื่อคุณหมุน) และเมื่ออินสแตนซ์ AsyncTask เดียวกันนี้ (หลังจากหมุน) ทำงานบน postExecute () รีซอร์สที่มี getResources () พร้อมอินสแตนซ์แฟรกเมนต์เก่า (อินสแตนซ์ที่ไม่ถูกต้อง):

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

ซึ่งเทียบเท่ากับ:

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

ดังนั้นทางออกสุดท้ายคือการจัดการอินสแตนซ์ AsyncTask (เพื่อยกเลิกหากยังคงทำงานอยู่) ก่อนที่แฟรกเมนต์จะสร้างใหม่เมื่อคุณหมุนหน้าจอและหากยกเลิกระหว่างการเปลี่ยนภาพให้รีสตาร์ท AsyncTask หลังจากการสร้างใหม่โดยใช้แฟล็กบูลีน

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

    }
}

แทนหากgetResources().***ใช้ความFragments.this.getResource().***ช่วยเหลือ
Prabs

17

พวกเขาค่อนข้างแก้ปัญหาสำหรับเรื่องนี้และการรั่วไหลของชิ้นส่วนจากกิจกรรม

ดังนั้นในกรณีของ getResource หรือสิ่งใดก็ตามที่ขึ้นอยู่กับบริบทของกิจกรรมที่เข้าถึงจากแฟรกเมนต์มันจะตรวจสอบสถานะกิจกรรมและสถานะแฟรกเมนต์เสมอดังนี้

 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
isAdded () เพียงพอเพราะ: บูลีนสาธารณะสุดท้าย isAdded () {return mHost! = null && mAdded; }
NguyenDat

ในกรณีของฉันการตรวจสอบนี้ไม่ได้ enoug ยังคงได้รับความผิดพลาดแม้ฉันจะเพิ่มเหล่านี้
David

@ David isAddedก็เพียงพอแล้ว ฉันไม่เคยเห็นสถานการณ์เมื่อชนถ้าgetString() isAdded == trueคุณแน่ใจหรือไม่ว่ามีการแสดงกิจกรรมและมีส่วนย่อยอยู่หรือไม่
CoolMind

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

ทำงานได้ในบางกรณี เพียงแค่แบ่งการเรียกใช้โค้ดจากมันและตรวจสอบให้แน่ใจว่าแอพไม่ทำงานผิดพลาด


10

ฉันประสบปัญหาเดียวกันฉันเพียงแค่เพิ่มอินสแตนซ์ singletone เพื่อรับทรัพยากรตามที่ Erick อ้างถึง

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

คุณยังสามารถใช้

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

ฉันหวังว่านี่จะช่วยได้


2

ฉันประสบปัญหาที่คล้ายกันเมื่อกิจกรรมการตั้งค่าแอปพลิเคชันที่มีการตั้งค่าโหลดปรากฏให้เห็น ถ้าฉันจะเปลี่ยนหนึ่งในการตั้งค่าแล้วทำให้เนื้อหาที่แสดงหมุนและเปลี่ยนการตั้งค่าอีกครั้งมันจะผิดพลาดกับข้อความว่าส่วน (ชั้นเรียนการตั้งค่าของฉัน) ไม่ได้แนบกับกิจกรรม

เมื่อทำการดีบั๊กมันดูเหมือนว่า onCreate () วิธีการของ PreferencesFragment ถูกเรียกสองครั้งเมื่อเนื้อหาการแสดงผลหมุน นั่นแปลกมากพออยู่แล้ว จากนั้นฉันเพิ่มการตรวจสอบ isAdded () ด้านนอกของบล็อกซึ่งจะบ่งบอกถึงความผิดพลาดและแก้ไขปัญหาได้

นี่คือรหัสของผู้ฟังที่อัพเดตสรุปการกำหนดค่าตามความชอบเพื่อแสดงรายการใหม่ ตั้งอยู่ในเมธอด onCreate () ของคลาส Preferences ของฉันซึ่งขยายคลาส 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");
                    }
                }
            }
        };
        // ...
    }

ฉันหวังว่านี่จะช่วยผู้อื่นได้!


0

หากคุณขยายApplicationคลาสและรักษาวัตถุบริบท 'ส่วนกลาง' แบบคงที่ดังต่อไปนี้คุณสามารถใช้สิ่งนั้นแทนกิจกรรมเพื่อโหลดทรัพยากร String

public class MyApplication extends Application {
    public static Context GLOBAL_APP_CONTEXT;

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

หากคุณใช้สิ่งนี้คุณสามารถหลีกเลี่ยงToastและโหลดทรัพยากรโดยไม่ต้องกังวลกับวงจรชีวิต


5
ฉันกำลังถูก downvote แต่ไม่มีใครอธิบายได้ว่าทำไม บริบทแบบคงที่มักจะไม่ดี แต่ฉันก็คิดว่ามันไม่ได้เป็นหน่วยความจำรั่วถ้าคุณมีการอ้างอิงแบบคงที่แอพลิเคชัน
Anthony Chuinard

คำตอบของคุณถูกลดระดับลงเพราะนี่เป็นเพียงวิธีการแก้ปัญหาที่ไม่เหมาะสม ตรวจสอบโซลูชันที่แบ่งปันโดย @nhaarman
Vivek Kumar Srivastava

0

ในกรณีส่วนวิธีของฉันได้รับการเรียกหลังจาก

getActivity().onBackPressed();

0

โพสต์เก่า แต่ฉันแปลกใจกับคำตอบที่ได้รับการโหวตมากที่สุด

ทางออกที่เหมาะสมสำหรับสิ่งนี้ควรจะยกเลิก asynctask ใน onStop (หรือที่ใดก็ตามที่เหมาะสมในส่วนของคุณ) วิธีนี้คุณจะไม่แนะนำการรั่วไหลของหน่วยความจำ (asynctask ที่อ้างอิงถึงชิ้นส่วนที่ถูกทำลายของคุณ) และคุณสามารถควบคุมสิ่งที่เกิดขึ้นในส่วนของคุณได้ดียิ่งขึ้น

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

1
คำตอบที่ upvoted ที่สุดรวมถึงสิ่งนี้ นอกจากนี้cancelอาจไม่ป้องกันไม่ให้onPostExecuteถูกเรียก
nhaarman

ยกเลิกการโทรไม่รับประกัน onPostExecute จะไม่ถูกเรียกการโทรทั้งสองจะรันในเธรดเดียวกันดังนั้นคุณจึงรับประกันว่าจะไม่ถูกเรียกใช้หลังจากการโทรยกเลิก
Raz
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.