조각 onCreateView 및 onActivityCreated가 두 번 호출 됨


101

Android 4.0 ICS 및 조각을 사용하여 앱을 개발 중입니다.

ICS 4.0.3 (API 레벨 15) API의 데모 예제 앱에서 수정 된 다음 예제를 고려하십시오.

public class FragmentTabs extends Activity {

private static final String TAG = FragmentTabs.class.getSimpleName();

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

    final ActionBar bar = getActionBar();
    bar.setNavigationMode(ActionBar.NAVIGATION_MODE_TABS);
    bar.setDisplayOptions(0, ActionBar.DISPLAY_SHOW_TITLE);

    bar.addTab(bar.newTab()
            .setText("Simple")
            .setTabListener(new TabListener<SimpleFragment>(
                    this, "mysimple", SimpleFragment.class)));

    if (savedInstanceState != null) {
        bar.setSelectedNavigationItem(savedInstanceState.getInt("tab", 0));
        Log.d(TAG, "FragmentTabs.onCreate tab: " + savedInstanceState.getInt("tab"));
        Log.d(TAG, "FragmentTabs.onCreate number: " + savedInstanceState.getInt("number"));
    }

}

@Override
protected void onSaveInstanceState(Bundle outState) {
    super.onSaveInstanceState(outState);
    outState.putInt("tab", getActionBar().getSelectedNavigationIndex());
}

public static class TabListener<T extends Fragment> implements ActionBar.TabListener {
    private final Activity mActivity;
    private final String mTag;
    private final Class<T> mClass;
    private final Bundle mArgs;
    private Fragment mFragment;

    public TabListener(Activity activity, String tag, Class<T> clz) {
        this(activity, tag, clz, null);
    }

    public TabListener(Activity activity, String tag, Class<T> clz, Bundle args) {
        mActivity = activity;
        mTag = tag;
        mClass = clz;
        mArgs = args;

        // Check to see if we already have a fragment for this tab, probably
        // from a previously saved state.  If so, deactivate it, because our
        // initial state is that a tab isn't shown.
        mFragment = mActivity.getFragmentManager().findFragmentByTag(mTag);
        if (mFragment != null && !mFragment.isDetached()) {
            Log.d(TAG, "constructor: detaching fragment " + mTag);
            FragmentTransaction ft = mActivity.getFragmentManager().beginTransaction();
            ft.detach(mFragment);
            ft.commit();
        }
    }

    public void onTabSelected(Tab tab, FragmentTransaction ft) {
        if (mFragment == null) {
            mFragment = Fragment.instantiate(mActivity, mClass.getName(), mArgs);
            Log.d(TAG, "onTabSelected adding fragment " + mTag);
            ft.add(android.R.id.content, mFragment, mTag);
        } else {
            Log.d(TAG, "onTabSelected attaching fragment " + mTag);
            ft.attach(mFragment);
        }
    }

    public void onTabUnselected(Tab tab, FragmentTransaction ft) {
        if (mFragment != null) {
            Log.d(TAG, "onTabUnselected detaching fragment " + mTag);
            ft.detach(mFragment);
        }
    }

    public void onTabReselected(Tab tab, FragmentTransaction ft) {
        Toast.makeText(mActivity, "Reselected!", Toast.LENGTH_SHORT).show();
    }
}

public static class SimpleFragment extends Fragment {
    TextView textView;
    int mNum;

    /**
     * When creating, retrieve this instance's number from its arguments.
     */
    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        Log.d(FragmentTabs.TAG, "onCreate " + (savedInstanceState != null ? ("state " + savedInstanceState.getInt("number")) : "no state"));
        if(savedInstanceState != null) {
            mNum = savedInstanceState.getInt("number");
        } else {
            mNum = 25;
        }
    }

    @Override
    public void onActivityCreated(Bundle savedInstanceState) {
        Log.d(TAG, "onActivityCreated");
        if(savedInstanceState != null) {
            Log.d(TAG, "saved variable number: " + savedInstanceState.getInt("number"));
        }
        super.onActivityCreated(savedInstanceState);
    }

    @Override
    public void onSaveInstanceState(Bundle outState) {
        Log.d(TAG, "onSaveInstanceState saving: " + mNum);
        outState.putInt("number", mNum);
        super.onSaveInstanceState(outState);
    }

    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
        Log.d(FragmentTabs.TAG, "onCreateView " + (savedInstanceState != null ? ("state: " + savedInstanceState.getInt("number")) : "no state"));
        textView = new TextView(getActivity());
        textView.setText("Hello world: " + mNum);
        textView.setBackgroundDrawable(getResources().getDrawable(android.R.drawable.gallery_thumb));
        return textView;
    }
}

}

다음은이 예제를 실행 한 다음 전화기를 회전하여 검색 한 출력입니다.

06-11 11:31:42.559: D/FragmentTabs(10726): onTabSelected adding fragment mysimple
06-11 11:31:42.559: D/FragmentTabs(10726): onCreate no state
06-11 11:31:42.559: D/FragmentTabs(10726): onCreateView no state
06-11 11:31:42.567: D/FragmentTabs(10726): onActivityCreated
06-11 11:31:45.286: D/FragmentTabs(10726): onSaveInstanceState saving: 25
06-11 11:31:45.325: D/FragmentTabs(10726): onCreate state 25
06-11 11:31:45.340: D/FragmentTabs(10726): constructor: detaching fragment mysimple
06-11 11:31:45.340: D/FragmentTabs(10726): onTabSelected attaching fragment mysimple
06-11 11:31:45.348: D/FragmentTabs(10726): FragmentTabs.onCreate tab: 0
06-11 11:31:45.348: D/FragmentTabs(10726): FragmentTabs.onCreate number: 0
06-11 11:31:45.348: D/FragmentTabs(10726): onCreateView state: 25
06-11 11:31:45.348: D/FragmentTabs(10726): onActivityCreated
06-11 11:31:45.348: D/FragmentTabs(10726): saved variable number: 25
06-11 11:31:45.348: D/FragmentTabs(10726): onCreateView no state
06-11 11:31:45.348: D/FragmentTabs(10726): onActivityCreated

내 질문은 왜 onCreateView와 onActivityCreated가 두 번 호출됩니까? 처음에는 저장된 상태의 번들을 사용하고 두 번째는 null savedInstanceState를 사용합니까?

이로 인해 회전시 조각의 상태를 유지하는 데 문제가 발생합니다.


답변:


45

나는 이것에 대해 잠시 동안 머리를 긁적이며 Dave의 설명이 이해하기 조금 어렵 기 때문에 내 (분명히 작동하는) 코드를 게시 할 것입니다.

private class TabListener<T extends Fragment> implements ActionBar.TabListener {
    private Fragment mFragment;
    private Activity mActivity;
    private final String mTag;
    private final Class<T> mClass;

    public TabListener(Activity activity, String tag, Class<T> clz) {
        mActivity = activity;
        mTag = tag;
        mClass = clz;
        mFragment=mActivity.getFragmentManager().findFragmentByTag(mTag);
    }

    public void onTabSelected(Tab tab, FragmentTransaction ft) {
        if (mFragment == null) {
            mFragment = Fragment.instantiate(mActivity, mClass.getName());
            ft.replace(android.R.id.content, mFragment, mTag);
        } else {
            if (mFragment.isDetached()) {
                ft.attach(mFragment);
            }
        }
    }

    public void onTabUnselected(Tab tab, FragmentTransaction ft) {
        if (mFragment != null) {
            ft.detach(mFragment);
        }
    }

    public void onTabReselected(Tab tab, FragmentTransaction ft) {
    }
}

보시다시피 생성자에서 분리하지 않고 add 대신 replace를 사용한다는 점을 제외하면 Android 샘플과 거의 비슷합니다 .

많은 헤드 스크래칭과 시행 착오 끝에 생성자에서 조각을 찾으면 이중 onCreateView 문제가 마술처럼 사라지는 것처럼 보입니다. 저장 / 복원 상태).


완벽하게 잘 작동합니다! 당신은 내 밤의 잠을 구했습니다! 감사합니다 :)
jaibatrik

당신은 또한 fragment.getClass ()를 사용할 수 있습니다 대한 getName ()는 클래스 변수를 제거하고 호출에서 매개 변수를 제거 할 경우.
벤 Sewards

"previous ref. TabListener"Android 샘플-tnx와 완벽하게 작동합니다. 최신 Android "TabListener 참조 샘플"[2013 년 4 ix 기준]은 정말, 정말 잘못되었습니다.
Grzegorz Dev

ft.commit () 메서드 호출은 어디에 있습니까?
MSaudi 2011

1
@MuhammadBabar, stackoverflow.com/questions/23248789/… 참조 . add대신 사용 replace하고 화면을 회전하면 많은 조각이 있습니다 onCreateView().
CoolMind

26

좋아요, 제가 알아 낸 내용입니다.

내가 이해하지 못한 것은 구성 변경 (전화 회전)이 발생할 때 활동에 연결된 모든 조각이 다시 생성되어 활동에 다시 추가된다는 것입니다. (말이되는)

TabListener 생성자에서 발생한 일은 탭이 발견되어 활동에 연결되면 분리 된 것입니다. 아래를 참조하십시오.

mFragment = mActivity.getFragmentManager().findFragmentByTag(mTag);
    if (mFragment != null && !mFragment.isDetached()) {
        Log.d(TAG, "constructor: detaching fragment " + mTag);
        FragmentTransaction ft = mActivity.getFragmentManager().beginTransaction();
        ft.detach(mFragment);
        ft.commit();
    }

나중에 onCreate 활동에서 이전에 선택한 탭이 저장된 인스턴스 상태에서 선택되었습니다. 아래를 참조하십시오.

if (savedInstanceState != null) {
    bar.setSelectedNavigationItem(savedInstanceState.getInt("tab", 0));
    Log.d(TAG, "FragmentTabs.onCreate tab: " + savedInstanceState.getInt("tab"));
    Log.d(TAG, "FragmentTabs.onCreate number: " + savedInstanceState.getInt("number"));
}

탭을 선택하면 onTabSelected 콜백에 다시 연결됩니다.

public void onTabSelected(Tab tab, FragmentTransaction ft) {
    if (mFragment == null) {
        mFragment = Fragment.instantiate(mActivity, mClass.getName(), mArgs);
        Log.d(TAG, "onTabSelected adding fragment " + mTag);
        ft.add(android.R.id.content, mFragment, mTag);
    } else {
        Log.d(TAG, "onTabSelected attaching fragment " + mTag);
        ft.attach(mFragment);
    }
}

연결되는 조각은 onCreateView 및 onActivityCreated 메서드에 대한 두 번째 호출입니다. (첫 번째는 시스템이 활동 및 모든 첨부 된 조각을 다시 생성 할 때) 처음에는 onSavedInstanceState 번들이 데이터를 저장했지만 두 번째에는 저장하지 않았습니다.

해결책은 TabListener 생성자에서 조각을 분리하지 않고 연결된 상태로 두는 것입니다. (여전히 FragmentManager에서 태그로 찾아야합니다.) 또한 onTabSelected 메서드에서 조각을 연결하기 전에 분리되었는지 확인합니다. 이 같은:

public void onTabSelected(Tab tab, FragmentTransaction ft) {
            if (mFragment == null) {
                mFragment = Fragment.instantiate(mActivity, mClass.getName(), mArgs);
                Log.d(TAG, "onTabSelected adding fragment " + mTag);
                ft.add(android.R.id.content, mFragment, mTag);
            } else {

                if(mFragment.isDetached()) {
                    Log.d(TAG, "onTabSelected attaching fragment " + mTag);
                    ft.attach(mFragment);
                } else {
                    Log.d(TAG, "onTabSelected fragment already attached " + mTag);
                }
            }
        }

4
언급 된 "TabListener 생성자에서 조각을 분리하지 않기"솔루션은 탭 조각이 서로 겹치도록합니다. 다른 조각 내용을 볼 수 있습니다. 나를 위해 작동하지 않습니다.
Aksel Fatih

@ flock.dux 서로 겹친다는 것이 무슨 뜻인지 잘 모르겠습니다. Android는 레이아웃 방식을 관리하므로 연결 또는 분리 만 지정합니다. 더 많은 일이 있어야합니다. 예제 코드로 새로운 질문을한다면 무슨 일이 일어나고 있는지 알아낼 수있을 것입니다.
Dave

1
나는 같은 문제가 있었다 (Android에서 여러 조각 생성자 호출). 당신의 발견은 내 문제를 해결합니다. 내가 이해하지 못한 것은 구성 변경이 발생할 때 활동에 연결된 모든 조각이 다시 만들어지고 활동에 다시 추가된다는 것입니다. (어떤 의미가 있습니다)
유진

26

나는 (때때로 대체 될) 하나의 조각 만 운반하는 간단한 활동에 동일한 문제가 있습니다. 그런 다음 활동이 아닌 조각에서만 onSaveInstanceState를 사용하고 onCreateView를 사용하여 savedInstanceState를 확인한다는 것을 깨달았습니다.

장치에서 조각을 포함하는 활동이 다시 시작되고 onCreated가 호출됩니다. 거기에 필요한 조각을 첨부했습니다 (첫 번째 시작에서 정확함).

기기에서 Android는 먼저 표시되었던 프래그먼트를 다시 만든 다음 내 프래그먼트가 연결된 포함 활동의 onCreate를 호출하여 원래 표시되는 것을 대체합니다.

이를 방지하려면 savedInstanceState를 확인하기 위해 활동을 변경했습니다.

protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);

if (savedInstanceState != null) {
/**making sure you are not attaching the fragments again as they have 
 been 
 *already added
 **/
 return; 
 }
 else{
  // following code to attach fragment initially
 }

 }

활동의 onSaveInstanceState도 덮어 쓰지 않았습니다.


감사합니다. 그것은 AppCompatActivity + PreferenceFragmentCompat로 나를 도왔고 방향 변경 후 환경 설정 조각에 대화 상자를 표시하는 동안 충돌이 발생했습니다. 조각 관리자가 두 번째 조각 생성에서 null 이었기 때문입니다.
RoK

12

두 upvoted 답변은 여기 탐색 모드와 함께 활동을위한 솔루션을 보여 NAVIGATION_MODE_TABS,하지만 난과 같은 문제가 있었다 NAVIGATION_MODE_LIST. 화면 방향이 변경되면 내 프래그먼트가 설명 할 수없는 상태를 잃게되었고 정말 짜증이났습니다. 고맙게도 도움이되는 코드 덕분에 알아낼 수있었습니다.

기본적으로 목록 탐색을 사용할 때``onNavigationItemSelected () is automatically called when your activity is created/re-created, whether you like it or not. To prevent your Fragment'sonCreateView () from being called twice, this initial automatic call toonNavigationItemSelected () should check whether the Fragment is already in existence inside your Activity. If it is, return immediately, because there is nothing to do; if it isn't, then simply construct the Fragment and add it to the Activity like you normally would. Performing this check prevents your Fragment from needlessly being created again, which is what causesonCreateView ()`가 두 번 호출됩니다!

onNavigationItemSelected()아래 구현을 참조하십시오 .

public class MyActivity extends FragmentActivity implements ActionBar.OnNavigationListener
{
    private static final String STATE_SELECTED_NAVIGATION_ITEM = "selected_navigation_item";

    private boolean mIsUserInitiatedNavItemSelection;

    // ... constructor code, etc.

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

        if (savedInstanceState.containsKey(STATE_SELECTED_NAVIGATION_ITEM))
        {
            getActionBar().setSelectedNavigationItem(savedInstanceState.getInt(STATE_SELECTED_NAVIGATION_ITEM));
        }
    }

    @Override
    public void onSaveInstanceState(Bundle outState)
    {
        outState.putInt(STATE_SELECTED_NAVIGATION_ITEM, getActionBar().getSelectedNavigationIndex());

        super.onSaveInstanceState(outState);
    }

    @Override
    public boolean onNavigationItemSelected(int position, long id)
    {    
        Fragment fragment;
        switch (position)
        {
            // ... choose and construct fragment here
        }

        // is this the automatic (non-user initiated) call to onNavigationItemSelected()
        // that occurs when the activity is created/re-created?
        if (!mIsUserInitiatedNavItemSelection)
        {
            // all subsequent calls to onNavigationItemSelected() won't be automatic
            mIsUserInitiatedNavItemSelection = true;

            // has the same fragment already replaced the container and assumed its id?
            Fragment existingFragment = getSupportFragmentManager().findFragmentById(R.id.container);
            if (existingFragment != null && existingFragment.getClass().equals(fragment.getClass()))
            {
                return true; //nothing to do, because the fragment is already there 
            }
        }

        getSupportFragmentManager().beginTransaction().replace(R.id.container, fragment).commit();
        return true;
    }
}

여기 에서이 솔루션에 대한 영감을 얻었습니다 .


이 솔루션은 내비게이션 서랍과 관련된 비슷한 문제에 대해 작동합니다. ID로 기존 조각을 찾고 다시 만들기 전에 새 조각과 동일한 클래스가 있는지 확인합니다.
William

8

매번 TabListener를 인스턴스화하기 때문에 시스템이 savedInstanceState에서 조각을 다시 생성하고 onCreate에서 다시 수행하기 때문인 것 같습니다.

당신은 if(savedInstanceState == null)그것을 a에 감싸 야한다. 그래서 그것은 savedInstanceState가없는 경우에만 발생한다.


나는 그것이 옳다고 생각하지 않는다. 내 addTab 코드를 if 블록에 래핑하면 조각이 활동에 첨부되지만 탭이 없습니다. onCreate 메서드에서 매번 탭을 추가해야하는 것으로 보입니다. 나는 이것을 계속 조사하고 내가 더 잘 이해하는대로 더 많이 게시 할 것이다.
Dave
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.