방향 변경에 대한 Android Fragment 수명주기


120

호환 패키지를 사용하여 Fragments를 사용하여 2.2를 타겟팅합니다.

앱에서 프래그먼트를 사용하도록 활동을 레코딩 한 후 방향 변경 / 상태 관리가 작동하지 않아 단일 FragmentActivity와 단일 프래그먼트가있는 작은 테스트 앱을 만들었습니다.

방향 변경의 로그는 조각 OnCreateView에 대한 여러 호출로 이상합니다.

새 인스턴스를 만드는 대신 조각을 분리하고 다시 연결하는 것과 같은 것이 분명하지만 어디에서 잘못 가고 있는지 나타내는 문서를 볼 수 없습니다.

누구든지 내가 여기서 잘못하고있는 일에 대해 밝힐 수 있습니까? 감사

방향 변경 후 로그는 다음과 같습니다.

Initial creation
12-04 11:57:15.808: D/FragmentTest.FragmentTestActivity(3143): onCreate
12-04 11:57:15.945: D/FragmentTest.FragmentOne(3143): OnCreateView
12-04 11:57:16.081: D/FragmentTest.FragmentOne(3143): OnCreateView->SavedInstanceState null


Orientation Change 1
12-04 11:57:39.031: D/FragmentTest.FragmentOne(3143): onSaveInstanceState
12-04 11:57:39.031: D/FragmentTest.FragmentTestActivity(3143): onCreate
12-04 11:57:39.031: D/FragmentTest.FragmentOne(3143): OnCreateView
12-04 11:57:39.031: D/FragmentTest.FragmentOne(3143): OnCreateView->SavedInstanceState not null
12-04 11:57:39.031: D/FragmentTest.FragmentOne(3143): OnCreateView
12-04 11:57:39.167: D/FragmentTest.FragmentOne(3143): OnCreateView->SavedInstanceState null


Orientation Change 2
12-04 11:58:32.162: D/FragmentTest.FragmentOne(3143): onSaveInstanceState
12-04 11:58:32.162: D/FragmentTest.FragmentOne(3143): onSaveInstanceState
12-04 11:58:32.361: D/FragmentTest.FragmentTestActivity(3143): onCreate
12-04 11:58:32.361: D/FragmentTest.FragmentOne(3143): OnCreateView
12-04 11:58:32.361: D/FragmentTest.FragmentOne(3143): OnCreateView->SavedInstanceState not null
12-04 11:58:32.361: D/FragmentTest.FragmentOne(3143): OnCreateView
12-04 11:58:32.361: D/FragmentTest.FragmentOne(3143): OnCreateView->SavedInstanceState not null
12-04 11:58:32.498: D/FragmentTest.FragmentOne(3143): OnCreateView
12-04 11:58:32.498: D/FragmentTest.FragmentOne(3143): OnCreateView->SavedInstanceState null

주요 활동 (FragmentActivity)

public class FragmentTestActivity extends FragmentActivity {
/** Called when the activity is first created. */

private static final String TAG = "FragmentTest.FragmentTestActivity";


FragmentManager mFragmentManager;

@Override
public void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.main);

    Log.d(TAG, "onCreate");

    mFragmentManager = getSupportFragmentManager();
    FragmentTransaction fragmentTransaction = mFragmentManager.beginTransaction();

    FragmentOne fragment = new FragmentOne();

    fragmentTransaction.add(R.id.fragment_container, fragment);
    fragmentTransaction.commit();
}

그리고 조각

public class FragmentOne extends Fragment {

private static final String TAG = "FragmentTest.FragmentOne";

EditText mEditText;

@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
        Bundle savedInstanceState) {

    Log.d(TAG, "OnCreateView");

    View v = inflater.inflate(R.layout.fragmentonelayout, container, false);

    // Retrieve the text editor, and restore the last saved state if needed.
    mEditText = (EditText)v.findViewById(R.id.editText1);

    if (savedInstanceState != null) {

        Log.d(TAG, "OnCreateView->SavedInstanceState not null");

        mEditText.setText(savedInstanceState.getCharSequence("text"));
    }
    else {
        Log.d(TAG,"OnCreateView->SavedInstanceState null");
    }
    return v;
}

@Override
public void onSaveInstanceState(Bundle outState) {
    super.onSaveInstanceState(outState);

    Log.d(TAG, "FragmentOne.onSaveInstanceState");

    // Remember the current text, to restore if we later restart.
    outState.putCharSequence("text", mEditText.getText());
}

명백한

<uses-sdk android:minSdkVersion="8" />

<application
    android:icon="@drawable/ic_launcher"
    android:label="@string/app_name" >
    <activity
        android:label="@string/app_name"
        android:name=".activities.FragmentTestActivity" 
        android:configChanges="orientation">
        <intent-filter >
            <action android:name="android.intent.action.MAIN" />

            <category android:name="android.intent.category.LAUNCHER" />
        </intent-filter>
    </activity>
</application>

올바른 대답인지 모르겠지만 fragment, add (R.id.fragment_container, fragment, "MYTAG")를 추가 할 때 태그를 사용하거나 실패 할 때, replace (R.id.fragment_container, fragment, "MYTAG ")
Jason

2
조사 중입니다. 방향 변경시 기본 활동 (FragmentTestActivity)이 다시 시작되고 FragmentManager의 새 인스턴스를 얻은 다음 FindFragmentByTag를 수행하여 여전히 존재하는 조각을 찾습니다. 따라서 조각은 기본 활동의 재생산 동안 유지됩니다. 조각을 찾고 아무것도하지 않으면 어쨌든 MainActivity와 함께 다시 표시됩니다.
MartinS

답변:


189

당신은 당신의 조각을 다른 조각 위에 레이어링하고 있습니다.

구성 변경이 발생하면 이전 프래그먼트가 다시 생성 될 때 새 활동에 자신을 추가합니다. 이것은 대부분의 경우 뒤쪽에 엄청난 고통입니다.

새 조각을 다시 만드는 대신 동일한 조각을 사용하여 발생하는 오류를 중지 할 수 있습니다. 다음 코드를 추가하기 만하면됩니다.

if (savedInstanceState == null) {
    // only create fragment if activity is started for the first time
    mFragmentManager = getSupportFragmentManager();
    FragmentTransaction fragmentTransaction = mFragmentManager.beginTransaction();

    FragmentOne fragment = new FragmentOne();

    fragmentTransaction.add(R.id.fragment_container, fragment);
    fragmentTransaction.commit();
} else {        
    // do nothing - fragment is recreated automatically
}

그러나 경고 : 수명주기가 미묘하게 변경되므로 Fragment 내부에서 활동 뷰에 액세스하려고하면 문제가 발생합니다. (조각에서 부모 활동에서 뷰를 가져 오는 것은 쉽지 않습니다).


54
"이것은 대부분의 시간에 후방에 엄청난 고통입니다"(엄지

1
FragmentStatePagerAdapter ... 어떤 제안과 함께 ViewPage를 사용하는 경우 동일한 시나리오를 어떻게 처리 할 수 ​​있습니까?
CoDe 2015

5
공식 문서에 유사한 주장이 있습니까? 이것은 가이드에 명시된 내용과 모순되지 "when the activity is destroyed, so are all fragments"않습니까? 이후 "When the screen orientation changes, the system destroys and recreates the activity [...]".
cYrus

4
Cyrus-아니요, Activity는 실제로 파괴되고, 여기에 포함 된 Fragment는 Activity에서만 참조되는 것이 아니라 FragmentManager에서 참조되므로 유지되고 읽혀집니다.
그레엄

4
FragmentManager에서 찾은 후 조각 onCreate 및 onDestroy 메서드와 해시 코드를 로깅하면 조각이 파괴되었음을 분명히 알 수 있습니다. 자동으로 다시 만들어지고 다시 연결됩니다. 당신이 조각에서 onCreate 방법 setRetainInstance (true)를 넣어 경우에만 정말로 파괴 될 이뤄져
Lemao1981

87

이 책 을 인용하자면 "일관된 사용자 경험을 보장하기 위해 Android는 구성 변경으로 인해 활동이 다시 시작될 때 조각 레이아웃과 관련 백 스택을 유지합니다." (124 쪽)

접근하는 방법은 먼저 Fragment 백 스택이 이미 채워져 있는지 확인하고 그렇지 않은 경우에만 새 조각 인스턴스를 만드는 것입니다.

@Override
public void onCreate(Bundle savedInstanceState) {

        ...    

    FragmentOne fragment = (FragmentOne) mFragmentManager.findFragmentById(R.id.fragment_container); 

    if (fragment == null) {
        FragmentTransaction fragmentTransaction = mFragmentManager.beginTransaction();
        fragmentTransaction.add(R.id.fragment_container, new FragmentOne());
        fragmentTransaction.commit();
    }
}

2
당신은 아마 이것으로 저에게 많은 시간을 절약했습니다. 이 답변을 Graeme의 답변과 결합하여 구성 변경 및 조각을 처리하는 완벽한 솔루션을 얻을 수 있습니다.
azpublic

10
이것은 실제로 정답이지 표시된 답이 아닙니다. 대단히 감사합니다!
Uriel Frankel 2013

ViewPager Fragment 구현의 경우 동일한 시나리오를 어떻게 처리 할 수 ​​있습니까?
CoDe 2015

이 작은 보석은 내가 며칠 동안 살펴본 문제를 해결하는 데 도움이되었습니다. 감사합니다! 이것은 확실히 해결책입니다.
Whome

1
@SharpEdge 여러 조각이있는 경우 컨테이너에 추가 할 때 태그를 지정한 다음 (findFragmentById 대신) mFragmentManager.findFragmentByTag를 사용하여 참조를 가져와야합니다. 이렇게하면 각 조각의 클래스를 알 수 있습니다. 올바르게 캐스트
k29

10

활동의 onCreate () 메서드는 본 것처럼 방향이 변경된 후에 호출됩니다. 따라서 활동의 방향이 변경된 후 Fragment를 추가하는 FragmentTransaction을 실행하지 마십시오.

@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    if (savedInstanceState==null) {
        //do your stuff
    }
}

조각은 변경되지 않아야하며 변경되지 않아야합니다.


조각이 생성되고 추가 된 후에 인스턴스가 저장된다는 것을 알고 있습니까? Fragment가 추가되기 직전에 사용자가 회전하면 모자를 의미합니까? 조각 상태를 포함하지 않는 null이 아닌 savedInstanceState가 계속 유지됩니다.
Farid

4

@Override사용하여 FragmentActivity를 수행 할 수 있습니다 onSaveInstanceState(). super.onSaveInstanceState()메서드에서를 호출하지 마십시오 .


2
이것은 이미 매우 복잡한 프로세스에서 더 많은 잠재적 문제를 야기하는 활동 라이프 사이클을 깨뜨릴 가능성이 높습니다. FragmentActivity의 소스 코드를 살펴보십시오. 여기에 모든 조각의 상태가 저장됩니다.
Brian

다른 방향에 대해 다른 어댑터 수가 있다는 문제가 있습니다. 그래서 나는 항상 장치를 돌리고 일부 페이지를 스 와이프 한 후 이상한 상황이 발생했습니다. savedInstance의 회전으로는 (befor를 내가 (거짓을 setSavedEnabled 사용) 모든 방향 변경에 큰 메모리 누수로 끝났다) 메모리 누수없이 잘 작동
Informatic0re

0

항상 nullpointer 예외를 방지해야하므로 먼저 saveinstance 메서드에서 번들 정보를 확인해야합니다. 이 블로그 링크 를 확인하는 간단한 설명

public static class DetailsActivity extends Activity {

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

        if (getResources().getConfiguration().orientation
            == Configuration.ORIENTATION_LANDSCAPE) {
            // If the screen is now in landscape mode, we can show the
            // dialog in-line with the list so we don't need this activity.
            finish();
            return;
        }

        if (savedInstanceState == null) {
            // During initial setup, plug in the details fragment.
            DetailsFragment details = new DetailsFragment();
            details.setArguments(getIntent().getExtras());
            getFragmentManager().beginTransaction().add(android.R.id.content, details).commit();
        }
    } 
}

0

프로젝트 만 수행하는 경우 프로젝트 관리자는 전환 기능 화면을 달성해야한다고 말하지만 다른 레이아웃을로드하는 화면 전환을 원하지는 않습니다 (레이아웃 및 레이아웃 포트 시스템을 만들 수 있습니다.

화면 상태를 자동으로 결정하고 해당 레이아웃을로드합니다.) 활동이나 프래그먼트를 다시 초기화해야하기 때문에 사용자 경험이 좋지 않고 화면 스위치에 직접 표시되지 않습니다. Url = YgNfP-vHy-Nuldi7YHTfNet3AtLdN-w__O3z1wLOnzr3wDjYo7X7PYdNyhw8R24ZE22xiKnydni7R0r35s2fOLcHOiLGYT9Qh_fjqtytJki & wd = & eqid = f258719e0001f24000000004585a1082 = & eqid = f258719

전제는 다음과 같이 layout_weight의 레이아웃 방식의 가중치를 사용하는 레이아웃입니다.

<LinearLayout
Android:id= "@+id/toplayout"
Android:layout_width= "match_parent"
Android:layout_height= "match_parent"
Android:layout_weight= "2"
Android:orientation= "horizontal" >

따라서 내 접근 방식은 화면 전환시 뷰 파일의 새 레이아웃을로드 할 필요가없고 onConfigurationChanged 동적 가중치에서 레이아웃을 수정할 필요가 없습니다. 다음 단계는 다음과 같습니다. 1 첫 번째 설정 : 활동 속성의 AndroidManifest.xml : android : configChanges = "keyboardHidden | orientation | screenSize"화면 전환을 방지하려면 다시로드하지 않도록하여 onConfigurationChanged 2 재 작성 활동 또는 onConfigurationChanged 메소드의 프래그먼트를 모니터링 할 수 있습니다.

@Override
Public void onConfigurationChanged (Configuration newConfig) {
    Super.onConfigurationChanged (newConfig);
    SetContentView (R.layout.activity_main);
    if (newConfig.orientation == Configuration.ORIENTATION_LANDSCAPE) {
        //On the layout / / weight adjustment
        LinearLayout toplayout = (LinearLayout) findViewById (R.id.toplayout);
        LinearLayout.LayoutParams LP = new LayoutParams(LinearLayout.LayoutParams.MATCH_PARENT, 0, 2.0f);
        Toplayout.setLayoutParams (LP);
        LinearLayout tradespace_layout = (LinearLayout) findViewById(R.id.tradespace_layout);
        LinearLayout.LayoutParams LP3 = new LayoutParams(LinearLayout.LayoutParams.MATCH_PARENT, 0, 2.8f);
        Tradespace_layout.setLayoutParams (LP3);
    }
    else if (newConfig.orientation == Configuration.ORIENTATION_PORTRAIT)
    {
        //On the layout / / weight adjustment
        LinearLayout toplayout = (LinearLayout) findViewById (R.id.toplayout);
        LinearLayout.LayoutParams LP = new LayoutParams (LinearLayout.LayoutParams.MATCH_PARENT, 0, 2.8f);
        Toplayout.setLayoutParams (LP);
        LinearLayout tradespace_layout = (LinearLayout) findViewById (R.id.tradespace_layout);
        LinearLayout.LayoutParams LP3 = new LayoutParams (LinearLayout.LayoutParams.MATCH_PARENT, 0, 2.0f);
        Tradespace_layout.setLayoutParams (LP3);
    }
}

0

구성 변경시 프레임 워크는 조각의 새 인스턴스를 만들고 활동에 추가합니다. 그래서이 대신 :

FragmentOne fragment = new FragmentOne();

fragmentTransaction.add(R.id.fragment_container, fragment);

이 작업을 수행:

if (mFragmentManager.findFragmentByTag(FRAG1_TAG) == null) {
    FragmentOne fragment = new FragmentOne();

    fragmentTransaction.add(R.id.fragment_container, fragment, FRAG1_TAG);
}

프레임 워크는 setRetainInstance (true)를 호출하지 않는 한 방향 변경시 FragmentOne의 새 인스턴스를 추가합니다.이 경우 FragmentOne의 이전 인스턴스가 추가됩니다.

당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.