Tổng quan về thư viện Paging 2 Một phần của Android Jetpack.
Thư viện Paging giúp bạn tải và hiển thị đồng thời nhiều phần dữ liệu nhỏ. Việc tải một phần dữ liệu theo yêu cầu sẽ làm giảm mức sử dụng băng thông mạng và tài nguyên hệ thống.
Hướng dẫn này cung cấp một số ví dụ khái niệm cũng như tổng quan về cách hoạt động của thư viện. Để xem toàn bộ ví dụ về cách thư viện này hoạt động, hãy tham khảo lớp học lập trình và các mẫu trong phần tài nguyên bổ sung.
Thiết lập
Để nhập các thành phần Paging vào ứng dụng Android, hãy thêm các phần phụ thuộc sau vào tệp build.gradle
của ứng dụng:
Groovy
dependencies { def paging_version = "2.1.2" implementation "androidx.paging:paging-runtime:$paging_version" // For Kotlin use paging-runtime-ktx // alternatively - without Android dependencies for testing testImplementation "androidx.paging:paging-common:$paging_version" // For Kotlin use paging-common-ktx // optional - RxJava support implementation "androidx.paging:paging-rxjava2:$paging_version" // For Kotlin use paging-rxjava2-ktx }
Kotlin
dependencies { val paging_version = "2.1.2" implementation("androidx.paging:paging-runtime:$paging_version") // For Kotlin use paging-runtime-ktx // alternatively - without Android dependencies for testing testImplementation("androidx.paging:paging-common:$paging_version") // For Kotlin use paging-common-ktx // optional - RxJava support implementation("androidx.paging:paging-rxjava2:$paging_version") // For Kotlin use paging-rxjava2-ktx }
Cấu trúc thư viện
Phần này mô tả và cho thấy các thành phần chính của thư viện Paging.
PagedList
Thành phần chính của thư viện Paging là lớp PagedList
, có nhiệm vụ tải các phần dữ liệu của ứng dụng hoặc các trang. Những dữ liệu cần thêm sẽ được phân trang vào đối tượng PagedList
hiện có. Nếu có bất kỳ thay đổi nào về dữ liệu được tải thì một thực thể mới của PagedList
sẽ được phát tới chủ sở hữu dữ liệu có thể quan sát từ LiveData
hoặc đối tượng dựa trên RxJava2. Khi đối tượng PagedList
được tạo, giao diện người dùng của ứng dụng sẽ trình bày nội dung của các mục đó mà vẫn tuân thủ vòng đời của bộ điều khiển giao diện người dùng.
Đoạn mã sau đây cho bạn biết cách định cấu hình mô hình xem của ứng dụng để tải và trình bày dữ liệu bằng cách dùng một chủ sở hữu LiveData
của đối tượng PagedList
:
Kotlin
class ConcertViewModel(concertDao: ConcertDao) : ViewModel() { val concertList: LiveData<PagedList<Concert>> = concertDao.concertsByDate().toLiveData(pageSize = 50) }
Java
public class ConcertViewModel extends ViewModel { private ConcertDao concertDao; public final LiveData<PagedList<Concert>> concertList; // Creates a PagedList object with 50 items per page. public ConcertViewModel(ConcertDao concertDao) { this.concertDao = concertDao; concertList = new LivePagedListBuilder<>( concertDao.concertsByDate(), 50).build(); } }
Dữ liệu
Mỗi thực thể của PagedList
đều tải ảnh chụp nhanh mới nhất về dữ liệu của ứng dụng từ đối tượng DataSource
tương ứng. Dữ liệu di chuyển từ phần phụ trợ hoặc cơ sở dữ liệu của ứng dụng tới đối tượng PagedList
.
Ví dụ sau đây sử dụng Thư viện lưu trữ Room để sắp xếp dữ liệu của ứng dụng, nhưng nếu muốn lưu trữ dữ liệu bằng một cách khác thì bạn cũng có thể tự mình cung cấp nguồn dữ liệu nhà máy.
Kotlin
@Dao interface ConcertDao { // The Int type parameter tells Room to use a PositionalDataSource object. @Query("SELECT * FROM concerts ORDER BY date DESC") fun concertsByDate(): DataSource.Factory<Int, Concert> }
Java
@Dao public interface ConcertDao { // The Integer type parameter tells Room to use a // PositionalDataSource object. @Query("SELECT * FROM concerts ORDER BY date DESC") DataSource.Factory<Integer, Concert> concertsByDate(); }
Để tìm hiểu thêm về cách bạn có thể tải dữ liệu vào đối tượng PagedList
, hãy xem hướng dẫn về cách Tải dữ liệu được phân trang.
Giao diện người dùng
Lớp PagedList
hoạt động cùng PagedListAdapter
để tải các mục vào một RecyclerView
. Các lớp này cùng nhau tìm nạp, hiển thị nội dung khi được tải và tìm nạp trước các thay đổi về nội dung nằm ngoài chế độ xem cũng như nội dung tạo ảnh động.
Để tìm hiểu thêm, hãy xem hướng dẫn về cách Hiển thị danh sách đã phân trang.
Hỗ trợ nhiều cấu trúc dữ liệu
Thư viện Paging hỗ trợ các cấu trúc dữ liệu sau:
- Chỉ được phân phát từ máy chủ phụ trợ.
- Chỉ được lưu trữ trong cơ sở dữ liệu trên thiết bị.
- Sự kết hợp của nhiều nguồn, dùng cơ sở dữ liệu trên thiết bị làm bộ nhớ đệm.
Hình 1 cho thấy cách dữ liệu di chuyển trong mỗi trường hợp cấu trúc này. Trong trường hợp giải pháp chỉ dành cho mạng hoặc cơ sở dữ liệu, dữ liệu sẽ chuyển thẳng đến mô hình giao diện của ứng dụng. Nếu bạn đang sử dụng phương pháp kết hợp thì dữ liệu sẽ chuyển từ máy chủ phụ trợ vào cơ sở dữ liệu trên thiết bị, sau đó chuyển đến mô hình giao diện của ứng dụng. Đôi khi, điểm cuối của mỗi luồng dữ liệu sẽ hết dữ liệu để tải. Lúc đó, thiết bị sẽ yêu cầu thêm dữ liệu từ thành phần đã cung cấp dữ liệu. Ví dụ: khi một cơ sở dữ liệu trên thiết bị hết dữ liệu, đơn vị này sẽ yêu cầu thêm dữ liệu từ máy chủ.
Phần còn lại của mục này cung cấp những đề xuất để định cấu hình từng trường hợp sử dụng luồng dữ liệu.
Chỉ với mạng
Để hiển thị dữ liệu từ máy chủ phụ trợ, hãy sử dụng phiên bản đồng bộ của API Retrofit để tải thông tin vào đối tượng DataSource
tuỳ chỉnh của bạn.
Chỉ với cơ sở dữ liệu
Hãy thiết lập RecyclerView
để quan sát dữ liệu cục bộ và bạn nên thiết lập bằng Thư viện lưu trữ Room. Bằng cách đó, bất cứ khi nào dữ liệu được chèn hoặc sửa đổi trong cơ sở dữ liệu của ứng dụng, những thay đổi này sẽ tự động được phản ánh trong RecyclerView
đang hiển thị dữ liệu này.
Mạng và cơ sở dữ liệu
Sau khi bắt đầu quan sát, bạn có thể biết khi nào cơ sở dữ liệu đã hết dữ liệu bằng cách sử dụng PagedList.BoundaryCallback
.
Sau đó, bạn có thể tìm nạp thêm các mục từ mạng của mình và chèn các mục đó vào cơ sở dữ liệu. Nếu giao diện người dùng đang quan sát cơ sở dữ liệu, thì đó là tất cả những gì bạn cần làm.
Xử lý lỗi mạng
Khi sử dụng mạng để tìm nạp hoặc phân trang dữ liệu bạn đang hiển thị bằng thư viện Paging, bạn không thể lúc nào cũng coi mạng là "có sẵn" hoặc "không có sẵn". Điều này rất quan trọng vì nhiều khi kết nối sẽ bị gián đoạn hoặc rời rạc:
- Một máy chủ cụ thể có thể sẽ không phản hồi được yêu cầu kết nối mạng.
- Có thể thiết bị kết nối với một mạng chậm hoặc yếu.
Thay vào đó, ứng dụng sẽ kiểm tra từng yêu cầu để tìm lỗi và khôi phục nhanh nhất có thể trong trường hợp không có mạng. Ví dụ: bạn có thể cung cấp nút "thử lại" để người dùng chọn nếu bước làm mới dữ liệu không hoạt động. Nếu xảy ra lỗi trong bước phân trang dữ liệu thì tốt nhất bạn nên thử tính năng tự động gửi lại các yêu cầu phân trang.
Cập nhật ứng dụng hiện có
Nếu ứng dụng của bạn đã sử dụng dữ liệu từ một cơ sở dữ liệu hoặc nguồn phụ trợ, thì bạn có thể nâng cấp trực tiếp lên chức năng mà thư viện Paging cung cấp. Phần này cho biết cách nâng cấp một ứng dụng có thiết kế có sẵn phổ biến.
Giải pháp phân trang tuỳ chỉnh
Nếu sử dụng chức năng tuỳ chỉnh để tải các tập hợp dữ liệu con từ nguồn dữ liệu của ứng dụng, bạn có thể thay thế logic này bằng logic của lớp PagedList
. Các thực thể của PagedList
cung cấp kết nối tích hợp sẵn cho các nguồn dữ liệu phổ biến. Những thực thể này cũng cung cấp bộ chuyển đổi cho các đối tượng RecyclerView
mà bạn có thể đưa vào giao diện của ứng dụng.
Dữ liệu được tải bằng cách sử dụng danh sách thay vì trang
Nếu bạn sử dụng một danh sách trong bộ nhớ làm cấu trúc dữ liệu sao lưu cho bộ chuyển đổi của giao diện, hãy cân nhắc việc theo dõi các bản cập nhật dữ liệu bằng cách sử dụng lớp PagedList
nếu số lượng mục trong danh sách có thể lớn. Các thực thể của PagedList
có thể sử dụng LiveData<PagedList>
hoặc Observable<List>
để truyền các bản cập nhật dữ liệu đến giao diện của ứng dụng. Việc này sẽ giúp giảm thiểu thời gian tải và mức sử dụng bộ nhớ. Hơn thế, bạn có thể thay thế đối tượng List
bằng đối tượng PagedList
trong ứng dụng mà không phải thực hiện bất kỳ thay đổi nào trong cấu trúc giao diện người dùng hoặc logic cập nhật dữ liệu của ứng dụng.
Liên kết con trỏ dữ liệu với chế độ xem danh sách bằng CursorAdapter
Ứng dụng của bạn có thể sử dụng CursorAdapter
để liên kết dữ liệu của Cursor
với ListView
. Trong trường hợp đó, thường thì bạn cần di chuyển vùng chứa từ ListView
đến RecyclerView
cho danh sách giao diện người dùng của ứng dụng, sau đó thay thế thành phần Cursor
với Room hoặc PositionalDataSource
. Việc chọn thành phần nào còn tuỳ thuộc vào việc các thực thể của Cursor
có truy cập vào cơ sở dữ liệu SQLite hay không.
Trong một số trường hợp, chẳng hạn như khi làm việc với các thực thể của Spinner
, bạn chỉ cần cung cấp chính bộ chuyển đổi đó. Sau đó, thư viện sẽ lấy dữ liệu đã được tải vào bộ chuyển đổi và cho bạn xem dữ liệu. Trong những trường hợp này, hãy thay đổi loại dữ liệu của bộ chuyển đổi thành LiveData<PagedList>
, sau đó bọc danh sách này trong một đối tượng ArrayAdapter
trước khi thử để lớp thư viện làm tăng các mục này trong giao diện người dùng.
Tải không đồng bộ nội dung bằng AsyncListUtil
Nếu bạn đang sử dụng các đối tượng AsyncListUtil
để tải và hiển thị không đồng bộ nhóm thông tin, thì thư viện Paging sẽ giúp bạn tải dữ liệu dễ dàng hơn:
- Dữ liệu của bạn không cần phải có vị trí. Bạn có thể sử dụng thư viện Paging để tải dữ liệu trực tiếp từ phần phụ trợ bằng cách sử dụng các khoá mà mạng cung cấp.
- Dữ liệu của bạn có thể lớn đến mức vô cùng. Bằng cách sử dụng thư viện Paging, bạn có thể tải dữ liệu vào các trang cho đến khi không còn dữ liệu nào.
- Bạn có thể quan sát dữ liệu dễ dàng hơn. Thư viện Paging có thể hiển thị dữ liệu mà ViewModel của ứng dụng chứa trong cấu trúc dữ liệu có thể quan sát.
Ví dụ về cơ sở dữ liệu
Các đoạn mã sau đây cho thấy một số cách để tất cả thành phần có thể hoạt động cùng nhau.
Quan sát dữ liệu đã phân trang bằng LiveData
Đoạn mã sau đây cho thấy tất cả thành phần đang hoạt động cùng nhau. Khi các sự kiện buổi hoà nhạc được thêm, xoá hoặc thay đổi trong cơ sở dữ liệu, nội dung trong RecyclerView
sẽ được cập nhật tự động một cách hiệu quả:
Kotlin
@Dao interface ConcertDao { // The Int type parameter tells Room to use a PositionalDataSource // object, with position-based loading under the hood. @Query("SELECT * FROM concerts ORDER BY date DESC") fun concertsByDate(): DataSource.Factory<Int, Concert> } class ConcertViewModel(concertDao: ConcertDao) : ViewModel() { val concertList: LiveData<PagedList<Concert>> = concertDao.concertsByDate().toLiveData(pageSize = 50) } class ConcertActivity : AppCompatActivity() { public override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) // Use the 'by viewModels()' Kotlin property delegate // from the activity-ktx artifact val viewModel: ConcertViewModel by viewModels() val recyclerView = findViewById(R.id.concert_list) val adapter = ConcertAdapter() viewModel.concertList.observe(this, PagedList(adapter::submitList)) recyclerView.setAdapter(adapter) } } class ConcertAdapter() : PagedListAdapter<Concert, ConcertViewHolder>(DIFF_CALLBACK) { fun onBindViewHolder(holder: ConcertViewHolder, position: Int) { val concert: Concert? = getItem(position) // Note that "concert" is a placeholder if it's null. holder.bindTo(concert) } companion object { private val DIFF_CALLBACK = object : DiffUtil.ItemCallback<Concert>() { // Concert details may have changed if reloaded from the database, // but ID is fixed. override fun areItemsTheSame(oldConcert: Concert, newConcert: Concert) = oldConcert.id == newConcert.id override fun areContentsTheSame(oldConcert: Concert, newConcert: Concert) = oldConcert == newConcert } } }
Java
@Dao public interface ConcertDao { // The Integer type parameter tells Room to use a PositionalDataSource // object, with position-based loading under the hood. @Query("SELECT * FROM concerts ORDER BY date DESC") DataSource.Factory<Integer, Concert> concertsByDate(); } public class ConcertViewModel extends ViewModel { private ConcertDao concertDao; public final LiveData<PagedList<Concert>> concertList; public ConcertViewModel(ConcertDao concertDao) { this.concertDao = concertDao; concertList = new LivePagedListBuilder<>( concertDao.concertsByDate(), /* page size */ 50).build(); } } public class ConcertActivity extends AppCompatActivity { @Override public void onCreate(@Nullable Bundle savedInstanceState) { super.onCreate(savedInstanceState); ConcertViewModel viewModel = new ViewModelProvider(this).get(ConcertViewModel.class); RecyclerView recyclerView = findViewById(R.id.concert_list); ConcertAdapter adapter = new ConcertAdapter(); viewModel.concertList.observe(this, adapter::submitList); recyclerView.setAdapter(adapter); } } public class ConcertAdapter extends PagedListAdapter<Concert, ConcertViewHolder> { protected ConcertAdapter() { super(DIFF_CALLBACK); } @Override public void onBindViewHolder(@NonNull ConcertViewHolder holder, int position) { Concert concert = getItem(position); if (concert != null) { holder.bindTo(concert); } else { // Null defines a placeholder item - PagedListAdapter automatically // invalidates this row when the actual object is loaded from the // database. holder.clear(); } } private static DiffUtil.ItemCallback<Concert> DIFF_CALLBACK = new DiffUtil.ItemCallback<Concert>() { // Concert details may have changed if reloaded from the database, // but ID is fixed. @Override public boolean areItemsTheSame(Concert oldConcert, Concert newConcert) { return oldConcert.getId() == newConcert.getId(); } @Override public boolean areContentsTheSame(Concert oldConcert, Concert newConcert) { return oldConcert.equals(newConcert); } }; }
Quan sát dữ liệu phân trang bằng RxJava2
Nếu muốn sử dụng RxJava2 thay vì LiveData
, bạn có thể tạo đối tượng Observable
hoặc Flowable
:
Kotlin
class ConcertViewModel(concertDao: ConcertDao) : ViewModel() { val concertList: Observable<PagedList<Concert>> = concertDao.concertsByDate().toObservable(pageSize = 50) }
Java
public class ConcertViewModel extends ViewModel { private ConcertDao concertDao; public final Observable<PagedList<Concert>> concertList; public ConcertViewModel(ConcertDao concertDao) { this.concertDao = concertDao; concertList = new RxPagedListBuilder<>( concertDao.concertsByDate(), /* page size */ 50) .buildObservable(); } }
Sau đó, bạn có thể bắt đầu và dừng quan sát dữ liệu bằng cách sử dụng đoạn mã sau:
Kotlin
class ConcertActivity : AppCompatActivity() { private val adapter = ConcertAdapter() // Use the 'by viewModels()' Kotlin property delegate // from the activity-ktx artifact private val viewModel: ConcertViewModel by viewModels() private val disposable = CompositeDisposable() public override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) val recyclerView = findViewById(R.id.concert_list) recyclerView.setAdapter(adapter) } override fun onStart() { super.onStart() disposable.add(viewModel.concertList .subscribe(adapter::submitList))) } override fun onStop() { super.onStop() disposable.clear() } }
Java
public class ConcertActivity extends AppCompatActivity { private ConcertAdapter adapter = new ConcertAdapter(); private ConcertViewModel viewModel; private CompositeDisposable disposable = new CompositeDisposable(); @Override public void onCreate(@Nullable Bundle savedInstanceState) { super.onCreate(savedInstanceState); RecyclerView recyclerView = findViewById(R.id.concert_list); viewModel = new ViewModelProvider(this).get(ConcertViewModel.class); recyclerView.setAdapter(adapter); } @Override protected void onStart() { super.onStart(); disposable.add(viewModel.concertList .subscribe(adapter.submitList(flowableList) )); } @Override protected void onStop() { super.onStop(); disposable.clear(); } }
Mã cho ConcertDao
và ConcertAdapter
là giống nhau đối với giải pháp dựa trên RxJava2 vì chúng là để dành cho giải pháp dựa trên LiveData
.
Gửi phản hồi
Hãy chia sẻ phản hồi và ý kiến của bạn với chúng tôi thông qua các tài nguyên sau:
- Công cụ theo dõi lỗi
- Báo cáo sự cố để chúng tôi có thể sửa lỗi.
Tài nguyên khác
Để tìm hiểu thêm về Thư viện Paging, hãy tham khảo các tài nguyên sau.
Mẫu
Lớp học lập trình
Video
- Android Jetpack: quản lý danh sách vô hạn bằng RecyclerView và Paging (Google I/O '18)
- Android Jetpack: Paging
Đề xuất cho bạn
- Lưu ý: văn bản có đường liên kết sẽ hiện khi JavaScript tắt
- Di chuyển sang Paging 3
- Hiện danh sách được phân trang
- Thu thập dữ liệu được phân trang