ความแตกต่างระหว่าง initLoader และ restartLoader ใน LoaderManager


129

ฉันหลงทางอย่างสิ้นเชิงเกี่ยวกับความแตกต่างระหว่างinitLoaderและrestartLoaderหน้าที่ของLoaderManager:

  • พวกเขาทั้งสองมีลายเซ็นเดียวกัน
  • restartLoader ยังสร้างตัวโหลดหากไม่มีอยู่ ("เริ่มต้นใหม่หรือรีสตาร์ทตัวโหลดที่มีอยู่ในตัวจัดการนี้")

มีความสัมพันธ์ระหว่างสองวิธีนี้หรือไม่? โทรrestartLoaderตลอดinitLoaderไหม? โทรมาrestartLoaderไม่ต้องโทรได้initLoaderไหม ปลอดภัยไหมที่จะโทรinitLoaderสองครั้งเพื่อรีเฟรชข้อมูล ฉันควรใช้หนึ่งในสองเมื่อใดและเพราะเหตุใด

คำตอบ:


202

ในการตอบคำถามนี้คุณต้องเจาะลึกLoaderManagerรหัส ในขณะที่เอกสารสำหรับ LoaderManager เองยังไม่ชัดเจนเพียงพอ (หรือไม่มีคำถามนี้) เอกสารสำหรับ LoaderManagerImpl ซึ่งเป็นคลาสย่อยของ LoaderManager แบบนามธรรมนั้นให้ความกระจ่างมากขึ้น

initLoader

โทรเพื่อเริ่มต้น ID เฉพาะด้วย Loader หาก ID นี้มี Loader เชื่อมโยงอยู่แล้ว ID นี้จะไม่เปลี่ยนแปลงและการเรียกกลับก่อนหน้านี้จะถูกแทนที่ด้วย ID ที่ให้มาใหม่ หากขณะนี้ไม่มี Loader สำหรับ ID จะมีการสร้างและเริ่มต้นใหม่

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

restartLoader

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

[... ] หลังจากเรียกใช้ฟังก์ชันนี้ Loaders ก่อนหน้าใด ๆ ที่เชื่อมโยงกับ ID นี้จะถือว่าไม่ถูกต้องและคุณจะไม่ได้รับการอัปเดตข้อมูลเพิ่มเติมจากพวกเขา

โดยทั่วไปมีสองกรณี:

  1. ไม่มีตัวโหลดที่มี id: ทั้งสองวิธีจะสร้างตัวโหลดใหม่ดังนั้นจึงไม่มีความแตกต่างกัน
  2. ตัวโหลดที่มี id มีอยู่แล้ว: initLoaderจะแทนที่เฉพาะการเรียกกลับที่ส่งผ่านเป็นพารามิเตอร์ แต่จะไม่ยกเลิกหรือหยุดตัวโหลด ในกรณีนี้CursorLoaderหมายความว่าเคอร์เซอร์ยังคงเปิดอยู่และทำงานอยู่ (หากเป็นเช่นนั้นก่อนการinitLoaderโทร) ในทางกลับกัน startLoader จะยกเลิกหยุดและทำลายตัวโหลด (และปิดแหล่งข้อมูลพื้นฐานเช่นเคอร์เซอร์) และสร้างตัวโหลดใหม่ (ซึ่งจะสร้างเคอร์เซอร์ใหม่และเรียกใช้แบบสอบถามอีกครั้งหากตัวโหลดเป็น CursorLoader)

นี่คือรหัสแบบง่ายสำหรับทั้งสองวิธี:

initLoader

LoaderInfo info = mLoaders.get(id);
if (info == null) {
    // Loader doesn't already exist -> create new one
    info = createAndInstallLoader(id, args, LoaderManager.LoaderCallbacks<Object>)callback);
} else {
   // Loader exists -> only replace callbacks   
   info.mCallbacks = (LoaderManager.LoaderCallbacks<Object>)callback;
}

restartLoader

LoaderInfo info = mLoaders.get(id);
if (info != null) {
    LoaderInfo inactive = mInactiveLoaders.get(id);
    if (inactive != null) {
        // does a lot of stuff to deal with already inactive loaders
    } else {
        // Keep track of the previous instance of this loader so we can destroy
        // it when the new one completes.
        info.mLoader.abandon();
        mInactiveLoaders.put(id, info);
    }
}
info = createAndInstallLoader(id, args,  (LoaderManager.LoaderCallbacks<Object>)callback);

ดังที่เราเห็นในกรณีที่ไม่มีตัวโหลด (ข้อมูล == null) ทั้งสองวิธีจะสร้างตัวโหลดใหม่ (info = createAndInstallLoader (... )) ในกรณีที่ตัวโหลดมีอยู่แล้วinitLoaderจะแทนที่การเรียกกลับเท่านั้น (info.mCallbacks = ... ) ในขณะที่restartLoaderปิดการใช้งานตัวโหลดเก่า (จะถูกทำลายเมื่อตัวโหลดใหม่ทำงานเสร็จสิ้น) จากนั้นสร้างใหม่

ดังนั้นจึงมีความชัดเจนว่าจะใช้initLoaderเมื่อใดและเมื่อใดควรใช้restartLoaderและเหตุใดจึงเหมาะสมที่จะมีสองวิธีนี้ initLoaderใช้เพื่อให้แน่ใจว่ามีตัวโหลดเริ่มต้น หากไม่มีอยู่จะมีการสร้างใหม่หากมีอยู่แล้วจะถูกนำมาใช้ใหม่ เราใช้วิธีนี้เสมอไม่ว่าเราจะต้องมีตัวโหลดใหม่เนื่องจากแบบสอบถามที่จะเรียกใช้มีการเปลี่ยนแปลง (ไม่ใช่ข้อมูลพื้นฐาน แต่เป็นแบบสอบถามจริงเช่นในคำสั่ง SQL สำหรับ CursorLoader) ซึ่งในกรณีนี้เราจะเรียกว่าrestartLoaderซึ่งในกรณีนี้เราจะเรียก

กิจกรรม / วงจรชีวิตส่วนมีอะไรจะทำอย่างไรกับการตัดสินใจที่จะใช้อย่างใดอย่างหนึ่งหรือวิธีการอื่น ๆ (และมีความจำเป็นที่จะติดตามการเรียกใช้ธงหนึ่งยิงขณะที่ไซมอนแนะนำ)! การตัดสินใจนี้ขึ้นอยู่กับ "ความต้องการ" สำหรับรถตักใหม่เท่านั้น ถ้าเราต้องการที่จะเรียกใช้แบบสอบถามเดียวกันกับที่เราใช้ถ้าเราต้องการที่จะเรียกใช้แบบสอบถามที่เราแตกต่างกันinitLoaderrestartLoader

เราสามารถใช้ได้เสมอrestartLoaderแต่จะไม่มีประสิทธิภาพ หลังจากการหมุนหน้าจอหรือหากผู้ใช้ออกจากแอปและกลับไปที่กิจกรรมเดียวกันในภายหลังเรามักจะต้องการแสดงผลการสืบค้นเดียวกันดังนั้นจึงrestartLoaderจะสร้างตัวโหลดขึ้นมาใหม่โดยไม่จำเป็นและปิดผลการสืบค้นที่อ้างอิง (อาจมีราคาแพง)

สิ่งสำคัญมากที่จะต้องเข้าใจความแตกต่างระหว่างข้อมูลที่โหลดและ "แบบสอบถาม" เพื่อโหลดข้อมูลนั้น สมมติว่าเราใช้ CursorLoader ในการสอบถามตารางสำหรับคำสั่งซื้อ หากมีการเพิ่มคำสั่งใหม่ลงในตารางนั้น CursorLoader จะใช้ onContentChanged () เพื่อแจ้งให้ UI อัปเดตและแสดงคำสั่งใหม่ (ไม่จำเป็นต้องใช้restartLoaderในกรณีนี้) หากเราต้องการแสดงเฉพาะคำสั่งซื้อที่เปิดอยู่เราจำเป็นต้องมีการสืบค้นใหม่และเราจะใช้restartLoaderเพื่อส่งคืน CursorLoader ใหม่ที่สะท้อนการสืบค้นใหม่


มีความสัมพันธ์ระหว่างสองวิธีนี้หรือไม่?

พวกเขาแชร์รหัสเพื่อสร้าง Loader ใหม่ แต่จะทำสิ่งที่แตกต่างออกไปเมื่อมีตัวโหลดอยู่แล้ว

โทรrestartLoaderตลอดinitLoaderไหม?

ไม่มันไม่เคยทำ

โทรมาrestartLoaderไม่ต้องโทรได้initLoaderไหม

ใช่.

ปลอดภัยไหมที่จะโทรinitLoaderสองครั้งเพื่อรีเฟรชข้อมูล

ปลอดภัยที่จะโทรinitLoaderสองครั้ง แต่จะไม่มีการรีเฟรชข้อมูล

ฉันควรใช้หนึ่งในสองเมื่อใดและเพราะเหตุใด


(หวังว่า) จะชัดเจนหลังจากคำอธิบายของฉันข้างต้น

การเปลี่ยนแปลงการกำหนดค่า

LoaderManager ยังคงรักษาสถานะของการเปลี่ยนแปลงการกำหนดค่า (รวมถึงการเปลี่ยนแปลงการวางแนว) ดังนั้นคุณจะคิดว่าไม่มีอะไรเหลือให้เราทำ คิดใหม่อีกครั้ง...

ประการแรก LoaderManager จะไม่เก็บการโทรกลับไว้ดังนั้นหากคุณไม่ทำอะไรเลยคุณจะไม่ได้รับสายไปยังวิธีการโทรกลับของคุณเช่นonLoadFinished()และสิ่งที่คล้ายกันและอาจทำให้แอปของคุณเสียหายได้

ดังนั้นเราต้องโทรอย่างน้อยinitLoaderเพื่อเรียกคืนวิธีการโทรกลับ ( restartLoaderแน่นอนว่าเป็นไปได้เช่นกัน) เอกสารฯ :

หาก ณ จุดที่เรียกผู้โทรอยู่ในสถานะเริ่มต้นและตัวโหลดที่ร้องขอมีอยู่แล้วและได้สร้างข้อมูลแล้วการโทรกลับonLoadFinished(Loader, D)จะถูกเรียกทันที (ภายในฟังก์ชันนี้) [... ]

นั่นหมายความว่าถ้าเราโทรไปinitLoaderหลังจากเปลี่ยนการวางแนวเราจะได้รับonLoadFinishedสายทันทีเพราะข้อมูลถูกโหลดแล้ว (สมมติว่าเป็นกรณีก่อนการเปลี่ยนแปลง) ในขณะที่ฟังดูตรงไปตรงมาอาจเป็นเรื่องยุ่งยาก (เราทุกคนไม่ชอบ Android ... )

เราต้องแยกแยะระหว่างสองกรณี:

  1. จัดการการกำหนดค่าเปลี่ยนแปลงตัวเอง: กรณีนี้สำหรับ Fragments ที่ใช้ setRetainInstance (true) หรือสำหรับกิจกรรมที่มีandroid:configChangesแท็กตามในรายการ ส่วนประกอบเหล่านี้จะไม่ได้รับการโทรแบบ onCreate หลังจากเช่นการหมุนหน้าจอดังนั้นอย่าลืมโทร initLoader/restartLoaderด้วยวิธีการโทรกลับแบบอื่น (เช่นใน onActivityCreated(Bundle)) เพื่อให้สามารถเริ่มการทำงานของ Loader ได้จำเป็นต้องจัดเก็บรหัสตัวโหลด (เช่นในรายการ) เนื่องจากส่วนประกอบถูกเก็บไว้ในการเปลี่ยนแปลงการกำหนดค่าเราจึงสามารถวนซ้ำรหัสตัวโหลดที่มีอยู่และเรียกinitLoader(loaderid, ...)ใช้
  2. ไม่จัดการการเปลี่ยนแปลงการกำหนดค่าเอง: ในกรณีนี้ Loaders สามารถเริ่มต้นใน onCreate ได้ แต่เราจำเป็นต้องเก็บรหัสตัวโหลดด้วยตนเองมิฉะนั้นเราจะไม่สามารถทำการเรียก หากรหัสถูกเก็บไว้ใน ArrayList เราจะทำ
    outState.putIntegerArrayList(loaderIdsKey, loaderIdsArray)ใน onSaveInstanceState และกู้คืนรหัสใน onCreate: loaderIdsArray = savedInstanceState.getIntegerArrayList(loaderIdsKey)ก่อนที่เราจะทำการเรียก initLoader

: +1: จุดสุดท้าย หากคุณใช้initLoader(และการโทรกลับทั้งหมดเสร็จสิ้น Loader ไม่ได้ใช้งาน) หลังจากการหมุนคุณจะไม่ได้รับการonLoadFinishedติดต่อกลับ แต่ถ้าคุณใช้restartLoaderคุณจะ?
Blundell

ไม่ถูกต้อง เมธอด initLoader เรียกใช้เมธอด onLoadFinished () ก่อนที่จะส่งคืน (หากตัวโหลดเริ่มทำงานและมีข้อมูล) ฉันได้เพิ่มย่อหน้าเกี่ยวกับการเปลี่ยนแปลงการกำหนดค่าเพื่ออธิบายรายละเอียดเพิ่มเติม
Emanuel Moecklin

6
แน่นอนว่าการรวมกันของคำตอบของคุณและ @ alexlockwood ให้ภาพที่สมบูรณ์ ฉันเดาว่าคำตอบสำหรับคนอื่นคือใช้ initLoader หากการสืบค้นของคุณเป็นแบบคงที่และ restartLoader ถ้าคุณต้องการเปลี่ยนแบบสอบถาม
Blundell

1
ที่เรียกมันอย่างดี: "ใช้ initLoader ถ้า Query ของคุณเป็นแบบคงที่และ restartLoader ถ้าคุณต้องการเปลี่ยนแบบสอบถาม"
Emanuel Moecklin

1
@Mhd Tahawi คุณไม่ได้เปลี่ยนการโทรกลับคุณจะกำหนดให้พวกเขาไปทุกที่ที่ควรไปเท่านั้น หลังจากหมุนหน้าจอแล้วต้องตั้งค่าใหม่เนื่องจาก Android จะไม่เก็บไว้รอบ ๆ เพื่อป้องกันการรั่วไหลของหน่วยความจำ คุณมีอิสระที่จะตั้งค่าเป็นสิ่งที่คุณต้องการตราบเท่าที่พวกเขาทำในสิ่งที่ถูกต้อง
Emanuel Moecklin

46

การโทรinitLoaderเมื่อมีการสร้าง Loader แล้ว (โดยทั่วไปจะเกิดขึ้นหลังจากการเปลี่ยนแปลงการกำหนดค่าเป็นต้น) บอกให้ LoaderManager ส่งข้อมูลล่าสุดของ Loader ไปให้onLoadFinishedทันที หากยังไม่ได้สร้าง Loader (เมื่อ activity / fragment เปิดตัวครั้งแรกเป็นต้น) การเรียกเพื่อinitLoaderบอก LoaderManager ให้เรียกonCreateLoaderเพื่อสร้าง Loader ใหม่

การโทรrestartLoaderจะทำลาย Loader ที่มีอยู่แล้ว (เช่นเดียวกับข้อมูลที่มีอยู่ที่เกี่ยวข้อง) และบอกให้ LoaderManager เรียกonCreateLoaderเพื่อสร้าง Loader ใหม่และเริ่มการโหลดใหม่


เอกสารประกอบก็ค่อนข้างชัดเจนเกี่ยวกับเรื่องนี้เช่นกัน:

  • initLoaderตรวจสอบให้แน่ใจว่า Loader เริ่มต้นและใช้งานได้ หากไม่มีตัวโหลดจะถูกสร้างขึ้นและ (หากกิจกรรม / แฟรกเมนต์เริ่มทำงานในขณะนี้) เริ่มตัวโหลด มิฉะนั้นตัวโหลดที่สร้างขึ้นล่าสุดจะถูกใช้ซ้ำ

  • restartLoaderเริ่มต้นใหม่หรือรีสตาร์ท Loader ที่มีอยู่ในตัวจัดการนี้ลงทะเบียนการเรียกกลับไปที่มันและ (หากกิจกรรม / ส่วนย่อยเริ่มต้นในขณะนี้) เริ่มโหลด หากตัวโหลดที่มี ID เดียวกันถูกสตาร์ทไว้ก่อนหน้านี้ระบบจะทำลายโดยอัตโนมัติเมื่อตัวโหลดใหม่ทำงานเสร็จสิ้น การเรียกกลับจะถูกส่งก่อนที่ตัวโหลดเก่าจะถูกทำลาย


@TomanMoney ฉันอธิบายความหมายในคำตอบของฉัน คุณสับสนเกี่ยวกับส่วนใด
Alex Lockwood

คุณเพิ่งแก้ไขเอกสารใหม่ แต่เอกสารไม่ได้ระบุว่าแต่ละวิธีควรใช้ที่ไหนและทำไมจึงไม่ดีที่จะทำให้มันยุ่งเหยิง จากประสบการณ์ของฉันเพียงแค่เรียก restartLoader และไม่เคยเรียก initLoader ก็ใช้ได้ดี ดังนั้นตอนนี้ยังสับสน
Tom anMoney

3
@TomanMoney โดยปกติคุณจะใช้initLoader()ในonCreate()/ onActivityCreated()เมื่อกิจกรรม / ส่วนแรกเริ่มต้นขึ้น ด้วยวิธีนี้เมื่อผู้ใช้เปิดกิจกรรมเป็นครั้งแรกตัวโหลดจะถูกสร้างขึ้นเป็นครั้งแรก ... แต่ในการเปลี่ยนแปลงการกำหนดค่าที่ตามมาซึ่งจะต้องทำลายกิจกรรม / ส่วนทั้งหมดการเรียกร้องต่อไปนี้initLoader()จะคืนค่าเก่าLoaderแทนที่จะเป็น สร้างใหม่ โดยปกติคุณจะใช้restartLoader()เมื่อคุณต้องการเปลี่ยนLoaderข้อความค้นหา (เช่นคุณต้องการรับข้อมูลที่กรอง / เรียงลำดับเป็นต้น)
Alex Lockwood

4
ฉันยังคงสับสนเกี่ยวกับการตัดสินใจของ API ที่มีทั้งสองวิธีเนื่องจากมีลายเซ็นเหมือนกัน เหตุใด API จึงไม่เป็นเมธอด startLoader () เดียวที่ "ถูกต้อง" ทุกครั้ง ฉันคิดว่านี่เป็นส่วนที่สร้างความสับสนให้กับผู้คนจำนวนมาก
Tom anMoney

1
@TomanMoney เอกสารที่นี่พูดว่า: developer.android.com/guide/components/loaders.html "พวกเขาจะเชื่อมต่อกับเคอร์เซอร์ของตัวโหลดตัวสุดท้ายโดยอัตโนมัติเมื่อถูกสร้างขึ้นใหม่หลังจากเปลี่ยนการกำหนดค่าดังนั้นพวกเขาไม่จำเป็นต้องสืบค้นข้อมูลอีกครั้ง"
IgorGanapolsky

16

ฉันเพิ่งประสบปัญหากับตัวจัดการตัวโหลดหลายตัวและการเปลี่ยนแปลงการวางแนวหน้าจอและอยากจะบอกว่าหลังจากการลองผิดลองถูกมากมายรูปแบบต่อไปนี้ใช้ได้กับฉันทั้งในกิจกรรมและส่วนย่อย:

onCreate: call initLoader(s)
          set a one-shot flag
onResume: call restartLoader (or later, as applicable) if the one-shot is not set.
          unset the one-shot in either case.

(ในคำอื่นตั้งค่าสถานะบางอย่างเพื่อให้initLoaderถูกเสมอเรียกใช้ครั้งเดียวและที่ restartLoader จะดำเนินการใน2 และต่อมาผ่านonResume )

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


ฉันลองใช้initLoaderเท่านั้น .... ดูเหมือนจะไม่ได้ผล

พยายามinitLoaderบนonCreateด้วย null args (เอกสารบอกว่านี่ใช้ได้) & restartLoader (พร้อม args ที่ถูกต้อง) ในonResume .... เอกสารผิด & initLoaderแสดงข้อยกเว้น nullpointer

พยายามrestartLoaderเท่านั้น ... ใช้งานได้ชั่วขณะหนึ่ง แต่เป่าในการวางแนวหน้าจอที่ 5 หรือ 6 อีกครั้ง

พยายามinitLoaderในonResume ; ทำงานอีกครั้งในขณะที่แล้วเป่า (โดยเฉพาะข้อผิดพลาด "เรียกว่า doRetain เมื่อไม่เริ่มต้น:" ... )

พยายามดังต่อไปนี้: (ตัดตอนมาจากคลาสปกที่มี id ตัวโหลดที่ส่งผ่านไปยังตัวสร้าง)

/**
 * start or restart the loader (why bother with 2 separate functions ?) (now I know why)
 * 
 * @param manager
 * @param args
 * @deprecated use {@link #restart(LoaderManager, Bundle)} in onResume (as appropriate) and {@link #initialise(LoaderManager, Bundle)} in onCreate 
 */
@Deprecated 
public void start(LoaderManager manager, Bundle args) {
    if (manager.getLoader(this.id) == null) {
        manager.initLoader(this.id, args, this);
    } else {
        manager.restartLoader(this.id, args, this);
    }
}

(ซึ่งฉันพบที่ไหนสักแห่งใน Stack-Overflow)

อีกครั้งสิ่งนี้ใช้งานได้ระยะหนึ่ง แต่ยังคงทำให้เกิดความผิดพลาดเป็นครั้งคราว


จากสิ่งที่ฉันสามารถหาได้ในขณะที่ดีบักฉันคิดว่ามีบางอย่างที่เกี่ยวข้องกับสถานะบันทึก / กู้คืนอินสแตนซ์ที่ต้องการให้เรียกใช้initLoader (/ s) ในonCreateส่วนของวงจรชีวิตหากพวกเขาจะอยู่รอดจากการหมุนของวงจร . (ฉันอาจจะคิดผิด)

ในกรณีของผู้จัดการที่ไม่สามารถเริ่มต้นได้จนกว่าผลลัพธ์จะกลับมาจากผู้จัดการหรืองานอื่น (เช่นไม่สามารถเริ่มต้นในonCreate ) ฉันใช้เพียงแค่initLoaderเท่านั้น (ฉันอาจจะไม่ถูกต้องในสิ่งนี้ แต่ดูเหมือนว่าจะใช้งานได้รถตักรองเหล่านี้ไม่ได้เป็นส่วนหนึ่งของสถานะอินสแตนซ์ทันทีดังนั้นการใช้initLoaderอาจถูกต้องในกรณีนี้)

วงจรชีวิต


เมื่อดูไดอะแกรมและเอกสารฉันคิดว่า initLoader ควรเข้าสู่ onCreate & restartLoader ใน onRestart for Activities แต่นั่นทำให้ Fragments ใช้รูปแบบที่แตกต่างกันและฉันไม่มีเวลาตรวจสอบว่าสิ่งนี้เสถียรจริงหรือไม่ ใครสามารถแสดงความคิดเห็นว่าพวกเขาประสบความสำเร็จกับรูปแบบกิจกรรมนี้หรือไม่?


/ @ Simon ถูกต้อง 100% และนี่ควรเป็นคำตอบที่ยอมรับได้ ฉันไม่ค่อยเชื่อคำตอบของเขาและใช้เวลาหลายชั่วโมงในการพยายามหาวิธีต่างๆในการทำงานนี้ ทันทีที่ฉันย้ายการเรียก initLoader ไปที่ onCreate สิ่งต่างๆก็เริ่มทำงาน จากนั้นคุณต้องใช้แฟล็ก one-shot เพื่อพิจารณาเวลาที่ onStart ถูกเรียก แต่ไม่มี onCreate
CjS

2
"พยายาม restartLoader เท่านั้น ... ใช้งานได้สักพัก แต่จะเปลี่ยนการวางแนวหน้าจอที่ 5 หรือ 6" มันไม่? ฉันเพิ่งลองและหมุนหน้าจอเป็นร้อยครั้งและไม่มีการระเบิด คุณได้รับข้อยกเว้นประเภทใด
Tom anMoney

-1 ฉันขอขอบคุณสำหรับความพยายามในการค้นคว้าเบื้องหลังคำตอบนี้ แต่ผลลัพธ์ส่วนใหญ่ไม่ถูกต้อง
Emanuel Moecklin

1
@IgorGanapolsky เกือบทุกอย่าง. หากคุณอ่านและเข้าใจคำตอบของฉันคุณจะเข้าใจว่า initLoader และ restartLoader ทำอะไรและใช้เมื่อใดและคุณจะเข้าใจด้วยว่าทำไมข้อสรุปของ Simon เกือบทั้งหมดจึงผิด ไม่มีการเชื่อมต่อระหว่างวงจรชีวิตของชิ้นส่วน / กิจกรรมและการตัดสินใจว่าจะใช้ initLoader / restartLoader เมื่อใด (โดยมีข้อแม้ที่ฉันอธิบายภายใต้การเปลี่ยนแปลงการกำหนดค่า) ไซมอนสรุปจากการลองผิดลองถูกว่าวงจรชีวิตเป็นเบาะแสในการทำความเข้าใจทั้งสองวิธี แต่ไม่ใช่
Emanuel Moecklin

@IgorGanapolsky ฉันไม่ได้พยายามโฆษณาคำตอบของตัวเอง ฉันแค่พยายามช่วยเหลือนักพัฒนารายอื่นและป้องกันไม่ให้พวกเขาใช้ผลลัพธ์ของ Simon สำหรับแอปของพวกเขาเอง เมื่อคุณเข้าใจว่าสองวิธีนี้มีไว้เพื่ออะไรแล้วสิ่งทั้งหมดจะค่อนข้างชัดเจนและตรงไปตรงมาเพื่อนำไปใช้
Emanuel Moecklin

0

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

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


-1

หากมีตัวโหลดอยู่แล้ว restartLoader จะหยุด / ยกเลิก / ทำลายอันเก่าในขณะที่ initLoader จะเริ่มต้นด้วยการเรียกกลับที่กำหนด ฉันไม่พบว่าการโทรกลับแบบเก่าทำอะไรในกรณีเหล่านี้ แต่ฉันเดาว่ามันจะถูกละทิ้ง

ฉันสแกนผ่านhttp://grepcode.com/file/repository.grepcode.com/java/ext/com.google.android/android/4.0.1_r1/android/app/LoaderManager.javaแต่ฉันไม่พบสิ่งที่แน่นอน ความแตกต่างคือนอกเหนือจากนั้นวิธีการทำสิ่งที่แตกต่างกัน ดังนั้นฉันจะบอกว่าใช้ initLoader เป็นครั้งแรกและรีสตาร์ทในครั้งต่อไปนี้แม้ว่าฉันจะไม่สามารถพูดได้อย่างมั่นใจว่าแต่ละคนจะทำอย่างไร


และจะinitLoaderทำอย่างไรในกรณีนี้?
theomega

-1

ตัวโหลดเริ่มต้นเมื่อเริ่มต้นครั้งแรกใช้วิธี loadInBackground () ในการเริ่มต้นครั้งที่สองจะถูกละไว้ ดังนั้นความคิดเห็นของฉันทางออกที่ดีกว่าคือ:

Loader<?> loa; 
try {
    loa = getLoaderManager().getLoader(0);
} catch (Exception e) {
    loa = null;
}
if (loa == null) {
    getLoaderManager().initLoader(0, null, this);
} else {
    loa.forceLoad();
}

////////////////////////////////////////////////// /////////////////////////

protected SimpleCursorAdapter mAdapter;

private abstract class SimpleCursorAdapterLoader 
    extends AsyncTaskLoader <Cursor> {

    public SimpleCursorAdapterLoader(Context context) {
        super(context);
    }

    @Override
    protected void onStartLoading() {
        if (takeContentChanged() || mAdapter.isEmpty()) {
            forceLoad();
        }
    }

    @Override
    protected void onStopLoading() {
        cancelLoad();
    }

    @Override
    protected void onReset() {
        super.onReset();
        onStopLoading();
    }
}

ฉันใช้เวลาหลายครั้งในการค้นหาโซลูชันนี้ - restartLoader (... ) ทำงานไม่ถูกต้องในกรณีของฉัน ForceLoad เท่านั้น () อนุญาตให้เสร็จสิ้นการโหลดเธรดก่อนหน้าโดยไม่ต้องเรียกกลับ (ดังนั้นคุณจะมีธุรกรรมฐานข้อมูลทั้งหมดเสร็จสิ้นอย่างถูกต้อง) และเริ่มเธรดใหม่อีกครั้ง ใช่มันต้องใช้เวลาเพิ่ม แต่มีเสถียรภาพมากขึ้น เฉพาะเธรดที่เริ่มต้นล่าสุดเท่านั้นที่จะเรียกกลับ ดังนั้นหากคุณต้องการทำการทดสอบด้วยการขัดจังหวะธุรกรรมฐานข้อมูลของคุณ - ยินดีต้อนรับคุณลอง restartLoader (... ) มิฉะนั้น forceLoad () ความสะดวกเพียงอย่างเดียวของ restartLoader (... ) คือการส่งข้อมูลเริ่มต้นใหม่ฉันหมายถึงพารามิเตอร์ และโปรดอย่าลืมทำลาย loader ในเมธอด onDetach () ของ Fragment ที่เหมาะสมในกรณีนี้ โปรดทราบว่าบางครั้งเมื่อคุณมีกิจกรรมและให้พวกเขาพูดว่า 2 ชิ้นส่วนที่มี Loader แต่ละกิจกรรมรวม - คุณจะเข้าถึงผู้จัดการ Loader เพียง 2 คนดังนั้นกิจกรรมจึงแชร์ LoaderManager กับ Fragment ซึ่งจะแสดงบนหน้าจอก่อนระหว่างการโหลด ลอง LoaderManager.enableDebugLogging (จริง); เพื่อดูรายละเอียดในแต่ละกรณี


2
-1 สำหรับตัดการโทรไปยังgetLoader(0)ไฟล์try { ... } catch (Exception e) { ... }.
Alex Lockwood
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.