วงจรชีวิตของ Android Fragment เหนือการเปลี่ยนแปลงการวางแนว


120

การใช้แพ็คเกจความเข้ากันได้เพื่อกำหนดเป้าหมาย 2.2 โดยใช้ Fragments

หลังจากเข้ารหัสกิจกรรมเพื่อใช้แฟรกเมนต์ในแอพแล้วฉันไม่สามารถรับการเปลี่ยนแปลงการวางแนว / การจัดการสถานะทำงานได้ดังนั้นฉันจึงสร้างแอพทดสอบขนาดเล็กที่มี FragmentActivity เดียวและ Fragment เดียว

บันทึกจากการเปลี่ยนแปลงการวางแนวเป็นเรื่องแปลกโดยมีการเรียกหลายครั้งไปยังส่วน 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>

ฉันไม่รู้ว่าเป็นคำตอบที่ถูกต้องหรือไม่ แต่ลองใช้แท็กเมื่อเพิ่มส่วนเพิ่ม (R.id.fragment_container, fragment, "MYTAG") หรือไม่สำเร็จให้แทนที่ (R.id.fragment_container, fragment, "MYTAG ")
Jason

2
ทำการสืบสวนบางอย่าง เมื่อกิจกรรมหลัก (FragmentTestActivity) เริ่มต้นใหม่เมื่อมีการเปลี่ยนแปลงการวางแนวและฉันได้รับอินสแตนซ์ใหม่ของ FragmentManager จากนั้นดำเนินการ FindFragmentByTag เพื่อค้นหาส่วนที่ยังคงมีอยู่ดังนั้นส่วนที่ถูกเก็บไว้จะถูกเก็บไว้ในการพักผ่อนหย่อนใจของกิจกรรมหลัก หากฉันพบชิ้นส่วนและไม่ทำอะไรเลยมันจะแสดงซ้ำพร้อมกับ MainActivity อยู่ดี
MartinS

คำตอบ:


189

คุณกำลังจัดวาง Fragments ของคุณไว้ด้านบนของอีกชิ้นหนึ่ง

เมื่อเกิดการเปลี่ยนแปลงการกำหนดค่า Fragment เก่าจะเพิ่มตัวเองลงในกิจกรรมใหม่เมื่อสร้างขึ้นใหม่ นี่เป็นความเจ็บปวดอย่างมากที่ด้านหลังเกือบตลอดเวลา

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

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 เนื่องจากวงจรชีวิตจะเปลี่ยนไปอย่างละเอียด (การรับมุมมองจากกิจกรรมหลักจาก Fragment ไม่ใช่เรื่องง่าย)


54
"นี่คือความเจ็บปวดอย่างมากที่ด้านหลังเกือบตลอดเวลา" (ยกนิ้วให้)
วิ่ง

1
จะจัดการกับสถานการณ์เดียวกันได้อย่างไรในกรณีที่ ViewPage ใช้กับ FragmentStatePagerAdapter ... มีข้อเสนอแนะอย่างไร
CoDe

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 - ไม่กิจกรรมนั้นถูกทำลายอย่างแท้จริง Fragments ที่มีอยู่นั้นถูกอ้างอิงใน FragmentManager ไม่ใช่จากกิจกรรมเพียงอย่างเดียวดังนั้นจึงยังคงอยู่และถูกอ่าน
Graeme

4
การบันทึกชิ้นส่วนเมธอด onCreate และ onDestroy รวมทั้งแฮชโค้ดหลังจากพบใน FragmentManager แสดงให้เห็นอย่างชัดเจนว่าชิ้นส่วนนั้นถูกทำลาย เพียงแค่สร้างขึ้นใหม่และติดตั้งใหม่โดยอัตโนมัติ เฉพาะในกรณีที่คุณใส่ setRetainInstance (true) ในส่วนของวิธีการสร้างมันจะไม่ถูกทำลายจริงๆ
Lemao1981

87

เพื่ออ้างอิงหนังสือเล่มนี้ "เพื่อให้แน่ใจว่าผู้ใช้จะได้รับประสบการณ์ที่สอดคล้องกัน Android จะยังคงใช้เค้าโครง Fragment และ back stack ที่เกี่ยวข้องเมื่อมีการรีสตาร์ทกิจกรรมเนื่องจากการเปลี่ยนแปลงการกำหนดค่า" (น. 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

วิธีจัดการสถานการณ์เดียวกันในกรณีของการใช้งาน ViewPager Fragment
CoDe

อัญมณีชิ้นเล็ก ๆ นี้ช่วยแก้ปัญหาที่ฉันกำลังมองหามาหลายวัน ขอบคุณ! นี่คือทางออกแน่นอน
ใคร

1
@SharpEdge หากคุณมีหลายส่วนคุณควรให้แท็กเมื่อเพิ่มลงในคอนเทนเนอร์จากนั้นใช้ mFragmentManager.findFragmentByTag (แทน findFragmentById) เพื่อรับการอ้างอิงถึงพวกเขาด้วยวิธีนี้คุณจะทราบคลาสของแต่ละส่วนและสามารถ ร่ายอย่างถูกต้อง
k29

10

เมธอด onCreate () ของกิจกรรมของคุณถูกเรียกใช้หลังจากการเปลี่ยนแปลงการวางแนวตามที่คุณเห็น ดังนั้นอย่าดำเนินการ FragmentTransaction ที่เพิ่ม Fragment หลังจากการเปลี่ยนทิศทางในกิจกรรมของคุณ

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

Fragments ควรและต้องไม่มีการเปลี่ยนแปลง


เรารู้หรือไม่ว่าอินสแตนซ์จะถูกบันทึกหลังจากสร้างและเพิ่มส่วนแล้ว ฉันหมายถึงหมวกถ้าผู้ใช้หมุนก่อนที่จะเพิ่ม Fragment? เราจะยังคงมี null ไม่ใช่ savedInstanceState ที่ไม่ได้มีส่วนรัฐ
ฟาริด

4

คุณสามารถ@OverrideFragmentActivity โดยใช้onSaveInstanceState()ไฟล์. โปรดอย่าโทรไปsuper.onSaveInstanceState()ที่วิธีการนี้


2
สิ่งนี้น่าจะทำลายวงจรชีวิตของกิจกรรมที่แนะนำปัญหาที่อาจเกิดขึ้นได้มากขึ้นในกระบวนการที่ค่อนข้างยุ่งเหยิงนี้ ดูซอร์สโค้ดของ FragmentActivity: มันกำลังบันทึกสถานะของชิ้นส่วนทั้งหมดที่นั่น
Brian

ฉันมีปัญหาว่าฉันมีจำนวนอะแดปเตอร์ที่แตกต่างกันสำหรับการวางแนวที่ต่างกัน ดังนั้นฉันจึงมีสถานการณ์แปลก ๆ อยู่เสมอหลังจากเปิดเครื่องและปัดบางหน้าฉันได้สิ่งที่เก่าและผิด ด้วยการพลิกกลับของบันทึกอินสแตนซ์มันจะทำงานได้ดีที่สุดโดยไม่มีการรั่วไหลของหน่วยความจำ (ฉันใช้ 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

หลักฐานคือเค้าโครงของคุณโดยใช้น้ำหนักของรูปแบบของ 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 rewrite activity หรือ fragment ในเมธอด 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);
}

โปรดทราบว่าเฟรมเวิร์กจะเพิ่มอินสแตนซ์ใหม่ของ FragmentOne ในการเปลี่ยนทิศทางเว้นแต่คุณจะเรียกใช้ setRetainInstance (true) ซึ่งในกรณีนี้จะเพิ่มอินสแตนซ์เก่าของ FragmentOne

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