لودرها

لودرها از Android 9 منسوخ شده اند (سطح API 28). گزینه پیشنهادی برای رسیدگی به بارگذاری داده ها در حین مدیریت چرخه حیات Activity و Fragment ، استفاده از ترکیبی از اشیاء ViewModel و LiveData است. مدل‌های مشاهده مانند لودرها از تغییرات پیکربندی جان سالم به در می‌برند، اما با کد دیگ بخار کمتر. LiveData یک روش آگاه از چرخه حیات برای بارگیری داده ها ارائه می دهد که می توانید در مدل های چند نما دوباره از آن استفاده کنید. همچنین می توانید LiveData با استفاده از MediatorLiveData ترکیب کنید. هر پرس و جوی قابل مشاهده، مانند مواردی که از پایگاه داده اتاق می آید، می تواند برای مشاهده تغییرات داده ها استفاده شود.

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

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

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

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

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

  • لودرها روی رشته‌های جداگانه اجرا می‌شوند تا از رابط کاربری کند یا پاسخگو جلوگیری کنند.
  • لودرها مدیریت thread را با ارائه روش های برگشت به تماس در هنگام وقوع رویدادها ساده می کنند.
  • لودرها باقی می مانند و نتایج را در سراسر تغییرات پیکربندی ذخیره می کنند تا از درخواست های تکراری جلوگیری شود.
  • لودرها می توانند یک ناظر را برای نظارت بر تغییرات در منبع داده زیربنایی پیاده سازی کنند. به عنوان مثال، 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 را زیر کلاس قرار دهید یا از یکی از زیر کلاس‌های داخلی زیر برای ساده‌سازی پیاده‌سازی استفاده کنید:

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

از لودرها در برنامه استفاده کنید

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

یک لودر راه اندازی کنید

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() تضمین می کند که یک لودر مقداردهی اولیه و فعال است. دو نتیجه ممکن دارد:

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

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

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

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

یک لودر را دوباره راه اندازی کنید

هنگامی که از 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() ، بررسی می کند که آیا لودر مشخص شده توسط ID وجود دارد یا خیر. اگر این کار را نکرد، متد LoaderManager.LoaderCallbacks onCreateLoader() را راه‌اندازی می‌کند. اینجا جایی است که شما یک لودر جدید ایجاد می کنید. به طور معمول این یک CursorLoader است، اما شما می توانید زیر کلاس Loader خود را پیاده سازی کنید.

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

  • uri : URI برای بازیابی محتوا.
  • طرح ریزی : فهرستی از ستون هایی که باید برگردانده شوند. ارسال null تمام ستون ها را برمی گرداند که ناکارآمد است.
  • Selection : فیلتری که مشخص می کند کدام ردیف ها باید برگردانده شوند، که به عنوان یک عبارت SQL WHERE قالب بندی شده است (به استثنای خود WHERE). پاس کردن null تمام سطرهای URI داده شده را برمی گرداند.
  • SelectArgs : اگر ?s را در انتخاب قرار دهید، به ترتیبی که در انتخاب ظاهر می شوند با مقادیر SelectArgs جایگزین می شوند. مقادیر به صورت رشته محدود می شوند.
  • 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 : نمونه ای از نحوه استفاده از throttling برای کاهش تعداد پرس و جوهایی که ارائه دهنده محتوا هنگام تغییر داده هایش انجام می دهد.
،

لودرها از Android 9 منسوخ شده اند (سطح API 28). گزینه پیشنهادی برای رسیدگی به بارگذاری داده ها در حین مدیریت چرخه حیات Activity و Fragment ، استفاده از ترکیبی از اشیاء ViewModel و LiveData است. مدل‌های مشاهده مانند لودرها از تغییرات پیکربندی جان سالم به در می‌برند، اما با کد دیگ بخار کمتر. LiveData یک روش آگاه از چرخه حیات برای بارگیری داده ها ارائه می دهد که می توانید در مدل های چند نما دوباره از آن استفاده کنید. همچنین می توانید LiveData با استفاده از MediatorLiveData ترکیب کنید. هر پرس و جوی قابل مشاهده، مانند مواردی که از پایگاه داده اتاق می آید، می تواند برای مشاهده تغییرات داده ها استفاده شود.

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

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

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

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

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

  • لودرها روی رشته‌های جداگانه اجرا می‌شوند تا از رابط کاربری کند یا پاسخگو جلوگیری کنند.
  • لودرها مدیریت thread را با ارائه روش های برگشت به تماس در هنگام رخ دادن رویدادها ساده می کنند.
  • لودرها باقی می مانند و نتایج را در سراسر تغییرات پیکربندی ذخیره می کنند تا از درخواست های تکراری جلوگیری شود.
  • لودرها می توانند یک ناظر را برای نظارت بر تغییرات در منبع داده زیربنایی پیاده سازی کنند. به عنوان مثال، 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 را زیر کلاس قرار دهید یا از یکی از زیر کلاس‌های داخلی زیر برای ساده‌سازی پیاده‌سازی استفاده کنید:

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

از لودرها در برنامه استفاده کنید

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

یک لودر راه اندازی کنید

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() تضمین می کند که یک لودر مقداردهی اولیه و فعال است. دو نتیجه ممکن دارد:

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

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

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

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

یک لودر را دوباره راه اندازی کنید

هنگامی که از 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() ، بررسی می کند که آیا لودر مشخص شده توسط ID وجود دارد یا خیر. اگر این کار را نکرد، متد LoaderManager.LoaderCallbacks onCreateLoader() را راه‌اندازی می‌کند. اینجا جایی است که شما یک لودر جدید ایجاد می کنید. به طور معمول این یک CursorLoader است، اما شما می توانید زیر کلاس Loader خود را پیاده سازی کنید.

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

  • uri : URI برای بازیابی محتوا.
  • طرح ریزی : فهرستی از ستون هایی که باید برگردانده شوند. ارسال null تمام ستون ها را برمی گرداند که ناکارآمد است.
  • Selection : فیلتری که مشخص می کند کدام ردیف ها باید برگردانده شوند، که به عنوان یک عبارت SQL WHERE قالب بندی شده است (به استثنای خود WHERE). پاس کردن null تمام سطرهای URI داده شده را برمی گرداند.
  • SelectArgs : اگر ?s را در انتخاب قرار دهید، به ترتیبی که در انتخاب ظاهر می شوند با مقادیر SelectArgs جایگزین می شوند. مقادیر به صورت رشته محدود می شوند.
  • 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 : نمونه ای از نحوه استفاده از throttling برای کاهش تعداد پرس و جوهایی که ارائه دهنده محتوا هنگام تغییر داده هایش انجام می دهد.