بارکننده‌ها

بارکننده‌ها از Android 9 (سطح میانای برنامه‌سازی کاربردی ۲۸) منسوخ شده‌اند. گزینه توصیه‌شده برای مدیریت بار کردن داده‌ها هنگام مدیریت چرخه‌های حیات Activity و Fragment، استفاده از ترکیبی از اشیاء ViewModel و LiveData است. مدل‌های View از تغییرات پیکربندی، مانند بارکننده‌ها، جان سالم به در می‌برند، اما با کد متن استاندارد کمتر. ‫LiveData روشی آگاه از چرخه حیات برای بار کردن داده‌ها ارائه می‌دهد که می‌توانید در چندین مدل نمایشی از آن استفاده مجدد کنید. همچنین می‌توانید LiveData را بااستفاده از MediatorLiveData ترکیب کنید. هر پُرسمان قابل‌مشاهده‌ای، مثل پُرسمان‌های پایگاه داده Room، می‌تواند برای مشاهده تغییرات داده‌ها استفاده شود.

ViewModel و LiveData در شرایطی که به LoaderManager دسترسی ندارید، مثلاً در Service، نیز دردسترس هستند. استفاده از این دو به‌صورت هم‌زمان روشی آسان برای دسترسی به داده‌های موردنیاز برنامه‌تان فراهم می‌کند، بدون اینکه لازم باشد با چرخه حیات واسط کاربری سروکار داشته باشید. برای کسب اطلاعات بیشتر درباره LiveData، نمای کلی LiveData را ببینید. برای کسب اطلاعات بیشتر درباره ViewModel، به نمای کلی ViewModel مراجعه کنید.

‫Loader API به شما امکان می‌دهد داده‌ها را از ارائه‌دهنده محتوا یا منبع داده دیگری برای نمایش در FragmentActivity یا Fragment بار کنید.

بدون بارکننده‌ها، برخی‌از مشکلاتی که ممکن است با آن‌ها مواجه شوید شامل موارد زیر است:

  • اگر داده‌ها را مستقیماً در فعالیت یا قطعه واکشی کنید، کاربران شما به‌دلیل اجرای پُرسمان‌های احتمالاً کُند از رشته میانای کاربر، با مشکل عدم پاسخ‌گویی مواجه می‌شوند.
  • اگر داده‌ها را از رشته دیگری واکشی کنید، شاید با AsyncTask، در این صورت مسئول مدیریت آن رشته و رشته میانای کاربر ازطریق رویدادهای مختلف چرخه حیات فعالیت یا قطعه هستید، مانند onDestroy() و تغییرات پیکربندی.

بارکننده‌ها این مشکلات را حل می‌کنند و مزایای دیگری نیز دارند:

  • بارکننده‌ها در رشته‌های جداگانه اجرا می‌شوند تا از کند شدن یا عدم پاسخگویی واسط کاربر جلوگیری شود.
  • بارکننده‌ها با ارائه روش‌های فراخوانی هنگام وقوع رویدادها، مدیریت رشته را ساده می‌کنند.
  • بارکننده‌ها نتایج را در تغییرات پیکربندی حفظ و ذخیره می‌کنند تا از تکرار پرسمان‌ها جلوگیری شود.
  • بارکننده‌ها می‌توانند ناظری را برای نظارت بر تغییرات در منبع داده‌های زیربنایی پیاده‌سازی کنند. برای مثال، CursorLoader به‌طور خودکار ContentObserver را ثبت می‌کند تا درصورت تغییر داده‌ها، بار کردن مجدد را راه‌اندازی کند.

خلاصه Loader API

هنگام استفاده از بارکننده‌ها در برنامه، ممکن است چندین کلاس و میانای مختلف درگیر باشند. این موارد در جدول زیر خلاصه شده‌اند:

کلاس/میانا شرح
LoaderManager کلاس انتزاعی مرتبط با FragmentActivity یا Fragment برای مدیریت یک یا چند نمونه Loader. فقط یک LoaderManager برای هر فعالیت یا قطعه وجود دارد، اما یک LoaderManager می‌تواند چندین بارکننده را مدیریت کند.

برای دریافت LoaderManager، getSupportLoaderManager() را از فعالیت یا قطعه فراخوانی کنید.

برای شروع بار کردن داده از بارکننده، initLoader() یا restartLoader() را فراخوانی کنید. سیستم به‌طور خودکار تعیین می‌کند که آیا بارکننده‌ای با همان شناسه عدد صحیح ازقبل وجود دارد یا نه و بارکننده جدیدی ایجاد می‌کند یا از بارکننده موجود دوباره استفاده می‌کند.

LoaderManager.LoaderCallbacks این میانای حاوی روش‌های فراخوانی است که هنگام وقوع رویدادهای بارکننده فراخوانده می‌شوند. این میانجی سه روش فراخوانی را تعریف می‌کند:
  • ‫onCreateLoader(int, Bundle): وقتی فراخوانی می‌شود که سیستم نیاز به ایجاد بارکننده جدیدی داشته باشد. در کدتان، شیء Loader ایجاد کنید و آن را به سیستم برگردانید.
  • onLoadFinished(Loader<D>, D): وقتی فراخوانی می‌شود که بارکننده بار کردن داده‌ها را تمام کرده باشد. معمولاً داده‌ها را در کدتان به کاربر نشان می‌دهید.
  • onLoaderReset(Loader<D>): وقتی بارکننده‌ای که قبلاً ایجاد شده است بازنشانی می‌شود، وقتی destroyLoader(int) را فراخوانی می‌کنید، یا وقتی فعالیت یا قطعه ازبین می‌رود و داده‌های آن دردسترس نیست، فراخوانی می‌شود. در کدتان، هرگونه ارجاع به داده‌های بارکننده را بردارید.
فعالیت یا قطعه شما معمولاً این میانای کاربری را پیاده‌سازی می‌کند و هنگام فراخوانی initLoader() یا restartLoader() ثبت می‌شود.
Loader بارکننده‌ها بار کردن داده‌ها را انجام می‌دهند. این کلاس انتزاعی است و به‌عنوان کلاس پایه برای همه بارکننده‌ها عمل می‌کند. می‌توانید مستقیماً از Loader زیرکلاس بسازید یا از یکی از زیرکلاس‌های داخلی زیر برای ساده‌سازی پیاده‌سازی استفاده کنید:
  • AsyncTaskLoader: بارکننده‌ای انتزاعی که AsyncTask را برای انجام عملیات بار کردن در رشته‌ای جداگانه ارائه می‌دهد.
  • CursorLoader: زیرکلاس واقعی از AsyncTaskLoader برای بار کردن ناهمگام داده‌ها از ContentProvider. این ویژگی یک ContentResolver را پُرسمان می‌کند و یک Cursor برمی‌گرداند.

بخش‌های زیر نحوه استفاده از این کلاس‌ها و میاناهای کاربردی را در برنامه نشان می‌دهد.

استفاده از بارکننده‌ها در برنامه

این بخش نحوه استفاده از بارکننده‌ها را در برنامه Android شرح می‌دهد. برنامه‌ای که از بارکننده‌ها استفاده می‌کند معمولاً شامل موارد زیر است:

  • ‫FragmentActivity یا Fragment.
  • نمونه‌ای از LoaderManager.
  • CursorLoader برای بار کردن داده‌های پشتیبانی‌شده توسط ContentProvider. به‌جای آن، می‌توانید زیرکلاس خودتان را از Loader یا AsyncTaskLoader پیاده‌سازی کنید تا داده‌ها را از منبع دیگری بار کنید.
  • پیاده‌سازی برای LoaderManager.LoaderCallbacks. اینجا جایی است که بارکننده‌های جدید ایجاد می‌کنید و مراجع خود را به بارکننده‌های موجود مدیریت می‌کنید.
  • روشی برای نمایش داده‌های بارکننده، مثل SimpleCursorAdapter.
  • منبع داده، مثل ContentProvider، هنگام استفاده از CursorLoader.

شروع بارکننده

LoaderManager یک یا چند نمونه Loader را در FragmentActivity یا Fragment مدیریت می‌کند. فقط یک LoaderManager برای هر فعالیت یا قطعه وجود دارد.

معمولاً Loader را در روش onCreate() فعالیت یا روش onCreate() قطعه مقداردهی اولیه می‌کنید. این کار را به این صورت انجام می‌دهید:

کاتلین

supportLoaderManager.initLoader(0, null, this)

جاوا

// Prepare the loader.  Either re-connect with an existing one,
// or start a new one.
getSupportLoaderManager().initLoader(0, null, this);

روش initLoader() پارامترهای زیر را می‌گیرد:

  • شناسه یکتایی که بارکننده را شناسایی می‌کند. در این مثال، شناسه 0 است.
  • متغیرهای مستقل اختیاری برای ارائه به بارکننده در ساخت (null در این مثال).
  • پیاده‌سازی LoaderManager.LoaderCallbacks که LoaderManager برای گزارش رویدادهای بارکننده با آن تماس می‌گیرد. در این مثال، کلاس محلی میانای LoaderManager.LoaderCallbacks را پیاده‌سازی می‌کند، بنابراین ارجاعی به خودش، this، ارسال می‌کند.

فراخوانی initLoader() تضمین می‌کند که بارکننده مقداردهی اولیه و فعال شده است. این آزمایش دو نتیجه احتمالی دارد:

  • اگر بارکننده‌ای که با شناسه مشخص شده است ازقبل وجود داشته باشد، از آخرین بارکننده ایجادشده مجدداً استفاده می‌شود.
  • اگر بارکننده مشخص‌شده با شناسه وجود نداشته باشد، initLoader() روش LoaderManager.LoaderCallbacks onCreateLoader() را راه‌اندازی می‌کند. اینجا جایی است که کد را برای نمونه‌سازی و برگرداندن بارکننده جدید پیاده‌سازی می‌کنید. برای بحث بیشتر، بخش مربوط به onCreateLoader را ببینید.

در هر دو مورد، پیاده‌سازی LoaderManager.LoaderCallbacks ارائه‌شده با بارکننده مرتبط است و وقتی وضعیت بارکننده تغییر می‌کند، فراخوانی می‌شود. اگر در زمان این فراخوانی، فراخواننده در وضعیت شروع‌شده باشد و بارکننده درخواستی ازقبل وجود داشته باشد و داده‌هایش را تولید کرده باشد، سیستم بلافاصله onLoadFinished() را درطول initLoader() فراخوانی می‌کند. باید برای این اتفاق آماده باشید. برای بحث بیشتر درباره این فراخوانی، به بخش مربوط به onLoadFinished مراجعه کنید.

روش initLoader() Loader ایجادشده را برمی‌گرداند، اما لازم نیست مرجعی برای آن ضبط کنید. LoaderManager عمر بارکننده را به‌طور خودکار مدیریت می‌کند. LoaderManager بار کردن را درصورت لزوم شروع و متوقف می‌کند و وضعیت بارکننده و محتوای مرتبط با آن را حفظ می‌کند.

همان‌طور که از این موضوع برمی‌آید، شما به‌ندرت مستقیماً با بارکننده‌ها تعامل دارید. معمولاً از روش‌های LoaderManager.LoaderCallbacks برای مداخله در فرایند بار کردن هنگام وقوع رویدادهای خاص استفاده می‌کنید. برای بحث بیشتر درباره این موضوع، به بخش استفاده از فراخوان‌های LoaderManager مراجعه کنید.

بازراه‌اندازی بارکننده

وقتی از initLoader() استفاده می‌کنید، همان‌طور که در بخش قبلی نشان داده شده است، از بارکننده‌ای موجود با شناسه مشخص‌شده (درصورت وجود) استفاده می‌کند. اگر وجود نداشته باشد، یکی ایجاد می‌کند. اما گاهی می‌خواهید داده‌های قدیمی‌تان را دور بیندازید و از نو شروع کنید.

برای دور انداختن داده‌های قدیمی، از restartLoader() استفاده کنید. برای مثال، پیاده‌سازی زیر از SearchView.OnQueryTextListener وقتی پُرسمان کاربر تغییر می‌کند، بارکننده را بازراه‌اندازی می‌کند. بارکننده باید بازراه‌اندازی شود تا بتواند از فیلتر جستجوی اصلاح‌شده برای انجام پُرسمان جدید استفاده کند.

کاتلین

fun onQueryTextChanged(newText: String?): Boolean {
    // Called when the action bar search text has changed.  Update
    // the search filter and restart the loader to do a new query
    // with this filter.
    curFilter = if (newText?.isNotEmpty() == true) newText else null
    supportLoaderManager.restartLoader(0, null, this)
    return true
}

جاوا

public boolean onQueryTextChanged(String newText) {
    // Called when the action bar search text has changed.  Update
    // the search filter, and restart the loader to do a new query
    // with this filter.
    curFilter = !TextUtils.isEmpty(newText) ? newText : null;
    getSupportLoaderManager().restartLoader(0, null, this);
    return true;
}

استفاده از فراخوان‌های LoaderManager

‫LoaderManager.LoaderCallbacks یک میانای بازخوانی است که به کارخواه اجازه می‌دهد با LoaderManager تعامل داشته باشد.

انتظار می‌رود بارکننده‌ها، به‌ویژه CursorLoader، داده‌هایشان را پس‌از متوقف شدن حفظ کنند. این کار به برنامه‌ها اجازه می‌دهد داده‌هایشان را در سراسر روش‌های onStop() و onStart() فعالیت یا قطعه حفظ کنند تا وقتی کاربران به برنامه برمی‌گردند، مجبور نباشند منتظر بارگیری مجدد داده‌ها بمانند.

از روش‌های LoaderManager.LoaderCallbacks استفاده می‌کنید تا بدانید چه زمانی باید بارکننده جدیدی ایجاد کنید و به برنامه بگویید چه زمانی باید استفاده از داده‌های بارکننده را متوقف کند.

‫LoaderManager.LoaderCallbacks شامل این روش‌ها است:

  • onCreateLoader(): نمونه‌سازی می‌کند و Loader جدیدی را برای شناسه داده‌شده برمی‌گرداند.
  • onLoadFinished(): وقتی فراخوانی می‌شود که بارکننده قبلاً ایجادشده بارگیری‌اش را تمام کرده باشد.
  • onLoaderReset(): زمانی فراخوانی می‌شود که بارکننده‌ای که قبلاً ایجاد شده است درحال بازنشانی باشد، بنابراین داده‌های آن دردسترس نخواهد بود.

این روش‌ها با جزئیات بیشتر در بخش‌های زیر شرح داده شده‌اند.

onCreateLoader

وقتی می‌خواهید به بارکننده‌ای دسترسی پیدا کنید، مثلاً ازطریق initLoader()، بررسی می‌کند که آیا بارکننده مشخص‌شده با شناسه وجود دارد یا نه. اگر این‌طور نباشد، روش LoaderManager.LoaderCallbacks onCreateLoader() را راه‌اندازی می‌کند. اینجا جایی است که بارکننده جدیدی ایجاد می‌کنید. معمولاً این یک CursorLoader است، اما می‌توانید زیرکلاس Loader خودتان را پیاده‌سازی کنید.

در مثال زیر، روش فراخوانی onCreateLoader() یک CursorLoader بااستفاده از روش سازنده خود ایجاد می‌کند که به مجموعه کامل اطلاعات موردنیاز برای انجام پرسمان به ContentProvider نیاز دارد. به‌طور خاص، به موارد زیر نیاز دارد:

  • uri: شناسه منبع یکسان (URI) برای محتوایی که باید بازیابی شود.
  • projection: فهرستی از ستون‌هایی که باید برگردانده شوند. ارسال null همه ستون‌ها را برمی‌گرداند که ناکارآمد است.
  • selection: فیلتری که مشخص می‌کند کدام ردیف‌ها برگردانده شوند، که به‌صورت عبارت SQL WHERE قالب‌بندی شده است (به‌استثنای خود WHERE). ارسال null همه ردیف‌ها را برای URI داده‌شده برمی‌گرداند.
  • selectionArgs: اگر در انتخاب ?s را اضافه کنید، با مقادیر selectionArgs به ترتیبی که در انتخاب ظاهر می‌شوند جایگزین می‌شوند. مقادیر به‌صورت رشته محدود شده‌اند.
  • sortOrder: نحوه مرتب کردن ردیف‌ها، قالب‌بندی‌شده به‌عنوان عبارت SQL ORDER BY (به‌استثنای خود ORDER BY). با ارسال null از ترتیب مرتب‌سازی پیش‌فرض استفاده می‌شود که ممکن است نامرتب باشد.

کاتلین

// If non-null, this is the current filter the user has provided.
private var curFilter: String? = null
...
override fun onCreateLoader(id: Int, args: Bundle?): Loader<Cursor> {
    // This is called when a new Loader needs to be created.  This
    // sample only has one Loader, so we don't care about the ID.
    // First, pick the base URI to use depending on whether we are
    // currently filtering.
    val baseUri: Uri = if (curFilter != null) {
        Uri.withAppendedPath(ContactsContract.Contacts.CONTENT_URI, Uri.encode(curFilter))
    } else {
        ContactsContract.Contacts.CONTENT_URI
    }

    // Now create and return a CursorLoader that will take care of
    // creating a Cursor for the data being displayed.
    val select: String = "((${Contacts.DISPLAY_NAME} NOTNULL) AND (" +
            "${Contacts.HAS_PHONE_NUMBER}=1) AND (" +
            "${Contacts.DISPLAY_NAME} != ''))"
    return (activity as? Context)?.let { context ->
        CursorLoader(
                context,
                baseUri,
                CONTACTS_SUMMARY_PROJECTION,
                select,
                null,
                "${Contacts.DISPLAY_NAME} COLLATE LOCALIZED ASC"
        )
    } ?: throw Exception("Activity cannot be null")
}

جاوا

// If non-null, this is the current filter the user has provided.
String curFilter;
...
public Loader<Cursor> onCreateLoader(int id, Bundle args) {
    // This is called when a new Loader needs to be created.  This
    // sample only has one Loader, so we don't care about the ID.
    // First, pick the base URI to use depending on whether we are
    // currently filtering.
    Uri baseUri;
    if (curFilter != null) {
        baseUri = Uri.withAppendedPath(Contacts.CONTENT_FILTER_URI,
                  Uri.encode(curFilter));
    } else {
        baseUri = Contacts.CONTENT_URI;
    }

    // Now create and return a CursorLoader that will take care of
    // creating a Cursor for the data being displayed.
    String select = "((" + Contacts.DISPLAY_NAME + " NOTNULL) AND ("
            + Contacts.HAS_PHONE_NUMBER + "=1) AND ("
            + Contacts.DISPLAY_NAME + " != '' ))";
    return new CursorLoader(getActivity(), baseUri,
            CONTACTS_SUMMARY_PROJECTION, select, null,
            Contacts.DISPLAY_NAME + " COLLATE LOCALIZED ASC");
}

onLoadFinished

این روش زمانی فراخوانی می‌شود که بارکننده‌ای که قبلاً ایجاد شده است بارگیری خود را تمام کند. تضمین می‌شود که این روش قبل‌از انتشار آخرین داده‌ای که برای این بارکننده ارائه شده است فراخوانی شود. در این مرحله، همه موارد استفاده از داده‌های قدیمی را بردارید، چون قرار است منتشر شود. اما خودتان داده‌ها را منتشر نکنید—بارکننده مالک آن است و این کار را انجام می‌دهد.

وقتی بارکننده متوجه شود که برنامه دیگر از داده‌ها استفاده نمی‌کند، آن‌ها را رها می‌کند. برای مثال، اگر داده‌ها مکان‌نمایی از CursorLoader باشد، خودتان close() را روی آن فراخوانی نکنید. اگر مکان‌نما در CursorAdapter قرار داده می‌شود، از روش swapCursor() استفاده کنید تا Cursor قدیمی بسته نشود، همان‌طور که در مثال زیر نشان داده شده است:

کاتلین

private lateinit var adapter: SimpleCursorAdapter
...
override fun onLoadFinished(loader: Loader<Cursor>, data: Cursor?) {
    // Swap the new cursor in. (The framework will take care of closing the
    // old cursor once we return.)
    adapter.swapCursor(data)
}

جاوا

// This is the Adapter being used to display the list's data.
SimpleCursorAdapter adapter;
...
public void onLoadFinished(Loader<Cursor> loader, Cursor data) {
    // Swap the new cursor in. (The framework will take care of closing the
    // old cursor once we return.)
    adapter.swapCursor(data);
}

onLoaderReset

این روش زمانی فراخوانی می‌شود که بارکننده‌ای که قبلاً ایجاد شده است درحال بازنشانی باشد، بنابراین داده‌های آن دردسترس نخواهد بود. این فراخوانی برگشتی به شما امکان می‌دهد متوجه شوید چه زمانی داده‌ها درحال انتشار هستند تا بتوانید ارجاع خود را به آن بردارید.

این پیاده‌سازی swapCursor() را با مقدار null فراخوانی می‌کند:

کاتلین

private lateinit var adapter: SimpleCursorAdapter
...
override fun onLoaderReset(loader: Loader<Cursor>) {
    // This is called when the last Cursor provided to onLoadFinished()
    // above is about to be closed.  We need to make sure we are no
    // longer using it.
    adapter.swapCursor(null)
}

جاوا

// This is the Adapter being used to display the list's data.
SimpleCursorAdapter adapter;
...
public void onLoaderReset(Loader<Cursor> loader) {
    // This is called when the last Cursor provided to onLoadFinished()
    // above is about to be closed.  We need to make sure we are no
    // longer using it.
    adapter.swapCursor(null);
}

مثال

به‌عنوان مثال، در اینجا پیاده‌سازی کامل Fragment را می‌بینید که ListView حاوی نتایج پُرسمان دربرابر ارائه‌دهنده محتوای مخاطبین را نمایش می‌دهد. این ویژگی از CursorLoader برای مدیریت پُرسمان در رساننده استفاده می‌کند.

چون این نمونه از برنامه‌ای برای دسترسی به مخاطبین کاربر است، مانیفست آن باید شامل اجازه READ_CONTACTS باشد.

کاتلین

private val CONTACTS_SUMMARY_PROJECTION: Array<String> = arrayOf(
        Contacts._ID,
        Contacts.DISPLAY_NAME,
        Contacts.CONTACT_STATUS,
        Contacts.CONTACT_PRESENCE,
        Contacts.PHOTO_ID,
        Contacts.LOOKUP_KEY
)


class CursorLoaderListFragment :
        ListFragment(),
        SearchView.OnQueryTextListener,
        LoaderManager.LoaderCallbacks<Cursor> {

    // This is the Adapter being used to display the list's data.
    private lateinit var mAdapter: SimpleCursorAdapter

    // If non-null, this is the current filter the user has provided.
    private var curFilter: String? = null

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)

        // Prepare the loader.  Either re-connect with an existing one,
        // or start a new one.
        loaderManager.initLoader(0, null, this)
    }

    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        super.onViewCreated(view, savedInstanceState)

        // Give some text to display if there is no data.  In a real
        // application, this would come from a resource.
        setEmptyText("No phone numbers")

        // We have a menu item to show in action bar.
        setHasOptionsMenu(true)

        // Create an empty adapter we will use to display the loaded data.
        mAdapter = SimpleCursorAdapter(activity,
                android.R.layout.simple_list_item_2,
                null,
                arrayOf(Contacts.DISPLAY_NAME, Contacts.CONTACT_STATUS),
                intArrayOf(android.R.id.text1, android.R.id.text2),
                0
        )
        listAdapter = mAdapter
    }

    override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) {
        // Place an action bar item for searching.
        menu.add("Search").apply {
            setIcon(android.R.drawable.ic_menu_search)
            setShowAsAction(MenuItem.SHOW_AS_ACTION_IF_ROOM)
            actionView = SearchView(activity).apply {
                setOnQueryTextListener(this@CursorLoaderListFragment)
            }
        }
    }

    override fun onQueryTextChange(newText: String?): Boolean {
        // Called when the action bar search text has changed.  Update
        // the search filter, and restart the loader to do a new query
        // with this filter.
        curFilter = if (newText?.isNotEmpty() == true) newText else null
        loaderManager.restartLoader(0, null, this)
        return true
    }

    override fun onQueryTextSubmit(query: String): Boolean {
        // Don't care about this.
        return true
    }

    override fun onListItemClick(l: ListView, v: View, position: Int, id: Long) {
        // Insert desired behavior here.
        Log.i("FragmentComplexList", "Item clicked: $id")
    }

    override fun onCreateLoader(id: Int, args: Bundle?): Loader<Cursor> {
        // This is called when a new Loader needs to be created.  This
        // sample only has one Loader, so we don't care about the ID.
        // First, pick the base URI to use depending on whether we are
        // currently filtering.
        val baseUri: Uri = if (curFilter != null) {
            Uri.withAppendedPath(Contacts.CONTENT_URI, Uri.encode(curFilter))
        } else {
            Contacts.CONTENT_URI
        }

        // Now create and return a CursorLoader that will take care of
        // creating a Cursor for the data being displayed.
        val select: String = "((${Contacts.DISPLAY_NAME} NOTNULL) AND (" +
                "${Contacts.HAS_PHONE_NUMBER}=1) AND (" +
                "${Contacts.DISPLAY_NAME} != ''))"
        return (activity as? Context)?.let { context ->
            CursorLoader(
                    context,
                    baseUri,
                    CONTACTS_SUMMARY_PROJECTION,
                    select,
                    null,
                    "${Contacts.DISPLAY_NAME} COLLATE LOCALIZED ASC"
            )
        } ?: throw Exception("Activity cannot be null")
    }

    override fun onLoadFinished(loader: Loader<Cursor>, data: Cursor) {
        // Swap the new cursor in.  (The framework will take care of closing the
        // old cursor once we return.)
        mAdapter.swapCursor(data)
    }

    override fun onLoaderReset(loader: Loader<Cursor>) {
        // This is called when the last Cursor provided to onLoadFinished()
        // above is about to be closed.  We need to make sure we are no
        // longer using it.
        mAdapter.swapCursor(null)
    }
}

جاوا

public static class CursorLoaderListFragment extends ListFragment
        implements OnQueryTextListener, LoaderManager.LoaderCallbacks<Cursor> {

    // This is the Adapter being used to display the list's data.
    SimpleCursorAdapter mAdapter;

    // If non-null, this is the current filter the user has provided.
    String curFilter;

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

        // Prepare the loader.  Either re-connect with an existing one,
        // or start a new one.
        getLoaderManager().initLoader(0, null, this);
    }

    @Override public void onViewCreated(@NonNull View view, Bundle savedInstanceState) {
        super.onViewCreated(view, savedInstanceState);

        // Give some text to display if there is no data.  In a real
        // application, this would come from a resource.
        setEmptyText("No phone numbers");

        // We have a menu item to show in action bar.
        setHasOptionsMenu(true);

        // Create an empty adapter we will use to display the loaded data.
        mAdapter = new SimpleCursorAdapter(getActivity(),
                android.R.layout.simple_list_item_2, null,
                new String[] { Contacts.DISPLAY_NAME, Contacts.CONTACT_STATUS },
                new int[] { android.R.id.text1, android.R.id.text2 }, 0);
        setListAdapter(mAdapter);
    }

    @Override public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
        // Place an action bar item for searching.
        MenuItem item = menu.add("Search");
        item.setIcon(android.R.drawable.ic_menu_search);
        item.setShowAsAction(MenuItem.SHOW_AS_ACTION_IF_ROOM);
        SearchView sv = new SearchView(getActivity());
        sv.setOnQueryTextListener(this);
        item.setActionView(sv);
    }

    public boolean onQueryTextChange(String newText) {
        // Called when the action bar search text has changed.  Update
        // the search filter, and restart the loader to do a new query
        // with this filter.
        curFilter = !TextUtils.isEmpty(newText) ? newText : null;
        getLoaderManager().restartLoader(0, null, this);
        return true;
    }

    @Override public boolean onQueryTextSubmit(String query) {
        // Don't care about this.
        return true;
    }

    @Override public void onListItemClick(ListView l, View v, int position, long id) {
        // Insert desired behavior here.
        Log.i("FragmentComplexList", "Item clicked: " + id);
    }

    // These are the Contacts rows that we will retrieve.
    static final String[] CONTACTS_SUMMARY_PROJECTION = new String[] {
        Contacts._ID,
        Contacts.DISPLAY_NAME,
        Contacts.CONTACT_STATUS,
        Contacts.CONTACT_PRESENCE,
        Contacts.PHOTO_ID,
        Contacts.LOOKUP_KEY,
    };
    public Loader<Cursor> onCreateLoader(int id, Bundle args) {
        // This is called when a new Loader needs to be created.  This
        // sample only has one Loader, so we don't care about the ID.
        // First, pick the base URI to use depending on whether we are
        // currently filtering.
        Uri baseUri;
        if (curFilter != null) {
            baseUri = Uri.withAppendedPath(Contacts.CONTENT_FILTER_URI,
                    Uri.encode(curFilter));
        } else {
            baseUri = Contacts.CONTENT_URI;
        }

        // Now create and return a CursorLoader that will take care of
        // creating a Cursor for the data being displayed.
        String select = "((" + Contacts.DISPLAY_NAME + " NOTNULL) AND ("
                + Contacts.HAS_PHONE_NUMBER + "=1) AND ("
                + Contacts.DISPLAY_NAME + " != '' ))";
        return new CursorLoader(getActivity(), baseUri,
                CONTACTS_SUMMARY_PROJECTION, select, null,
                Contacts.DISPLAY_NAME + " COLLATE LOCALIZED ASC");
    }

    public void onLoadFinished(Loader<Cursor> loader, Cursor data) {
        // Swap the new cursor in.  (The framework will take care of closing the
        // old cursor once we return.)
        mAdapter.swapCursor(data);
    }

    public void onLoaderReset(Loader<Cursor> loader) {
        // This is called when the last Cursor provided to onLoadFinished()
        // above is about to be closed.  We need to make sure we are no
        // longer using it.
        mAdapter.swapCursor(null);
    }
}

مثال‌های بیشتر

نمونه‌های زیر نحوه استفاده از بارکننده‌ها را نشان می‌دهند:

  • LoaderCursor: نسخه کامل قطعه قبلی.
  • بازیابی فهرست مخاطبین: راهنمای گام‌به‌گام که از CursorLoader برای بازیابی داده‌ها از ارائه‌دهنده مخاطبین استفاده می‌کند.
  • LoaderThrottle: نمونه‌ای از نحوه استفاده از محدودسازی برای کاهش تعداد پُرسمان‌هایی که ارائه‌دهنده محتوا هنگام تغییر داده‌هایش انجام می‌دهد.