لودرها از 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.LoaderCallbacks | این رابط شامل متدهای برگشتی است که هنگام رخ دادن رویدادهای لودر فراخوانی می شوند. رابط سه روش برگشت به تماس را تعریف می کند:
initLoader() یا restartLoader() را فرا میخوانید ثبت میشود. |
Loader | لودرها بارگذاری داده ها را انجام می دهند. این کلاس انتزاعی است و به عنوان کلاس پایه برای همه لودرها عمل می کند. میتوانید مستقیماً Loader را زیر کلاس قرار دهید یا از یکی از زیر کلاسهای داخلی زیر برای سادهسازی پیادهسازی استفاده کنید:
|
بخش های زیر نحوه استفاده از این کلاس ها و رابط ها را در یک برنامه به شما نشان می دهد.
از لودرها در برنامه استفاده کنید
در این بخش نحوه استفاده از لودر در یک برنامه اندرویدی توضیح داده شده است. برنامهای که از لودر استفاده میکند معمولاً شامل موارد زیر است:
- یک
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()
تضمین می کند که یک لودر مقداردهی اولیه و فعال است. دو نتیجه ممکن دارد:
- اگر لودر مشخص شده توسط 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.LoaderCallbacks | این رابط شامل متدهای برگشتی است که هنگام رخ دادن رویدادهای لودر فراخوانی می شوند. رابط سه روش برگشت به تماس را تعریف می کند:
initLoader() یا restartLoader() را فراخوانی می کنید ثبت می شود. |
Loader | لودرها بارگذاری داده ها را انجام می دهند. این کلاس انتزاعی است و به عنوان کلاس پایه برای همه لودرها عمل می کند. میتوانید مستقیماً Loader را زیر کلاس قرار دهید یا از یکی از زیر کلاسهای داخلی زیر برای سادهسازی پیادهسازی استفاده کنید:
|
بخش های زیر نحوه استفاده از این کلاس ها و رابط ها را در یک برنامه به شما نشان می دهد.
از لودرها در برنامه استفاده کنید
در این بخش نحوه استفاده از لودر در یک برنامه اندرویدی توضیح داده شده است. برنامهای که از لودر استفاده میکند معمولاً شامل موارد زیر است:
- یک
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()
تضمین می کند که یک لودر مقداردهی اولیه و فعال است. دو نتیجه ممکن دارد:
- اگر لودر مشخص شده توسط 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 برای کاهش تعداد پرس و جوهایی که ارائه دهنده محتوا هنگام تغییر داده هایش انجام می دهد.