نظرة عامة على المكتبة في صفحة النقل 2 جزء من Android Jetpack.
تساعدك "مكتبة تسجيل الصفحات" في تحميل أجزاء صغيرة من البيانات وعرضها في كل مرة. يؤدي تحميل البيانات الجزئية عند الطلب إلى تقليل استخدام معدل نقل البيانات للشبكة وموارد النظام.
يقدم هذا الدليل العديد من الأمثلة المفاهيمية للمكتبة، إلى جانب نظرة عامة حول كيفية عملها. لعرض أمثلة كاملة حول آلية عمل هذه المكتبة، يمكنك تجربة الدرس التطبيقي حول الترميز ونماذج من قسم الموارد الإضافية.
ضبط إعدادات
لاستيراد مكوِّنات الترحيل إلى تطبيق Android، أضف
التبعيات التالية إلى ملف build.gradle
في تطبيقك:
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 }
هندسة المكتبة
يصف هذا القسم المكوّنات الرئيسية لمكتبة الصفحات وتعرضها.
قائمة صفحات
المكوّن الرئيسي في مكتبة تسجيل الصفحات هو فئة PagedList
التي تحمّل
مجموعات من بيانات تطبيقك أو الصفحات. وعند الحاجة إلى المزيد من البيانات، يتم تقسيمها إلى كائن PagedList
الحالي. في حال تغيير أي بيانات تم تحميلها، يتم إطلاق مثيل جديد من PagedList
إلى صاحب البيانات القابل للتتبّع من كائن مستند إلى LiveData
أو RxJava2. عند إنشاء عناصر PagedList
، تعرض واجهة المستخدم الخاصة بتطبيقك محتواها، وذلك مع مراعاة دورات نشاط وحدات التحكم في واجهة المستخدم.
يوضّح مقتطف الرمز التالي كيفية ضبط نموذج الملف الشخصي للتطبيق من أجل تحميل البيانات وعرضها باستخدام عنصر LiveData
الذي يحتوي على عناصر 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(); } }
البيانات
يُحمِّل كل مثيل PagedList
لقطة حديثة لبيانات تطبيقك من عنصر
DataSource
المقابل له. تتدفق البيانات من الواجهة الخلفية أو قاعدة بيانات تطبيقك إلى كائن PagedList
.
يستخدم المثال التالي مكتبة استمرارية الغرفة لتنظيم بيانات تطبيقك، ولكن إذا كنت تريد تخزين بياناتك باستخدام وسيلة أخرى، يمكنك أيضًا توفير مصنع مصادر البيانات الخاص بك.
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(); }
لمعرفة المزيد من المعلومات حول كيفية تحميل البيانات في عناصر PagedList
، يُرجى مراجعة
الدليل حول طريقة تحميل البيانات المقسّمة إلى صفحات.
واجهة المستخدم
تعمل الفئة PagedList
مع
PagedListAdapter
لتحميل العناصر في
RecyclerView
. وتعمل هذه الفئات معًا لجلب المحتوى وعرضه أثناء تحميله، والجلب المُسبق للمحتوى خارج العرض، والمؤثرات الحركية للمحتوى.
لمعرفة المزيد من المعلومات، اطّلِع على دليل كيفية عرض القوائم المقسّمة إلى صفحات.
دعم بنيات البيانات المختلفة
تتوافق "مكتبة تسجيل الصفحات" مع بُنى البيانات التالية:
- لا يتم عرضه إلا من خادم خلفية.
- يتم تخزين هذه البيانات في قاعدة بيانات على الجهاز فقط.
- مجموعة من المصادر الأخرى، باستخدام قاعدة البيانات على الجهاز فقط كذاكرة تخزين مؤقت
يوضح الشكل 1 كيفية تدفق البيانات في كل من سيناريوهات البنية هذه. وفي حال توفُّر حل للشبكة فقط أو قاعدة بيانات فقط، تتدفق البيانات مباشرةً إلى نموذج واجهة المستخدم لتطبيقك. إذا كنت تستخدم منهجًا مجمّعًا، تتدفق البيانات من خادم الخلفية إلى قاعدة بيانات على الجهاز ثم إلى نموذج واجهة المستخدم في تطبيقك. من حين لآخر، تنفد البيانات في نقطة نهاية كل تدفق بيانات، وفي هذه المرحلة تطلب المزيد من البيانات من المكون الذي وفر البيانات. على سبيل المثال، عند نفاد البيانات في قاعدة بيانات على الجهاز، فإنها تطلب المزيد من البيانات من الخادم.
![مخططات لتدفقات البيانات](https://developer.android.google.cn/static/topic/libraries/architecture/images/paging-library-data-flow.png?authuser=5&hl=ar)
يقدم الجزء المتبقي من هذا القسم اقتراحات لتهيئة كل حالة استخدام لتدفق البيانات.
الشبكة فقط
لعرض البيانات من خادم خلفية، استخدِم الإصدار المتزامن من Retrofit API لتحميل المعلومات إلى كائن DataSource
المخصّص الخاص بك.
قاعدة البيانات فقط
يمكنك إعداد RecyclerView
لمراقبة مساحة التخزين المحلية، ويُفضَّل استخدام مكتبة
استمرارية الغرفة. بهذه الطريقة، عندما يتم إدراج البيانات أو تعديلها في قاعدة بيانات تطبيقك، تنعكس هذه التغييرات تلقائيًا في RecyclerView
التي تعرض هذه البيانات.
الشبكة وقاعدة البيانات
بعد بدء مراقبة قاعدة البيانات، يمكنك معرفة الوقت الذي
تكون فيه قاعدة البيانات غير متوفرة باستخدام PagedList.BoundaryCallback
.
يمكنك بعد ذلك جلب المزيد من العناصر من شبكتك وإدراجها في قاعدة البيانات. إذا كانت واجهة المستخدم تراقب قاعدة البيانات، فهذا كل ما عليك فعله.
معالجة أخطاء الشبكة
عند استخدام الشبكة لجلب البيانات التي تعرضها باستخدام "مكتبة الصفحات" أو وضع صفحات عليها، من المهم عدم التعامل مع الشبكة على أنّها "متاحة" أو "غير متاحة" طوال الوقت، لأنّ العديد من الاتصالات تكون متقطّعة أو غير مستقرة:
- قد يفشل خادم معين في الاستجابة لطلب الشبكة.
- قد يكون الجهاز متصلاً بشبكة بطيئة أو ضعيفة.
وبدلاً من ذلك، يجب أن يتحقّق تطبيقك من كل طلب لرصد الأعطال، وأن يستردّ البيانات بأكبر قدر ممكن من السلاسة في الحالات التي لا تتوفّر فيها الشبكة. على سبيل المثال، يمكنك توفير زر "إعادة المحاولة" للمستخدمين لتحديد ما إذا كانت خطوة تحديث البيانات لا تعمل. في حال حدوث خطأ أثناء خطوة نقل البيانات إلى صفحات، من الأفضل إعادة محاولة تنفيذ طلبات نقل الصفحات تلقائيًا.
تحديث تطبيقك الحالي
إذا كان تطبيقك يستهلك حاليًا بيانات من قاعدة بيانات أو مصدر خلفي، من الممكن الترقية مباشرةً إلى الوظائف التي توفّرها "مكتبة تسجيل الصفحات". يوضح هذا القسم كيفية ترقية تطبيق له تصميم حالي شائع.
حلول نقل الصفحات المخصصة
إذا كنت تستخدم وظائف مخصّصة لتحميل مجموعات فرعية صغيرة من البيانات من مصدر بيانات تطبيقك، يمكنك استبدال هذا المنطق من الفئة PagedList
. توفّر مثيلات
PagedList
روابط مضمّنة بمصادر البيانات الشائعة. وتوفّر هذه المثيلات أيضًا محوّلات لعناصر RecyclerView
التي يمكن تضمينها في واجهة المستخدم الخاصة بتطبيقك.
البيانات التي تم تحميلها باستخدام القوائم بدلاً من الصفحات
إذا كنت تستخدم قائمة في الذاكرة باعتبارها بنية البيانات الاحتياطية لمعدِّل واجهة المستخدم، ننصحك بتتبُّع تعديلات البيانات باستخدام الفئة PagedList
إذا كان عدد العناصر في القائمة كبيرًا. يمكن لمثيلات PagedList
استخدام إما
LiveData<PagedList>
أو
Observable<List>
لتمرير تحديثات البيانات إلى واجهة المستخدم الخاصة بالتطبيق، ما يقلل من أوقات التحميل
واستخدام الذاكرة. والأفضل من ذلك أنّ استبدال كائن List
بكائن PagedList
في تطبيقك لا يتطلّب إجراء أي تغييرات على بنية واجهة المستخدم أو منطق تعديل البيانات.
ربط مؤشر بيانات بعرض قائمة باستخدام CursorAdapter
قد يستخدم تطبيقك CursorAdapter
لربط البيانات من Cursor
بـ
ListView
. في هذه الحالة، عليك عادةً
نقل البيانات من ListView
إلى
RecyclerView
كحاوية لواجهة المستخدم في قائمة تطبيقك، ثم استبدال المكوِّن Cursor
بالخيار Room أو
PositionalDataSource
، وذلك بناءً على ما إذا كانت مثيلات Cursor
تصل إلى
قاعدة بيانات SQLite.
في بعض الحالات، مثل عند التعامل مع نُسخ Spinner
، يتم تقديم المحوِّل نفسه فقط. ثم تأخذ المكتبة البيانات التي تم تحميلها في ذلك
المحول وتعرض البيانات لك. في هذه الحالات، يمكنك تغيير نوع بيانات المحوِّل إلى LiveData<PagedList>
، ثم لف هذه القائمة في عنصر ArrayAdapter
قبل محاولة تضخيم فئة مكتبة إلى هذه العناصر في واجهة المستخدم.
تحميل المحتوى بشكل غير متزامن باستخدام AsyncListUfill
إذا كنت تستخدم عناصر
AsyncListUtil
لتحميل مجموعات من المعلومات وعرضها بشكل غير متزامن، تتيح لك "مكتبة تسجيل الصفحات"
تحميل البيانات بسهولة أكبر:
- ليس من الضروري أن تكون بياناتك موضعية. تتيح لك مكتبة الترحيل تحميل البيانات مباشرة من الخلفية باستخدام المفاتيح التي توفرها الشبكة.
- قد تكون بياناتك كبيرة بشكل لا يُحصى. باستخدام مكتبة التقسيم، يمكنك تحميل البيانات إلى الصفحات حتى نفاد أي بيانات.
- يمكنك تتبُّع بياناتك بسهولة أكبر. يمكن لمكتبة الترحيل تقديم بياناتك التي يحتفظ بها ViewModel لتطبيقك في بنية بيانات يمكن ملاحظتها.
أمثلة على قاعدة البيانات
تعرض مقتطفات الرمز التالية عدة طرق محتملة لعمل جميع القطع معًا.
مراقبة البيانات المقسّمة على صفحات باستخدام بيانات LiveData
يعرض مقتطف الرمز التالي جميع الأجزاء وهي تعمل معًا. عند إضافة فعاليات الحفلات الموسيقية أو إزالتها أو تغييرها في قاعدة البيانات، يتم تعديل المحتوى في RecyclerView
تلقائيًا وبكفاءة:
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); } }; }
مراقبة البيانات المقسّمة على صفحات باستخدام RxJava2
إذا كنت تفضّل استخدام RxJava2 بدلاً من LiveData
، يمكنك بدلاً من ذلك إنشاء كائن Observable
أو 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(); } }
يمكنك بعد ذلك بدء مراقبة البيانات وإيقافها باستخدام الرمز البرمجي في المقتطف التالي:
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(); } }
يتم استخدام رمز ConcertDao
وConcertAdapter
للحلّ المستند إلى RxJava2 نفسه والحلّ المستند إلى LiveData
.
تقديم ملاحظات
يُرجى مشاركة ملاحظاتك وآرائك معنا من خلال الموارد التالية:
- أداة تتبّع المشاكل
- يمكنك الإبلاغ عن المشاكل حتى نتمكّن من إصلاح الأخطاء.
مراجع إضافية
لمعرفة المزيد حول مكتبة الترحيل، يمكنك الرجوع إلى الموارد التالية.
عيّنات
- نموذج الانتقال إلى صفحات مكونات البنية المعمارية لنظام التشغيل Android
- تقسيم البيانات باستخدام نموذج الشبكة
الدروس التطبيقية حول الترميز
الفيديوهات الطويلة
- Android Jetpack: إدارة القوائم اللانهائية باستخدام RecyclerView وPaging (مؤتمر Google I/O لعام 2018)
- Android Jetpack: الانتقال من الصفحات
أفلام مُقترَحة لك
- ملاحظة: يتم عرض نص الرابط عند إيقاف JavaScript.
- نقل البيانات إلى الصفحة 3
- عرض قوائم مقسّمة إلى صفحات
- جمع البيانات المقسّمة على صفحات