عامل التحميل

تم إيقاف أدوات التحميل نهائيًا بدءًا من نظام التشغيل Android 9 (المستوى 28 من واجهة برمجة التطبيقات). ننصح باستخدام مزيج من كائنات ViewModel وLiveData معًا للتعامل مع تحميل البيانات أثناء معالجة دورات حياة Activity وFragment. تتيح لك هذه النماذج الاطّلاع على التغييرات التي تطرأ على الإعدادات، مثل برامج التحميل، ولكن باستخدام رمز نموذجي أقل. توفِّر LiveData طريقة تراعي مراحل النشاط لتحميل البيانات والتي يمكنك إعادة استخدامها في نماذج طرق عرض متعدّدة. يمكنك أيضًا دمج LiveData باستخدام MediatorLiveData. يمكن استخدام أي طلبات بحث يمكن ملاحظتها، مثل تلك الواردة من قاعدة بيانات غرفة، لملاحظة التغييرات في البيانات.

يكون ViewModel وLiveData أيضًا متاحَين في الحالات التي لا يمكنك فيها الوصول إلى LoaderManager، مثلاً في Service. يوفر استخدام الاثنين معًا طريقة سهلة للوصول إلى البيانات التي يحتاجها تطبيقك دون الحاجة إلى التعامل مع دورة حياة واجهة المستخدم. لمزيد من المعلومات حول LiveData، يمكنك الاطّلاع على نظرة عامة على LiveData. لمزيد من المعلومات حول ViewModel، يمكنك الاطّلاع على نظرة عامة على ViewModel.

تتيح لك Loader API تحميل بيانات من موفّر محتوى أو مصدر بيانات آخر لعرضها في FragmentActivity أو Fragment.

بدون برامج التحميل، تتضمن بعض المشكلات التي قد تواجهها ما يلي:

  • في حال جلب البيانات مباشرةً في النشاط أو الجزء، يعاني المستخدمون من عدم استجابة المستخدمين بسبب تنفيذ طلبات بحث يُحتمل أن تكون بطيئة من سلسلة محادثات واجهة المستخدم.
  • في حال جلب البيانات من سلسلة محادثات أخرى، مثلاً من خلال AsyncTask، ستكون مسؤولاً عن إدارة كل من سلسلة المحادثات هذه وسلسلة محادثات واجهة المستخدم من خلال أنشطة مختلفة أو أحداث مراحل نشاط مجزأة، مثل onDestroy() والتغييرات على الإعدادات.

تحل اللواحق هذه المشكلات وتتضمن مزايا أخرى:

  • تعمل برامج التحميل على سلاسل محادثات منفصلة لمنع ظهور واجهة مستخدم بطيئة أو لا تستجيب.
  • تبسّط أدوات التحميل إدارة سلسلة المحادثات من خلال توفير طرق لرد الاتصال عند وقوع الأحداث.
  • تظل برامج التحميل وتحفظ النتائج في ذاكرة التخزين المؤقت في جميع تغييرات الإعدادات لمنع طلبات البحث المكررة.
  • يمكن لبرامج التحميل استخدام أداة مراقبة لرصد التغييرات في مصدر البيانات الأساسي. على سبيل المثال، تسجِّل CursorLoader تلقائيًا ContentObserver لتشغيل عملية إعادة تحميل عند تغيير البيانات.

ملخص واجهة برمجة تطبيقات Loader

هناك فئات وواجهات متعددة يمكن أن يتم تضمينها عند استخدام أدوات التحميل في أحد التطبيقات. ويتم تلخيصها في الجدول التالي:

الصف/الواجهة الوصف
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 الفرعية أو استخدام إحدى الفئات الفرعية التالية المضمَّنة لتبسيط عملية التنفيذ:

توضح لك الأقسام التالية كيفية استخدام هذه الفئات والواجهات في أحد التطبيقات.

استخدام برامج التحميل في أحد التطبيقات

يصف هذا القسم كيفية استخدام برامج التحميل في تطبيق Android. عادةً ما يتضمن التطبيق الذي يستخدم برامج التحميل ما يلي:

تشغيل أداة تحميل

يدير LoaderManager مثيل Loader واحدًا أو أكثر ضمن FragmentActivity أو Fragment. ولا يمكن استخدام أكثر من LoaderManager واحد لكل نشاط أو جزء.

عادةً ما يتم ضبط Loader في طريقة onCreate() الخاصة بالنشاط أو طريقة onCreate() للجزء. يمكنك القيام بذلك على النحو التالي:

Kotlin

supportLoaderManager.initLoader(0, null, this)

Java

// 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 التالي إلى إعادة تشغيل برنامج التحميل عندما يتغير طلب بحث المستخدم. يجب إعادة تشغيل برنامج التحميل لتتمكن من استخدام عامل تصفية البحث الذي تمت مراجعته لإجراء استعلام جديد.

Kotlin

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
}

Java

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 الطرق التالية:

  • onLoadFinished(): يستدعي هذا الإجراء عند انتهاء تحميل برنامج تحميل تم إنشاؤه مسبقًا.
  • onLoaderReset(): يستدعي ذلك عند إعادة ضبط برنامج تحميل تم إنشاؤه مسبقًا، ما يجعل بياناته غير متوفرة.

يتم وصف هذه الطرق بمزيد من التفصيل في الأقسام التالية.

onCreateLoader

عند محاولة الوصول إلى أداة تحميل، مثلاً من خلال initLoader()، فإنّها تتحقّق ممّا إذا كانت أداة التحميل متوفّرة من خلال المعرّف. وإذا لم يحدث ذلك، سيتم تفعيل طريقة LoaderManager.LoaderCallbacks onCreateLoader(). هذا هو المكان الذي تقوم فيه بإنشاء تحميل جديد. وعادةً ما تكون هذه السمة CursorLoader، ولكن يمكنك تنفيذ فئتك الفرعية Loader الخاصة بك.

في المثال التالي، تنشئ طريقة معاودة الاتصال onCreateLoader() CursorLoader باستخدام طريقة الدالة الإنشائية، والتي تتطلب مجموعة كاملة من المعلومات اللازمة لإجراء طلب بحث في ContentProvider. على وجه التحديد، يحتاج إلى ما يلي:

  • uri: معرّف الموارد المنتظم (URI) للمحتوى المطلوب استرداده.
  • projection: قائمة بالأعمدة المطلوب عرضها يؤدي تمرير null إلى عرض جميع الأعمدة، وهي طريقة غير فعّالة.
  • sselect: فلتر يحدد الصفوف المراد عرضها، ويكون منسَّقًا كفقرة SQL WHERE (باستثناء WHERE). يؤدي تمرير null إلى عرض جميع الصفوف لعنوان URI محدّد.
  • sselectArgs: إذا أدرجت ?s في التحديد، سيتم استبدالها بالقيم من sselectArgs بالترتيب الذي تظهر به في التحديد. يتم ربط القيم كسلاسل.
  • sortOrder: كيفية ترتيب الصفوف، منسقة كعبارة SQL ORDER BY (باستثناء عبارة ORDER BY نفسها). يستخدم تمرير null ترتيب الفرز التلقائي، والذي قد يكون غير مرتب.

Kotlin

// 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")
}

Java

// 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");
}

onLoadEnded

يتم استدعاء هذه الطريقة عند انتهاء تحميل برنامج التحميل الذي تم إنشاؤه مسبقًا. ومن المضمون طلب هذه الطريقة قبل إصدار البيانات الأخيرة التي تم توفيرها لهذا التحميل. في هذه المرحلة، قم بإزالة كل استخدام البيانات القديمة، حيث سيتم إصدارها. ولكن لا تطلق البيانات بنفسك، فإن التحميل يمتلكها ويهتم بها.

يقوم برنامج التحميل بتحرير البيانات بمجرد معرفة أن التطبيق لم يعد يستخدمها. على سبيل المثال، إذا كانت البيانات هي مؤشر من CursorLoader، لا تطلب close() عليها بنفسك. في حال وضع المؤشر في CursorAdapter، استخدِم الإجراء swapCursor() بحيث لا يتم إغلاق Cursor القديم، كما هو موضّح في المثال التالي:

Kotlin

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)
}

Java

// 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:

Kotlin

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)
}

Java

// 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.

Kotlin

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)
    }
}

Java

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: مثال على كيفية استخدام التقييد لتقليل عدد طلبات البحث التي يجريها موفّر المحتوى عندما تتغير بياناته.