Để trả lời câu hỏi này, bạn cần đào sâu vào LoaderManager
mã. Mặc dù tài liệu về LoaderManager không đủ rõ ràng (hoặc sẽ không có câu hỏi này), nhưng tài liệu về LoaderManagerImpl, một lớp con của LoaderManager trừu tượng, sẽ sáng tỏ hơn nhiều.
initLoader
Gọi để khởi tạo một ID cụ thể bằng Trình tải. Nếu ID này đã có Trình tải được liên kết với nó, nó sẽ không thay đổi và bất kỳ cuộc gọi lại nào trước đó được thay thế bằng các ID mới được cung cấp. Nếu hiện tại không có Trình tải cho ID, một trình tải mới sẽ được tạo và bắt đầu.
Hàm này thường được sử dụng khi một thành phần đang khởi tạo, để đảm bảo rằng Trình tải mà nó dựa vào được tạo ra. Điều này cho phép nó sử dụng lại dữ liệu của Trình tải hiện có nếu đã có dữ liệu, do đó, khi hoạt động được tạo lại sau khi thay đổi cấu hình, không cần tạo lại trình tải của nó.
khởi động lại
Gọi để tạo lại Trình tải được liên kết với một ID cụ thể. Nếu hiện tại có một Trình tải được liên kết với ID này, nó sẽ bị hủy / dừng / hủy nếu thích hợp. Trình tải mới với các đối số đã cho sẽ được tạo và dữ liệu của nó được gửi cho bạn một khi có sẵn.
[...] Sau khi gọi chức năng này, mọi Trình tải trước đó được liên kết với ID này sẽ được coi là không hợp lệ và bạn sẽ không nhận được cập nhật dữ liệu nào nữa từ chúng.
Về cơ bản có hai trường hợp:
- Trình tải với id không tồn tại: cả hai phương thức sẽ tạo một trình tải mới để không có sự khác biệt ở đó
- Trình tải với id đã tồn tại:
initLoader
sẽ chỉ thay thế các cuộc gọi lại được truyền dưới dạng tham số nhưng sẽ không hủy hoặc dừng trình tải. Đối với CursorLoader
điều đó có nghĩa là con trỏ vẫn mở và hoạt động (nếu đó là trường hợp trước initLoader
cuộc gọi). `restartLoader, mặt khác, sẽ hủy, dừng và hủy bộ tải (và đóng nguồn dữ liệu cơ bản như một con trỏ) và tạo một trình tải mới (cũng sẽ tạo một con trỏ mới và chạy lại truy vấn nếu trình tải một CoderLoader).
Đây là mã đơn giản hóa cho cả hai phương thức:
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;
}
khởi động lại
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);
Như chúng ta có thể thấy trong trường hợp trình tải không tồn tại (thông tin == null) cả hai phương thức sẽ tạo một trình tải mới (thông tin = createdAndInstallLoader (...)). Trong trường hợp trình tải đã tồn tại initLoader
chỉ thay thế các cuộc gọi lại (info.mCallbacks = ...) trong khi restartLoader
vô hiệu hóa trình tải cũ (nó sẽ bị hủy khi trình tải mới hoàn thành công việc của nó) và sau đó tạo một trình gọi mới.
Vì vậy, cho biết bây giờ rõ ràng khi nào nên sử dụng initLoader
và khi nào sử dụng restartLoader
và tại sao nó có ý nghĩa để có hai phương pháp.
initLoader
được sử dụng để đảm bảo có bộ nạp khởi tạo. Nếu không tồn tại một cái mới được tạo ra, nếu cái đó đã tồn tại thì nó được sử dụng lại. Chúng tôi luôn sử dụng phương thức này KHÔNG GIỚI HẠN, chúng tôi cần một trình tải mới vì truy vấn để chạy đã thay đổi (không phải dữ liệu cơ bản mà là truy vấn thực tế như trong câu lệnh SQL cho CoderLoader), trong trường hợp đó chúng tôi sẽ gọi restartLoader
.
Vòng đời Hoạt động / Đoạn không liên quan gì đến quyết định sử dụng một hoặc phương thức khác (và không cần phải theo dõi các cuộc gọi bằng cờ một lần như Simon đề xuất)! Quyết định này được đưa ra chỉ dựa trên "nhu cầu" đối với bộ tải mới. Nếu chúng tôi muốn chạy cùng một truy vấn chúng tôi sử dụng initLoader
, nếu chúng tôi muốn chạy một truy vấn khác, chúng tôi sử dụng restartLoader
.
Chúng tôi luôn có thể sử dụng restartLoader
nhưng điều đó sẽ không hiệu quả. Sau khi xoay màn hình hoặc nếu người dùng điều hướng khỏi ứng dụng và quay lại cùng một Hoạt động, chúng ta thường muốn hiển thị cùng một kết quả truy vấn và do đó restartLoader
sẽ tạo lại trình tải một cách không cần thiết và loại bỏ kết quả truy vấn cơ bản (có khả năng tốn kém).
Điều rất quan trọng để hiểu sự khác biệt giữa dữ liệu được tải và "truy vấn" để tải dữ liệu đó. Giả sử chúng ta sử dụng một CoderLoader truy vấn một bảng cho các đơn đặt hàng. Nếu một đơn hàng mới được thêm vào bảng đó, CthonLoader sử dụng onContentChanged () để thông báo cho UI để cập nhật và hiển thị đơn hàng mới (không cần sử dụng restartLoader
trong trường hợp này). Nếu chúng tôi muốn chỉ hiển thị các đơn đặt hàng đang mở, chúng tôi cần một truy vấn mới và chúng tôi sẽ sử dụng restartLoader
để trả về một CoderLoader mới phản ánh truy vấn mới.
Có một số mối quan hệ giữa hai phương pháp?
Họ chia sẻ mã để tạo Trình tải mới nhưng họ làm những việc khác nhau khi trình tải đã tồn tại.
Có phải gọi restartLoader
luôn luôn gọi initLoader
?
Không, nó không bao giờ làm.
Tôi có thể gọi restartLoader
mà không phải gọi initLoader
không?
Đúng.
Có an toàn để gọi initLoader
hai lần để làm mới dữ liệu?
Gọi initLoader
hai lần là an toàn nhưng không có dữ liệu nào được làm mới.
Khi nào tôi nên sử dụng một trong hai và tại sao ?
Điều đó (hy vọng) sẽ rõ ràng sau những giải thích của tôi ở trên.
Thay đổi cấu hình
LoaderManager duy trì trạng thái của nó qua các thay đổi cấu hình (bao gồm cả thay đổi hướng), do đó bạn sẽ nghĩ chúng tôi không còn gì để làm. Nghĩ lại...
Trước hết, LoaderManager không giữ lại các cuộc gọi lại, vì vậy nếu bạn không làm gì thì bạn sẽ không nhận được các cuộc gọi đến các phương thức gọi lại của mình như thế nào onLoadFinished()
và tương tự và điều đó rất có thể sẽ phá vỡ ứng dụng của bạn.
Do đó, chúng ta PHẢI gọi ít nhất là initLoader
để khôi phục các phương thức gọi lại ( restartLoader
dĩ nhiên là có thể). Các tài liệu nêu:
Nếu tại điểm gọi của người gọi đang ở trạng thái bắt đầu và trình tải được yêu cầu đã tồn tại và đã tạo dữ liệu của nó, thì cuộc gọi lại onLoadFinished(Loader, D)
sẽ được gọi ngay lập tức (bên trong chức năng này) [...].
Điều đó có nghĩa là nếu chúng tôi gọi initLoader
sau khi thay đổi định hướng, chúng tôi sẽ nhận được một onLoadFinished
cuộc gọi ngay lập tức vì dữ liệu đã được tải (giả sử đó là trường hợp trước khi thay đổi). Mặc dù điều đó nghe có vẻ khó khăn nhưng không phải tất cả chúng ta đều yêu thích Android ...).
Chúng ta phải phân biệt giữa hai trường hợp:
- Cấu hình của Handles tự thay đổi: đây là trường hợp của Fragment sử dụng setRetainInstance (true) hoặc cho một Activity với
android:configChanges
thẻ theo trong tệp kê khai. Các thành phần này sẽ không nhận được cuộc gọi onCreate sau khi quay màn hình, vì vậy hãy nhớ gọi
initLoader/restartLoader
theo phương thức gọi lại khác (ví dụ: trong
onActivityCreated(Bundle)
). Để có thể khởi tạo (các) Trình tải, các id trình tải cần được lưu trữ (ví dụ: trong Danh sách). Vì thành phần được giữ lại qua các thay đổi cấu hình, chúng tôi chỉ có thể lặp qua các id và trình gọi của trình tải hiện có initLoader(loaderid,
...)
.
- Không tự xử lý các thay đổi cấu hình: Trong trường hợp này, Trình tải có thể được khởi tạo trong onCreate nhưng chúng tôi cần giữ lại các id trình tải theo cách thủ công hoặc chúng tôi sẽ không thể thực hiện các cuộc gọi initLoader / restartLoader cần thiết. Nếu các id được lưu trữ trong một ArrayList, chúng tôi sẽ thực hiện một thao tác
outState.putIntegerArrayList(loaderIdsKey, loaderIdsArray)
trong onSaveInstanceState và khôi phục các id trong onCreate:
loaderIdsArray =
savedInstanceState.getIntegerArrayList(loaderIdsKey)
trước khi chúng tôi thực hiện cuộc gọi initLoader.
initLoader
(và tất cả các cuộc gọi lại đã kết thúc, Trình tải không hoạt động) sau khi xoay bạn sẽ không nhận đượconLoadFinished
cuộc gọi lại nhưng nếu bạn sử dụng thìrestartLoader
bạn sẽ làm gì?