ฉันจะรักษาสถานะแฟรกเมนต์ได้อย่างไรเมื่อเพิ่มไปยังสแต็กหลัง?


160

ฉันเขียนกิจกรรมจำลองที่สลับระหว่างสองชิ้น เมื่อคุณไปจาก FragmentA ถึง FragmentB FragmentA จะถูกเพิ่มเข้าไปในกองหลัง อย่างไรก็ตามเมื่อฉันกลับไปที่ FragmentA (โดยกดปุ่มย้อนกลับ) FragmentA ใหม่ทั้งหมดจะถูกสร้างขึ้นและสถานะจะหายไป ฉันรู้สึกว่าฉันอยู่หลังสิ่งเดียวกันกับคำถามนี้แต่ฉันได้รวมตัวอย่างโค้ดที่สมบูรณ์เพื่อช่วยขจัดปัญหา:

public class FooActivity extends Activity {
  @Override public void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    final FragmentTransaction transaction = getFragmentManager().beginTransaction();
    transaction.replace(android.R.id.content, new FragmentA());
    transaction.commit();
  }

  public void nextFragment() {
    final FragmentTransaction transaction = getFragmentManager().beginTransaction();
    transaction.replace(android.R.id.content, new FragmentB());
    transaction.addToBackStack(null);
    transaction.commit();
  }

  public static class FragmentA extends Fragment {
    @Override public View onCreateView(LayoutInflater inflater, ViewGroup container,
        Bundle savedInstanceState) {
      final View main = inflater.inflate(R.layout.main, container, false);
      main.findViewById(R.id.next_fragment_button).setOnClickListener(new View.OnClickListener() {
        public void onClick(View v) {
          ((FooActivity) getActivity()).nextFragment();
        }
      });
      return main;
    }

    @Override public void onSaveInstanceState(Bundle outState) {
      super.onSaveInstanceState(outState);
      // Save some state!
    }
  }

  public static class FragmentB extends Fragment {
    @Override public View onCreateView(LayoutInflater inflater, ViewGroup container,
        Bundle savedInstanceState) {
      return inflater.inflate(R.layout.b, container, false);
    }
  }
}

เมื่อมีการเพิ่มข้อความบันทึก:

07-05 14:28:59.722 D/OMG     ( 1260): FooActivity.onCreate
07-05 14:28:59.742 D/OMG     ( 1260): FragmentA.onCreateView
07-05 14:28:59.742 D/OMG     ( 1260): FooActivity.onResume
<Tap Button on FragmentA>
07-05 14:29:12.842 D/OMG     ( 1260): FooActivity.nextFragment
07-05 14:29:12.852 D/OMG     ( 1260): FragmentB.onCreateView
<Tap 'Back'>
07-05 14:29:16.792 D/OMG     ( 1260): FragmentA.onCreateView

มันไม่เคยเรียก FragmentA.onSaveInstanceState และมันจะสร้าง FragmentA ใหม่เมื่อคุณถูกโจมตี อย่างไรก็ตามหากฉันใช้ FragmentA และฉันล็อกหน้าจอ FragmentA.onSaveInstanceState จะถูกเรียกใช้ แปลกมาก ... ฉันผิดที่คาดหวังว่าจะมีการเพิ่มแฟรกเมนต์ลงในสแต็กหลังเพื่อไม่ต้องการสร้างใหม่หรือไม่? นี่คือสิ่งที่เอกสารพูดว่า:

โดยที่ถ้าคุณเรียกใช้ addToBackStack () เมื่อลบแฟรกเมนต์แฟรกเมนต์จะหยุดทำงานและจะกลับมาทำงานต่อหากผู้ใช้นำทางกลับ


3
@ Jan-Henk เกี่ยวกับสิ่งที่จะต้องมีการเรียก? ListViewยกตัวอย่างเช่นเลื่อนตำแหน่งของ ดูเหมือนว่าห่วงมากเกินไปที่จะแนบผู้ฟังเลื่อนและปรับปรุงตัวแปรอินสแตนซ์
Jake Wharton

2
@ JakeWharton ฉันเห็นด้วยว่ามันควรจะง่ายกว่า แต่เท่าที่ฉันรู้ว่ามันไม่มีวิธีแก้ปัญหานี้เพราะ onCreateView ถูกเรียกเมื่อชิ้นส่วนถูกเรียกคืนจากแบ็คสแต็ค แต่ฉันอาจผิด :)
Jan-Henk

1
onCreate ไม่ได้รับการเรียก เห็นได้ชัดว่ามันกลับมาใช้อินสแตนซ์เดียวกันอีกครั้ง แต่เรียก onCreateView อีกครั้ง กะพร่องกะแพร่ง. ฉันเดาว่าฉันสามารถแคชผลลัพธ์ของ onCreateView และเพียงแค่ส่งคืนมุมมองที่มีอยู่หาก onCreateView ถูกเรียกอีกครั้ง
Eric

1
สิ่งที่ฉันกำลังมองหาเป็นเวลาหลายชั่วโมง คุณช่วยโพสต์ว่าคุณประสบความสำเร็จได้อย่างไรโดยใช้ตัวแปรตัวต่อ
Uma

1
ดังนั้นฉันเพิ่งเริ่มใช้งานตัวเองในgithub.com/frostymarvelous/Folioและพบปัญหา ฉันสามารถสร้างหน้า / แฟรกเมนต์ที่ซับซ้อนได้ประมาณ 5 รายการก่อนที่ฉันจะเริ่มต้นปัญหา OOM นั่นคือสิ่งที่ทำให้ฉันมาที่นี่ การซ่อนและแสดงผลไม่เพียงพอ จำนวนการดูหน่วยความจำหนักเกินไป
frostymarvelous

คำตอบ:


120

ถ้าคุณกลับไปที่แฟรกเมนต์จากแบ็คสแต็กมันจะไม่สร้างแฟรกเมนต์อีกครั้ง แต่จะใช้อินสแตนซ์เดียวกันอีกครั้งและเริ่มต้นด้วยonCreateView()ในวงจรชีวิตแฟรกเมนต์

ดังนั้นหากคุณต้องการเพื่อให้รัฐจัดเก็บที่คุณควรใช้ตัวแปรเช่นและไม่onSaveInstanceState()พึ่งพา


32
เอกสารเวอร์ชันปัจจุบันขัดแย้งกับการอ้างสิทธิ์นี้ แผนผังลำดับงานบอกสิ่งที่คุณระบุ แต่ข้อความในพื้นที่หลักของหน้าระบุว่า onCreateView () เรียกว่าครั้งแรกที่แสดงแฟรกเมนต์เท่านั้น: developer.android.com/guide/components/fragments.html ฉันกำลังต่อสู้กับสิ่งนี้ ออกตอนนี้และฉันไม่เห็นวิธีการใด ๆ ที่เรียกว่าเมื่อคืนชิ้นส่วนจาก backstack (Android 4.2)
Colin M.

10
พยายามบันทึกพฤติกรรมของมันแล้ว onCreateView () จะถูกเรียกเสมอเมื่อมีการแสดงแฟรกเมนต์
princepiero

4
@ColinM ทางออกสำหรับปัญหาใด ๆ
พายุหิมะ

9
มันไม่ได้ผลสำหรับฉัน ตัวแปรอินสแตนซ์ของฉันเป็นโมฆะเมื่อกลับไปที่แฟรกเมนต์! ฉันจะช่วยรัฐได้อย่างไร?
Don Rhummy

5
ดังนั้นหากเราไม่ควรส่งต่ออินสแตนซ์บันทึกวิธีควรบันทึกสถานะและข้อมูลส่วนย่อยอย่างไร
มาห์ดี

80

เมื่อเทียบกับแอปเปิ้ลUINavigationControllerและUIViewControllerGoogle ไม่สามารถทำได้ดีในสถาปัตยกรรมซอฟต์แวร์ Android และเอกสารเกี่ยวกับ Android ก็Fragmentไม่ได้ช่วยอะไรมาก

เมื่อคุณเข้าสู่ FragmentB จาก FragmentA อินสแตนซ์ FragmentA ที่มีอยู่จะไม่ถูกทำลาย เมื่อคุณกด Back in FragmentB และกลับสู่ FragmentA เราจะไม่สร้างอินสแตนซ์ FragmentA ใหม่ อินสแตนซ์ FragmentA ที่มีอยู่onCreateView()จะถูกเรียก

สิ่งสำคัญคือเราไม่ควรขยายมุมมองอีกครั้งใน FragmentA onCreateView()เพราะเราใช้อินสแตนซ์ของ FragmentA ที่มีอยู่ เราจำเป็นต้องบันทึกและนำ rootView กลับมาใช้ใหม่

รหัสต่อไปนี้ทำงานได้ดี มันไม่เพียง แต่รักษาสถานะแฟรกเมนต์เท่านั้น แต่ยังช่วยลดการโหลด RAM และ CPU (เพราะเราขยายเค้าโครงเท่านั้นหากจำเป็น) ฉันไม่อยากเชื่อรหัสตัวอย่างและเอกสารของ Google ที่ไม่เคยพูดถึงมัน แต่ขยายเลย์เอาต์เสมอ

รุ่น 1 (อย่าใช้รุ่น 1 ใช้รุ่น 2)

public class FragmentA extends Fragment {
    View _rootView;
    public View onCreateView(LayoutInflater inflater, ViewGroup container,
            Bundle savedInstanceState) {
        if (_rootView == null) {
            // Inflate the layout for this fragment
            _rootView = inflater.inflate(R.layout.fragment_a, container, false);
            // Find and setup subviews
            _listView = (ListView)_rootView.findViewById(R.id.listView);
            ...
        } else {
            // Do not inflate the layout again.
            // The returned View of onCreateView will be added into the fragment.
            // However it is not allowed to be added twice even if the parent is same.
            // So we must remove _rootView from the existing parent view group
            // (it will be added back).
            ((ViewGroup)_rootView.getParent()).removeView(_rootView);
        }
        return _rootView;
    }
}

------ อัปเดตเมื่อวันที่ 3 พฤษภาคม 2005: -------

ตามความเห็นที่กล่าวถึงบางครั้งก็ไม่_rootView.getParent()เป็นผลonCreateViewไม่มีผลซึ่งทำให้เกิดความผิดพลาด เวอร์ชัน 2 จะลบ _rootView ใน onDestroyView () ตามที่ dell116 แนะนำ ทดสอบกับ Android 4.0.3, 4.4.4, 5.1.0

เวอร์ชัน 2

public class FragmentA extends Fragment {
    View _rootView;
    public View onCreateView(LayoutInflater inflater, ViewGroup container,
            Bundle savedInstanceState) {
        if (_rootView == null) {
            // Inflate the layout for this fragment
            _rootView = inflater.inflate(R.layout.fragment_a, container, false);
            // Find and setup subviews
            _listView = (ListView)_rootView.findViewById(R.id.listView);
            ...
        } else {
            // Do not inflate the layout again.
            // The returned View of onCreateView will be added into the fragment.
            // However it is not allowed to be added twice even if the parent is same.
            // So we must remove _rootView from the existing parent view group
            // in onDestroyView() (it will be added back).
        }
        return _rootView;
    }

    @Override
    public void onDestroyView() {
        if (_rootView.getParent() != null) {
            ((ViewGroup)_rootView.getParent()).removeView(_rootView);
        }
        super.onDestroyView();
    }
}

คำเตือน!!!

นี่คือแฮ็ค! แม้ว่าฉันจะใช้มันในแอพของฉัน แต่คุณต้องทดสอบและอ่านความคิดเห็นอย่างรอบคอบ


38
การถือการอ้างอิงถึงรูทวิวของแฟรกเมนต์ทั้งหมดเป็นความคิดที่ไม่ดี IMO หากคุณกำลังเพิ่มแฟรกเมนต์หลาย ๆ อันลงในแบ็คสแต็คอย่างต่อเนื่องและพวกมันทั้งหมดกำลังถือ rootview (ซึ่งมีหน่วยความจำขนาดใหญ่มาก) ดังนั้นจึงมีความเป็นไปได้สูงที่คุณจะจบลงด้วย OutOfMemoryError เนื่องจากแฟรกเมนต์ทั้งหมด รวบรวมมัน ฉันคิดว่าวิธีที่ดีกว่าคือการขยายมุมมองตลอดเวลา (และให้ระบบ Android จัดการการสร้าง / การทำลายมุมมองของมัน) และ onActivityCreated / onViewCreated ตรวจสอบว่าข้อมูลของคุณเป็นโมฆะหรือไม่ หากใช่โหลดจากนั้นตั้งค่าข้อมูลเป็นมุมมอง
traninho

15
อย่าทำอย่างนี้! เมื่อมีการสร้างลำดับชั้นมุมมองของแฟรกเมนต์จะมีการอ้างอิงภายในกับกิจกรรมที่จัดแฟรกเมนต์ในเวลา เมื่อมีการเปลี่ยนแปลงการกำหนดค่ากิจกรรมมักจะถูกสร้างขึ้นใหม่ การใช้เลย์เอาต์เก่าทำให้กิจกรรมซอมบี้นั้นอยู่ในหน่วยความจำพร้อมกับวัตถุอื่น ๆ ที่อ้างอิง การสูญเสียความทรงจำเช่นนี้เป็นอุปสรรคต่อประสิทธิภาพและทำให้แอปของคุณเป็นตัวเลือกอันดับต้น ๆ สำหรับการยกเลิกทันทีเมื่อไม่ได้อยู่ในเบื้องหน้า
Krylez

4
@AllDayAmazing นี่เป็นจุดที่ดี พูดตามตรงฉันสับสนมากในตอนนี้ ใครสามารถอธิบายได้ว่าทำไมการเก็บการอ้างอิงถึงรูทวิวของแฟรกเมนต์นั้นไม่เป็นไร แต่ถือการอ้างอิงสำหรับเด็ก ๆ ที่มีรูทวิว (ซึ่งมีการอ้างอิงถึงรูทวิวอยู่แล้ว) ก็โอเค?
traninho

2
อยู่ห่างจากสิ่งนี้หากคุณไม่ต้องการเสียเวลา 5 ชั่วโมงในการค้นหาว่ามีอะไรกำลังดักฟังรหัสของคุณหรือไม่ ... จากนั้นเพียงเพื่อจะพบว่านี่เป็นสาเหตุ ตอนนี้ฉันต้อง refactor หลายสิ่งเพราะฉันใช้แฮ็คนี้ จะดีกว่ามากที่จะใช้ FragmentTransaction.add ถ้าคุณต้องการให้ UI ของส่วนย่อยอยู่ในชั้นเชิงเมื่อนำสิ่งอื่นเข้ามาดู (แม้จะอยู่ด้านบน) fragmentTransaction.replace () มีวัตถุประสงค์เพื่อทำลายมุมมองของชิ้นส่วน ..... อย่าต่อสู้กับระบบ
dell116

2
@VinceYuan - ฉันทดสอบกับไลบรารี v7-appcompat ล่าสุดบน Android 5.1 และเหลืออีก 6 อินสแตนซ์ของแฟรกเมนต์ที่ควรถูกลบออกใน FragmentManager ของกิจกรรมของฉัน แม้ว่า GC จะจัดการกับมันอย่างถูกต้อง (ซึ่งฉันไม่เชื่อว่ามันจะเป็นเช่นนั้น) นี่ก็เป็นสาเหตุที่ทำให้เกิดความเครียดในหน่วยความจำที่ไม่จำเป็นสำหรับแอปของคุณรวมถึงอุปกรณ์โดยทั่วไป เพียงใช้. add () กำจัดความต้องการรหัสแฮ็กนี้ทั้งหมด การทำสิ่งนี้ขัดกับสิ่งที่ใช้ FragmentTransaction.replace () ที่ตั้งใจทำในตอนแรก
dell116

53

ฉันคิดว่ามีวิธีอื่นในการบรรลุสิ่งที่คุณกำลังมองหา ฉันไม่ได้พูดว่ามันเป็นโซลูชั่นที่สมบูรณ์ แต่มันมีจุดประสงค์ในกรณีของฉัน

สิ่งที่ฉันทำคือแทนที่จะแทนที่ชิ้นส่วนที่ฉันเพิ่งเพิ่มชิ้นส่วนเป้าหมาย ดังนั้นโดยทั่วไปคุณจะได้รับจะใช้วิธีการแทนadd()replace()

ฉันทำอะไรอีก ฉันซ่อนส่วนปัจจุบันของฉันและเพิ่มลงใน backstack

ดังนั้นมันจึงทับซ้อนชิ้นส่วนใหม่กับชิ้นส่วนปัจจุบันโดยไม่ทำลายมุมมองของมัน (ตรวจสอบว่าonDestroyView()ไม่มีการเรียกใช้วิธีการของมันรวมทั้งการเพิ่มเข้าไปเพื่อbackstateให้ข้อได้เปรียบของฉันในการเริ่มต้นชิ้นส่วนต่อไป

นี่คือรหัส:

Fragment fragment=new DestinationFragment();
FragmentManager fragmentManager = getFragmentManager();
android.app.FragmentTransaction ft=fragmentManager.beginTransaction();
ft.add(R.id.content_frame, fragment);
ft.hide(SourceFragment.this);
ft.addToBackStack(SourceFragment.class.getName());
ft.commit();

ระบบ AFAIK จะเรียกใช้เฉพาะonCreateView()เมื่อมุมมองถูกทำลายหรือไม่ได้ถูกสร้างขึ้น แต่ที่นี่เราได้บันทึกมุมมองโดยไม่ลบออกจากหน่วยความจำ ดังนั้นมันจะไม่สร้างมุมมองใหม่

และเมื่อคุณกลับมาจาก Destination Fragment มันจะป๊อปอันสุดท้าย FragmentTransactionถอดแฟรกเมนต์บนสุดออกซึ่งจะทำให้มุมมอง (SourceFragment's) สูงสุดที่ปรากฏขึ้นบนหน้าจอ

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

ดังนั้นมันจึงไม่ใช่วิธีการรักษาสถานะของคุณ แต่สำหรับมุมมองของคุณ


ในกรณีของฉันโดยการเพิ่มแฟรกเมนต์แทนการแทนที่สาเหตุของปัญหาเมื่อใช้โพลหรือการร้องขอเว็บชนิดใด ๆ ในแฟรกเมนต์ ฉันต้องการหยุดการสำรวจนี้ใน Fragment A เมื่อเพิ่ม Fragment B มีความคิดเกี่ยวกับเรื่องนี้ไหม?
Uma

คุณใช้โพลใน FirstFragment อย่างไร คุณต้องทำด้วยตัวเองเนื่องจากชิ้นส่วนทั้งสองยังคงอยู่ในหน่วยความจำดังนั้นคุณสามารถใช้อินสแตนซ์ของพวกเขาเพื่อดำเนินการที่จำเป็นเบาะแสสร้างกิจกรรมในกิจกรรมหลักซึ่งทำบางสิ่งเมื่อคุณเพิ่มส่วนที่สอง หวังว่าจะช่วยได้
kaushal trivedi

1
ขอบคุณสำหรับคำใบ้ =) ฉันทำสิ่งนี้เสร็จแล้ว แต่นี่เป็นวิธีเดียวที่จะทำ? และเป็นวิธีที่เหมาะสมหรือไม่ เมื่อฉันกดปุ่มโฮมและเปิดแอปพลิเคชันอีกครั้งชิ้นส่วนทั้งหมดก็จะกลับมาทำงานอีกครั้ง สมมติว่าฉันอยู่ที่นี่ใน Fragment B ผ่านทางนี้ Activity A{Fragment A --> Fragment B}เมื่อฉันเรียกใช้แอปพลิเคชันอีกครั้งหลังจากกดปุ่มโฮมทั้งสองส่วนonResume()ถูกเรียกและด้วยเหตุนี้พวกเขาจึงเริ่มการลงคะแนนเลือกตั้ง ฉันจะควบคุมสิ่งนี้ได้อย่างไร
Uma

1
น่าเสียดายที่คุณไม่สามารถทำได้ระบบไม่ทำงานในลักษณะปกติในลักษณะนี้มันจะพิจารณาทั้งสองส่วนเป็นลูกโดยตรงของกิจกรรมดังนั้นจึงมีจุดประสงค์ในการรักษาสถานะของชิ้นส่วนสิ่งอื่น ๆ ได้รับการจัดการยากมากเมื่อเร็ว ๆ นี้ ค้นพบปัญหาทั้งหมดตอนนี้ข้อเสนอแนะของฉันคือจะไม่ไปหาทางนี้
kaushal trivedi

1
แน่นอนในที่สุดฉันจะบอกว่าไม่ไปกับวิธีการนี้จนกว่าคุณจะพบทางออกอื่น ๆ เพราะมันยากที่จะจัดการ
kaushal trivedi

7

ฉันขอแนะนำวิธีแก้ปัญหาที่ง่ายมาก

ใช้ตัวแปรมุมมองการอ้างอิงและตั้งค่ามุมมองใน OnCreateView ตรวจสอบว่ามีมุมมองอยู่แล้วในตัวแปรนี้แล้วส่งคืนมุมมองเดียวกัน

   private View fragmentView;

   public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
        super.onCreateView(inflater, container, savedInstanceState);

        if (fragmentView != null) {
            return fragmentView;
        }
        View view = inflater.inflate(R.layout.yourfragment, container, false);
        fragmentView = view;
        return view;
    }

1
มีโอกาสที่หน่วยความจำรั่วถ้าเราไม่ได้ลบตัวแปร 'fragmentView' ใน onDestroy ()
Arun PM

@Arrpm ดังนั้นวิธีการที่จะลบ fragmentView ใน onDestroy ()? if (_rootView.getParent() != null) { ((ViewGroup)_rootView.getParent()).removeView(_rootView); }มีความเหมาะสมที่จะล้างหน่วยความจำ?
เมห์เม็ตเกอร์

1
@ MehmetGürฉันใช้โซลูชันนี้หลายครั้ง จนถึงตอนนี้ฉันไม่ได้รับข้อผิดพลาดหน่วยความจำรั่ว แต่คุณสามารถใช้โซลูชัน ArunPM กับสิ่งนั้นได้หากคุณต้องการ ฉันคิดว่าเขาบอกให้ตั้งค่า FragmentView เป็น null ใน OnDestroy () วิธีการ
Mandeep Singh

1
ฉันใช้LeakCanaryเพื่อตรวจหาการรั่วไหลของหน่วยความจำและปัญหาการรั่วไหลของการโยนเมื่อฉันทำตามวิธีนี้ แต่ตามที่ @Meepeep Sigh พูดถึงในความคิดเห็นเราสามารถเอาชนะปัญหานี้ได้โดยการกำหนดให้nullกับfragmentView ตัวแปรในonDestroy()วิธีการ
อรุณน

1
onDestroyView()ตามความรู้ของฉันเมื่อมีส่วนได้รับการทำลายมุมมองที่เกี่ยวข้องกับชิ้นส่วนจะถูกล้างออกใน การล้างนี้ไม่ได้เกิดขึ้นสำหรับตัวแปรมุมมองการสำรองข้อมูลของเรา (ที่นี่fragmentView ) และจะทำให้หน่วยความจำรั่วเมื่อชิ้นส่วนกลับซ้อนกัน / ถูกทำลาย คุณสามารถค้นหาข้อมูลอ้างอิงเดียวกันได้ใน [สาเหตุทั่วไปของการรั่วไหลของหน่วยความจำ] ( square.github.io/leakcanary/fundamentals/… ) ในบทนำของ LeakCanery
อรุณฯ

6

ฉันเจอปัญหานี้ใน Fragment ที่มีแผนที่ซึ่งมีรายละเอียดการตั้งค่ามากเกินไปที่จะบันทึก / โหลดซ้ำ โซลูชันของฉันคือทำให้ชิ้นส่วนนี้ใช้งานได้ตลอดเวลา (คล้ายกับที่ @kaushal พูดถึง)

สมมติว่าคุณมี Fragment A อยู่ในปัจจุบันและต้องการแสดง Fragment B โดยสรุปผลที่ตามมา:

  • แทนที่ () - ลบ Fragment A และแทนที่ด้วย Fragment B Fragment A จะถูกสร้างใหม่เมื่อนำมาที่ด้านหน้าอีกครั้ง
  • เพิ่ม () - (สร้างและ) เพิ่ม Fragment B และมันทับซ้อน Fragment A ซึ่งยังคงทำงานอยู่ในพื้นหลัง
  • ลบ () - สามารถใช้เพื่อลบ Fragment B และกลับไปที่ A. Fragment B จะถูกสร้างขึ้นใหม่เมื่อถูกเรียกในภายหลัง

ดังนั้นหากคุณต้องการให้ "บันทึก" ทั้งสองแฟรกเมนต์ไว้ให้สลับโดยใช้ hide () / show ()

ข้อดี : วิธีการที่ง่ายและไม่ซับซ้อนในการทำให้แฟรกเมนต์หลายชิ้นทำงานอยู่
ข้อเสีย : คุณใช้หน่วยความจำมากขึ้นเพื่อให้พวกเขาทำงานต่อไป อาจพบปัญหาเช่นการแสดงบิตแมปขนาดใหญ่จำนวนมาก


คุณช่วยบอกฉันหน่อยได้ไหมว่าเมื่อเราลบแฟรกเมนต์ b และกลับไปที่ A ดังนั้นวิธีการใดที่เรียกว่าในแฟรกเมนต์ A ฉันต้องการดำเนินการบางอย่างเมื่อเราลบส่วน B
Google

5

onSaveInstanceState() ถูกเรียกใช้เฉพาะเมื่อมีการเปลี่ยนแปลงการกำหนดค่า

ตั้งแต่เปลี่ยนจากแฟรกเมนต์หนึ่งเป็นอีกส่วนหนึ่งจะไม่มีการเปลี่ยนแปลงการกำหนดค่าดังนั้นจึงไม่มีการเรียก onSaveInstanceState()ใช้ สถานะใดไม่ถูกบันทึก คุณสามารถระบุได้หรือไม่

หากคุณป้อนข้อความใน EditText ข้อความนั้นจะถูกบันทึกโดยอัตโนมัติ รายการ UI ใด ๆ ที่ไม่มี ID คือรายการที่มีสถานะมุมมองที่จะไม่ถูกบันทึก


onSaveInstanceState()จะถูกเรียกเช่นกันเมื่อระบบทำลายกิจกรรมเนื่องจากไม่มีทรัพยากร
Marcel Bro

0

ที่นี่เนื่องจากonSaveInstanceStateในแฟรกเมนต์ไม่ได้โทรเมื่อคุณเพิ่มแฟรกเมนต์ลงใน backstack วงจรชีวิตส่วนใน backstack เมื่อคืนค่าเริ่มต้นonCreateViewและจุดสิ้นสุดonDestroyViewในขณะที่onSaveInstanceStateเรียกว่าระหว่างและonDestroyView onDestroyโซลูชันของฉันคือสร้างตัวแปรอินสแตนซ์และเริ่มonCreateต้น รหัสตัวอย่าง:

private boolean isDataLoading = true;
private ArrayList<String> listData;
public void onCreate(Bundle savedInstanceState){
     super.onCreate(savedInstanceState);
     isDataLoading = false;
     // init list at once when create fragment
     listData = new ArrayList();
}

และตรวจสอบในonActivityCreated:

public void onViewCreated(View view, @Nullable Bundle savedInstanceState) {
    super.onViewCreated(view, savedInstanceState);
    if(isDataLoading){
         fetchData();
    }else{
         //get saved instance variable listData()
    }
}

private void fetchData(){
     // do fetch data into listData
}

0
getSupportFragmentManager().addOnBackStackChangedListener(new FragmentManager.OnBackStackChangedListener()
    {
        @Override
        public void onBackStackChanged()
        {
            if (getSupportFragmentManager().getBackStackEntryCount() == 0)
            {
                //setToolbarTitle("Main Activity");
            }
            else
            {
                Log.e("fragment_replace11111", "replace");
            }
        }
    });


YourActivity.java
@Override
public void onBackPressed()
{
 Fragment fragment = getSupportFragmentManager().findFragmentById(R.id.Fragment_content);
  if (fragment instanceof YourFragmentName)
    {
        fragmentReplace(new HomeFragment(),"Home Fragment");
        txt_toolbar_title.setText("Your Fragment");
    }
  else{
     super.onBackPressed();
   }
 }


public void fragmentReplace(Fragment fragment, String fragment_name)
{
    try
    {
        fragmentTransaction = fragmentManager.beginTransaction();
        fragmentTransaction.replace(R.id.Fragment_content, fragment, fragment_name);
        fragmentTransaction.setCustomAnimations(R.anim.enter_from_right, R.anim.exit_to_left, R.anim.enter_from_left, R.anim.exit_to_right);
        fragmentTransaction.addToBackStack(fragment_name);
        fragmentTransaction.commitAllowingStateLoss();
    }
    catch (Exception e)
    {
        e.printStackTrace();
    }
}

0

ปัญหาของฉันคล้ายกัน แต่ฉันเอาชนะฉันได้โดยไม่ทำให้ชิ้นส่วนยังมีชีวิตอยู่ สมมติว่าคุณมีกิจกรรมที่มี 2 แฟรกเมนต์ - F1 และ F2 F1 เริ่มต้นในตอนแรกและให้บอกว่ามีข้อมูลผู้ใช้บางส่วนแล้วตามเงื่อนไข F2 ปรากฏขึ้นเพื่อขอให้ผู้ใช้กรอกแอตทริบิวต์เพิ่มเติม - หมายเลขโทรศัพท์ของพวกเขา ถัดไปคุณต้องการให้หมายเลขโทรศัพท์นั้นกลับมาที่ F1 และลงทะเบียนให้เสร็จสมบูรณ์ แต่คุณทราบว่าข้อมูลผู้ใช้ก่อนหน้านี้ทั้งหมดสูญหายและคุณไม่มีข้อมูลก่อนหน้า ส่วนถูกสร้างจากรอยขีดข่วนและแม้ว่าคุณจะบันทึกข้อมูลนี้ในonSaveInstanceStateกำมา null onActivityCreatedกลับมาอยู่ใน

วิธีแก้ไข: บันทึกข้อมูลที่ต้องการเป็นตัวแปรอินสแตนซ์ในกิจกรรมการโทร จากนั้นส่งตัวแปรอินสแตนซ์นั้นไปยังส่วนของคุณ

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

    Bundle args = getArguments();

    // this will be null the first time F1 is created. 
    // it will be populated once you replace fragment and provide bundle data
    if (args != null) {
        if (args.get("your_info") != null) {
            // do what you want with restored information
        }
    }
}

ดังนั้นตามด้วยตัวอย่างของฉัน: ก่อนที่ฉันจะแสดง F2 ฉันบันทึกข้อมูลผู้ใช้ในตัวแปรอินสแตนซ์โดยใช้การโทรกลับ จากนั้นฉันก็เริ่มต้น F2 ผู้ใช้กรอกหมายเลขโทรศัพท์และกดบันทึก ฉันใช้การติดต่อกลับในกิจกรรมรวบรวมข้อมูลนี้และแทนที่แฟรกเมนต์ F1 ของฉันในครั้งนี้มีข้อมูลบันเดิลที่ฉันสามารถใช้ได้

@Override
public void onPhoneAdded(String phone) {
        //replace fragment
        F1 f1 = new F1 ();
        Bundle args = new Bundle();
        yourInfo.setPhone(phone);
        args.putSerializable("you_info", yourInfo);
        f1.setArguments(args);

        getFragmentManager().beginTransaction()
                .replace(R.id.fragmentContainer, f1).addToBackStack(null).commit();

    }
}

ข้อมูลเพิ่มเติมเกี่ยวกับการเรียกกลับสามารถดูได้ที่นี่: https://developer.android.com/training/basics/fragments/communicating.html


0

แรก : เพียงแค่ใช้วิธีการเพิ่มแทนวิธีการแทนที่ของ FragmentTransaction ระดับแล้วคุณจะต้องเพิ่ม secondFragment เพื่อกองโดยวิธี addToBackStack

ที่สอง : เมื่อคลิกกลับคุณต้องเรียก popBackStackImmediate ()

Fragment sourceFragment = new SourceFragment ();
final Fragment secondFragment = new SecondFragment();
final FragmentTransaction ft = getChildFragmentManager().beginTransaction();
ft.add(R.id.child_fragment_container, secondFragment );
ft.hide(sourceFragment );
ft.addToBackStack(NewsShow.class.getName());
ft.commit();
                                
((SecondFragment)secondFragment).backFragmentInstanceClick = new SecondFragment.backFragmentNewsResult()
{
        @Override
        public void backFragmentNewsResult()
        {                                    
            getChildFragmentManager().popBackStackImmediate();                                
        }
};

0

แทนที่ Fragment โดยใช้รหัสต่อไปนี้:

Fragment fragment = new AddPaymentFragment();
getSupportFragmentManager().beginTransaction().replace(R.id.frame, fragment, "Tag_AddPayment")
                .addToBackStack("Tag_AddPayment")
                .commit();

กิจกรรมของ onBackPressed () คือ:

  @Override
public void onBackPressed() {
    android.support.v4.app.FragmentManager fm = getSupportFragmentManager();
    if (fm.getBackStackEntryCount() > 1) {

        fm.popBackStack();
    } else {


        finish();

    }
    Log.e("popping BACKSTRACK===> ",""+fm.getBackStackEntryCount());

}

0
Public void replaceFragment(Fragment mFragment, int id, String tag, boolean addToStack) {
        FragmentTransaction mTransaction = getSupportFragmentManager().beginTransaction();
        mTransaction.replace(id, mFragment);
        hideKeyboard();
        if (addToStack) {
            mTransaction.addToBackStack(tag);
        }
        mTransaction.commitAllowingStateLoss();
    }
replaceFragment(new Splash_Fragment(), R.id.container, null, false);

1
ขอขอบคุณสำหรับข้อมูลโค้ดนี้ซึ่งอาจให้ความช่วยเหลือแบบ จำกัด และทันที คำอธิบายที่เหมาะสมมากจะเพิ่มมูลค่าในระยะยาวด้วยการแสดงเหตุผลนี้เป็นทางออกที่ดีในการแก้ไขปัญหาและจะทำให้มันมีประโยชน์มากขึ้นให้กับผู้อ่านในอนาคตกับอื่น ๆ คำถามที่คล้ายกัน โปรดแก้ไขคำตอบของคุณเพื่อเพิ่มคำอธิบายรวมถึงข้อสมมติฐานที่คุณทำ
Machavity

0

โซลูชั่นที่สมบูรณ์แบบที่พบชิ้นส่วนเก่าในสแต็กและโหลดถ้ามีอยู่ในสแต็ก

/**
     * replace or add fragment to the container
     *
     * @param fragment pass android.support.v4.app.Fragment
     * @param bundle pass your extra bundle if any
     * @param popBackStack if true it will clear back stack
     * @param findInStack if true it will load old fragment if found
     */
    public void replaceFragment(Fragment fragment, @Nullable Bundle bundle, boolean popBackStack, boolean findInStack) {
        FragmentManager fm = getSupportFragmentManager();
        FragmentTransaction ft = fm.beginTransaction();
        String tag = fragment.getClass().getName();
        Fragment parentFragment;
        if (findInStack && fm.findFragmentByTag(tag) != null) {
            parentFragment = fm.findFragmentByTag(tag);
        } else {
            parentFragment = fragment;
        }
        // if user passes the @bundle in not null, then can be added to the fragment
        if (bundle != null)
            parentFragment.setArguments(bundle);
        else parentFragment.setArguments(null);
        // this is for the very first fragment not to be added into the back stack.
        if (popBackStack) {
            fm.popBackStack(null, FragmentManager.POP_BACK_STACK_INCLUSIVE);
        } else {
            ft.addToBackStack(parentFragment.getClass().getName() + "");
        }
        ft.replace(R.id.contenedor_principal, parentFragment, tag);
        ft.commit();
        fm.executePendingTransactions();
    }

ใช้มันเหมือน

Fragment f = new YourFragment();
replaceFragment(f, null, boolean true, true); 
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.