활동에 첨부되지 않은 조각 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.