활동에 첨부되지 않은 조각 MyFragment


393

내 문제를 나타내는 작은 테스트 응용 프로그램을 만들었습니다. ActionBarSherlock을 사용하여 (Sherlock) Fragments로 탭을 구현하고 있습니다.

내 코드 : 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데이터 다운로드를 시뮬레이션 하는 부분을 추가했습니다 . 의 코드는의 onPostExecute사용을 시뮬레이션하는 것입니다 Fragment.

가로와 세로 사이에서 화면을 매우 빠르게 회전하면 onPostExecute코드 에서 예외가 발생 합니다.

java.lang.IllegalStateException : Fragment MyFragment {410f6060}이 활동에 첨부되지 않았습니다

MyFragment그 동안 새로운 것이 만들어 져서 AsyncTask완료 되기 전에 활동에 첨부 되었기 때문이라고 생각합니다 . 코드 onPostExecute는 첨부되지 않은 호출합니다 MyFragment.

그러나 어떻게 해결할 수 있습니까?


1
프래그먼트 인플레이터의 뷰를 사용해야합니다. mView = inflater.inflate(R.layout.my_layout, container, false) 이제 리소스를 얻으려고 할 때이보기를 사용하십시오 mView.getResources().***. 이 버그를 해결하는 데 도움이됩니다.
— foxis

@foxis 그것은 Context당신의`mView`에 첨부 된 것을 유출합니다 .
— nhaarman

아직 확인하지 않았을 수도 있습니다. 누출을 피하기 위해 mViewonDestroy에서 null을 얻는 방법은 무엇입니까?
— foxis

답변:


774

나는 매우 간단한 대답을 찾았습니다. isAdded():

true프래그먼트가 현재 활동에 추가 된 경우 리턴 합니다.

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

에 연결되지 않은 상태에서 onPostExecute호출 되지 않도록하려면 일시 중지 또는 중지시 를 취소하는 것 입니다. 그러면 더 이상 필요하지 않을 것입니다. 그러나이 점검을 유지하는 것이 좋습니다.FragmentActivityAsyncTaskFragmentisAdded()


필자의 경우 다른 응용 프로그램 의도를 시작하면 ... 동일한 오류가 발생합니다 ... 제안이 있습니까?
— 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를 사용할 때이 문제가 발생했습니다. dialogFragment를 닫은 후 다른 활동을 시작하려고했습니다. 그런 다음이 오류가 발생했습니다. startActivity 후에 dismiss ()를 호출 하여이 오류를 피했습니다. 문제는 조각이 이미 활동에서 분리되었다는 것입니다.
— 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 인스턴스 (회전 후)가 onPostExecute ()에서 실행될 때 onDestroy ()에서 AsyncTask 인스턴스를 올바르게 취소하지 않았기 때문입니다. 이전 프래그먼트 인스턴스 (잘못된 인스턴스)가있는 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 또는 Fragment에서 액세스하는 활동 컨텍스트에 따라 다른 경우 항상 다음과 같이 활동 상태 및 조각 상태를 확인합니다.

 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
i Public ()은 다음과 같은 이유로 충분합니다. final public boolean }
— NguyenDat

제 경우에는이 검사가 멍청하지 않습니다.
— David

@David isAdded로 충분합니다. getString()if가 추락했을 때 상황을 보지 못했습니다 isAdded == true. 활동이 표시되었고 조각이 첨부 되었습니까?
— CoolMind

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

경우에 따라 작동합니다. 코드 실행을 중단하고 앱이 충돌하지 않도록하십시오.


10

나는 같은 문제에 직면했다. 나는 Erick이 참조 한대로 리소스를 얻기 위해 단일 톤 인스턴스를 추가한다.

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

당신은 또한 사용할 수 있습니다

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

이것이 도움이되기를 바랍니다.


2

환경 설정이로드 된 응용 프로그램 설정 활동이 표시되었을 때 비슷한 문제에 직면했습니다. 환경 설정 중 하나를 변경 한 다음 표시 컨텐츠를 회전시키고 환경 설정을 다시 변경하면 프래그먼트 (내 환경 설정 클래스)가 활동에 첨부되지 않았다는 메시지와 함께 충돌이 발생합니다.

디버깅 할 때 디스플레이 내용이 회전 할 때 PreferencesFragment의 onCreate () 메서드가 두 번 호출 된 것처럼 보입니다. 그것은 이미 충분히 이상했다. 그런 다음 충돌 외부를 나타내는 iExclude () 검사를 블록 외부에 추가하여 문제를 해결했습니다.

다음은 새 항목을 표시하기 위해 환경 설정 요약을 업데이트하는 리스너 코드입니다. PreferenceFragment 클래스를 확장하는 Preferences 클래스의 onCreate () 메소드에 있습니다.

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다음과 같이 클래스 를 확장하고 정적 '전역'컨텍스트 개체를 유지 관리하는 경우 활동 대신 해당 개체를 사용하여 문자열 리소스를로드 할 수 있습니다.

public class MyApplication extends Application {
    public static Context GLOBAL_APP_CONTEXT;

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

이를 사용하면 Toast수명주기에 대한 걱정없이 리소스로드를 피할 수 있습니다 .


5
나는 downvoted되고 있지만 아무도 이유를 설명하지 않았습니다. 정적 컨텍스트는 일반적으로 좋지 않지만 정적 응용 프로그램 참조가 있으면 메모리 누수가 아니라는 생각이었습니다.
— Anthony Chuinard 2016 년

이것은 해킹이 아니기 때문에 적절한 해결책이 아닙니다. @nhaarman 공유 솔루션 확인
— 비벡 쿠마 스리 바스타

0

내 경우에는 조각 메소드가 다음에 호출되었습니다.

getActivity().onBackPressed();

0

오래된 게시물이지만 가장 많이 투표 된 답변에 놀랐습니다.

이를위한 올바른 해결책은 onStop (또는 조각에서 적절한 위치)에서 비동기 작업을 취소하는 것입니다. 이렇게하면 메모리 누수 (파손 된 조각에 대한 참조를 유지하는 비동기 작업)가 발생하지 않으며 조각에서 일어나는 일을 더 잘 제어 할 수 있습니다.

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

1
가장 많이 답한 답변에는 다음이 포함됩니다. 또한 호출 cancel되지 않을 수도 있습니다 onPostExecute.
— nhaarman

cancel을 호출하면 onPostExecute가 호출되지 않습니다. 두 호출 모두 동일한 스레드에서 실행되므로 cancel 호출 후 호출되지 않습니다.
— Raz
당사 사이트를 사용함과 동시에 당사의 쿠키 정책과 개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.