二十六、了解加载器

本章着眼于通过推荐的加载器机制从数据源加载数据。加载器的 API 被设计用来处理通过活动和片段加载数据的两个问题。

首先是活动的不确定性,活动可能部分或全部隐藏,由于设备轮换而重新启动,或者由于内存不足而在后台从内存中删除。这些事件称为活动生命周期事件。任何检索数据的代码都必须与活动生命周期事件协调工作。在 3.0 (API 11)中引入加载器之前,这是通过管理的游标来处理的。这种机制现已停产,取而代之的是加载器。

在活动和片段中加载数据的第二个问题是,数据访问在主线程上可能需要更长时间,从而导致应用不响应(ANR)消息。加载器通过在工作线程上完成工作,并提供对活动和片段的回调来响应数据获取的异步特性,从而解决了这个问题。

了解加载器的架构

加载器使得异步加载活动或片段中的数据变得容易。多个加载器,每个都有自己的数据集,可以与一个活动或一个片段相关联。加载器还监控其数据源,并在数据内容发生变化时提供新的结果。配置更改后重新创建时,加载程序会自动重新连接到先前检索的数据结构,就像光标 。由于前一个游标未被销毁,因此不会重新查询数据。

当我们在本章中讨论加载器时,加载器的所有方面都适用于活动和片段,除非我们从现在开始另外指明。

每个活动都使用一个单独的 LoaderManager 对象来管理与该活动相关的加载器。一旦加载器向加载器管理器注册, LoaderManager 将促进必要的回调,以 a)创建并初始化加载器,b)当加载器完成加载数据时读取数据,以及 c)当加载器由于不再需要活动而即将被销毁时关闭资源。 LoaderManager 对你是隐藏的,你通过回调和 LoaderManager 公共 API 来使用它。LoaderManager 的创建由活动控制。LoaderManager 几乎就像是活动本身不可或缺的一部分。

已注册的加载器负责使用其数据源,并且还负责使用加载器管理器读取数据并将结果发送回加载器管理器。然后, LoaderManager 将调用数据准备好的活动的回调。加载器还负责暂停数据访问或监控数据更改,或者与加载器管理器一起工作,以了解活动生命周期事件并对其做出反应。

虽然您可以通过扩展加载器 API 为您的特定数据需求从头开始编写加载器,但是您通常使用已经在 SDK 中实现的加载器。大多数加载器都扩展了 AsyncTaskLoader ,它提供了在工作线程上完成工作的基本能力,从而释放了主线程。当工作线程返回数据时, LoaderManager 将调用活动的主回调,表明数据已在主线程上准备好。

这些预构建加载器中最常用的是光标加载器 。随着光标加载器的出现,使用加载器变得非常非常简单,只需要几行代码。这是因为所有的细节都隐藏在 LoaderManager 、 Loader 、 AsyncTaskLoader 和 CursorLoader 之后。

列出基本加载器 API 类

清单 26-1 列出了加载器 API 中涉及的关键类。

清单 26-1 。Android Loader API 关键参与类

LoaderManager
LoaderManager.LoaderCallbacks
Loader
AsyncTaskLoader
CursorLoader

最常用的 API 是 LoaderManager。LoaderCallbacks 和 CursorLoader 。但是,让我们简单介绍一下这些类。

每个活动有一个 LoaderManager 对象。这个对象定义了加载器应该如何工作的协议。因此 LoaderManager 是与活动相关的加载器的协调器。 LoaderManager 与活动的交互是通过 LoaderManager 实现的。LoaderCallbacks 。在这些加载器回调中,加载器通过 LoaderManager 向您提供数据,并期望您与活动进行交互。

Loader 类定义了如果想设计自己的加载器必须遵守的协议。 AsyncTaskLoader 就是一个例子,它在工作线程上以异步方式实现加载器协议。通常,AsyncTaskLoader 是实现大多数加载器的基类。 CursorLoader 是这个 AsyncTaskLoader 的一个实现,它知道如何从内容供应器那里加载光标。如果一个人正在实现自己的加载器,那么理解来自 LoaderManager 的与加载器的所有交互都发生在主线程上是很重要的。甚至由活动实现的 LoaderManager 回调也发生在主线程上。

展示加载器

我们现在将通过实现一个简单的单页应用(图 26-1 )向您展示如何使用加载器,该应用从 Android 设备上的联系人提供者数据库中加载联系人。这个应用是开发 Android 活动的典型方式。您甚至可以将这个示例项目用作起始应用模板。

9781430246800_Fig26-01.jpg

图 26-1 。通过加载器加载的联系人过滤列表

我们希望图 26-1 中的活动展现出以下特征 : 1)它应该显示设备上的所有联系人;b)它应该异步检索数据;c)在检索数据时,活动应该显示进度条视图,而不是列表视图;d)在成功检索数据时,代码应该用填充的列表视图替换进度视图;e)活动应提供一种搜索机制,以筛选必要的联系人;f)当旋转设备时,它应该再次显示联系人,而无需向联系人内容提供者进行重新查询;g)代码应该允许我们看到回调的顺序以及活动生命周期回调。

我们将首先展示活动的源代码,然后解释每个部分。到本章结束时,你会清楚地了解加载器是如何工作的,以及如何在你的代码中使用它们。至此,清单 26-2 显示了图 26-1 的活动代码。请注意清单 26-2 中的代码依赖于这里提供的一些资源。其中一些字符串资源你可以在图 26-1 中看到,但是对于其他的和这里没有包括的代码,请查看可下载的项目。和往常一样,这里给出的代码对于当前的主题来说已经足够了。

清单 26-2 。用加载器加载数据的活动

public class TestLoadersActivity
extends      MonitoredListActivity //very simple class to log activity callbacks
implements   LoaderManager.LoaderCallbacks<Cursor> //Loader Manager callbacks
             ,OnQueryTextListener //Search text callback to filter contacts
{
   private static final String tag = "TestLoadersActivity";

   //Adapter for displaying the list's data
   //Initialized to null cursor in onCreate and set on the list
   //Use it in later callbacks to swap cursor
   //This is reinitialized to null cursor when rotation occurs
    SimpleCursorAdapter mAdapter;

    //Search filter working with OnQueryTextListener
    String mCurFilter;

    //Contacts columns that we will retrieve
    static final String[] PROJECTION = new String[] {ContactsContract.Data._ID,
            ContactsContract.Data.DISPLAY_NAME};

    //select criteria for the contacts URI
    static final String SELECTION = "((" +
            ContactsContract.Data.DISPLAY_NAME + " NOTNULL) AND (" +
            ContactsContract.Data.DISPLAY_NAME + " != '' ))";

    public TestLoadersActivity()  {
       super(tag);
    }
    @Override
    protected void onCreate(Bundle savedInstanceState)  {
       super.onCreate(savedInstanceState);
       this.setContentView(R.layout.test_loaders_activity_layout);

       //Initialize the adapter
       this.mAdapter = createEmptyAdapter();
       this.setListAdapter(mAdapter);

       //Hide the listview and show the progress bar
       this.showProgressbar();

       //Initialize a loader for an id of 0
       getLoaderManager().initLoader(0, null, this);
    }
    //Create a simple list adapter with a null cursor
    //The good cursor will come later in the loader callback
    private SimpleCursorAdapter createEmptyAdapter() {
       // For the cursor adapter, specify which columns go into which views
        String[] fromColumns = {ContactsContract.Data.DISPLAY_NAME};
        int[] toViews = {android.R.id.text1}; // The TextView in simple_list_item_1
        //Return the cursor
       return new SimpleCursorAdapter(this,
             android.R.layout.simple_list_item_1,
             null, //cursor
             fromColumns,
             toViews);
    }
   //This is a LoaderManager callback. Return a properly constructed CursorLoader
   //This gets called only if the loader does not previously exist.
   //This means this method will not be called on rotation because
   //a previous loader with this ID is already available and initialized.
   //This also gets called when the loader is "restarted" by calling
   //LoaderManager.restartLoader()
   @Override
   public Loader<Cursor> onCreateLoader(int id, Bundle args) {
      Log.d(tag,"onCreateLoader for loader id:" + id);
      Uri baseUri;
      if (mCurFilter != null) {
          baseUri = Uri.withAppendedPath(ContactsContract.Contacts.CONTENT_FILTER_URI,
                 Uri.encode(mCurFilter));
      } else {
          baseUri = Contacts.CONTENT_URI;
      }
      String[] selectionArgs = null;
      String sortOrder = null;
      return new CursorLoader(this, baseUri,
            PROJECTION, SELECTION, selectionArgs, sortOrder);
   }
   //This is a LoaderManager callback. Use the data here.
   //This gets called when he loader finishes. Called on the main thread.
   //Can be called multiple times as the data changes underneath.
   //Also gets called after rotation with out requerying the cursor.
   @Override
   public void onLoadFinished(Loader<Cursor> loader, Cursor cursor) {
       Log.d(tag,"onLoadFinished for loader id:" + loader.getId());
       Log.d(tag,"Number of contacts found:" + cursor.getCount());
        this.hideProgressbar();
        this.mAdapter.swapCursor(cursor);
   }
   //This is a LoaderManager callback. Remove any references to this data.
   //This gets called when the loader is destroyed like when activity is done.
   //FYI - this does NOT get called because of loader "restart"
   //This can be seen as a "destructor" for the loader.
   @Override
   public void onLoaderReset(Loader<Cursor> loader) {
      Log.d(tag,"onLoaderReset for loader id:" + loader.getId());
      this.showProgressbar();
      this.mAdapter.swapCursor(null);
   }
    @Override
    public boolean onCreateOptionsMenu(Menu menu) {
        // 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(this);
        sv.setOnQueryTextListener(this);
        item.setActionView(sv);
        return true;
    }
    //This is a Searchview callback. Restart the loader.
    //This gets called when user enters new search text.
    //Call LoaderManager.restartLoader to trigger the onCreateLoader
    @Override
    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.
        mCurFilter = !TextUtils.isEmpty(newText) ? newText : null;
        Log.d(tag,"Restarting the loader");
        getLoaderManager().restartLoader(0, null, this);
        return true;
    }
    @Override
    public boolean onQueryTextSubmit(String query) {
        return true;
    }
    private void showProgressbar() {
       //show progress bar
       View pbar = this.getProgressbar();
       pbar.setVisibility(View.VISIBLE);
       //hide listview
       this.getListView().setVisibility(View.GONE);
       findViewById(android.R.id.empty).setVisibility(View.GONE);
    }
    private void hideProgressbar()  {
       //show progress bar
       View pbar = this.getProgressbar();
       pbar.setVisibility(View.GONE);
       //hide listview
       this.getListView().setVisibility(View.VISIBLE);
    }
    private View getProgressbar()  {
       return findViewById(R.id.tla_pbar);
    }
}//eof-class

在我们向您展示了清单 26-3 中活动代码的支持布局之后,我们将解释清单 26-2 中的每一部分。清单 26-3 中的布局应该能阐明图 26-1 中的视图(请注意,这里没有包括一些资源,但是可以在 apress.com/9781430246800 的可下载文件中找到)。

清单 26-3 。加载器的典型列表活动布局

<?xml version="1.0" encoding="utf-8"?>
<!--
*********************************************
* /res/layout/test_loaders_activity_layout.xml
* corresponding activity: TestLoadersActicity.java
* prefix: tla_ (Used for prefixing unique identifiers)
*
* Use:
*    Demonstrate loading a cursor using loaders
* Structure:
*    Header message: text view (tla_header)
*    ListView Heading, ListView (fixed)
*    ProgressBar (To show when data is being fetched)
*    Empty View (To show when the list is empty): ProgressBar
*    Footer: text view (tla_footer)
************************************************
-->
<LinearLayout xmlns:android="[http://schemas.android.com/apk/res/android](http://schemas.android.com/apk/res/android)"
    android:orientation="vertical"
    android:layout_width="match_parent"  android:layout_height="match_parent"
    android:paddingLeft="2dp"  android:paddingRight="2dp">
    <!--  Header and Main documentation text -->
    <TextView android:id="@+id/tla_header"
        android:layout_width="match_parent"  android:layout_height="wrap_content"
        android:background="@drawable/box2"
        android:layout_marginTop="4dp" android:padding="8dp"
        android:text="@string/tla_header"/>
    <!--  Heading for the list view -->
    <TextView android:id="@+id/tla_listview_heading"
        android:layout_width="match_parent"    android:layout_height="wrap_content"
        android:background="@color/gray"
        android:layout_marginTop="4dp"  android:padding="8dp"
        android:textColor="@color/black" style="@android:style/TextAppearance.Medium"
        android:text="List of Contacts"/>
    <!--  ListView used by the ListActivity. Uses a standard id needed by a list view -->
    <!--  Fix the height of the listview in a production setting -->
    <ListView android:id="@android:id/list"
        android:layout_width="match_parent"  android:layout_height="wrap_content"
        android:background="@drawable/box2"
        android:layout_marginTop="4dp" android:layout_marginBottom="4dp"
        android:drawSelectorOnTop="false"/>
    <!--  ProgressBar: To show and hide the progress bar as loaders load data -->
    <ProgressBar android:id="@+id/tla_pbar"
        android:layout_width="match_parent" android:layout_height="wrap_content"
        android:layout_gravity="center"
        android:indeterminate="true"/>
     <!--  Empty List: Uses a standard id needed by a list view -->
     <TextView android:id="@android:id/empty"
        android:layout_width="match_parent" android:layout_height="wrap_content"
        android:visibility="gone"
        android:layout_marginTop="4dp"  android:layout_marginBottom="4dp"
        android:padding="8dp"
        android:text="No Contacts to Match the Criteria"/>
     <!--  Footer: Additional documentation text and the footer-->
     <TextView android:id="@+id/tla_footer"
        android:layout_width="match_parent"  android:layout_height="wrap_content"
        android:background="@drawable/box2"  android:padding="8dp"
        android:text="@string/tla_footer"/>
</LinearLayout>

现在让我们来理解清单 26-2 中的代码。我们将通过一系列步骤来解释这段代码,您可以按照这些步骤使用加载器进行编码。让我们从步骤 1 开始,这里需要扩展活动以支持 LoaderManager 回调。

步骤 1:准备加载数据的活动

使用加载器加载数据所需的代码非常少,因为大部分工作是由光标加载器完成的。您需要做的第一件事是让您的活动扩展 LoaderManager。Loader 回调<光标> 并实现所需的三个方法: onCreateLoader() 、 onLoadFinished() 和 onLoaderReset() 。你可以在清单 26-2 中看到。通过实现这个接口,您已经使活动能够通过这三个回调成为 LoaderManager 事件的接收者。

步骤 2:初始化加载程序

接下来,您必须告诉活动您想要一个加载器对象来加载数据。这是通过在活动的 onCreate() 方法期间注册并初始化一个加载器来完成的,如清单 26-4 所示。你也可以在清单 26-3 的 onCreate() 中,在整个代码的上下文中看到这一点。

清单 26-4 。初始化加载程序

int loaderid = 0; Bundle argBundle = null;
LoaderCallbacks<Cursor> loaderCallbacks = this; //this activity itself
getLoaderManager().initLoader(loaderid, argBundle, loaderCallbacks);

loaderid 参数是开发人员在该活动的上下文中分配的唯一编号,用于将该加载程序与在该活动中注册的其他加载程序进行唯一标识。注意,在这里的例子中,我们只使用了一个加载器。

第二个 argsBundle 参数 用于在需要时将附加参数传递给 onCreateLoader() 回调。这种“参数捆绑”方法遵循 Android 中许多托管组件中不同工厂对象构造的常见模式。活动、片段和加载器都是这种模式的例子。

第三个参数, loaderCallbacks ,是对 LoaderManager 所需回调的实现的引用。在清单 26-2清单 26-4 中,活动本身正在扮演这个角色,所以我们将引用活动的这个变量作为参数值传递给。

一旦加载器被注册和初始化,加载器管理器将在必要时调度对 onCreateLoader() 回调的调用。如果先前对 onCreateLoader() 进行了调用,并且对应于该加载器 ID 的加载器对象可用,则不会触发方法 onCreateLoader() 。如前所述,例外情况是开发人员通过调用 loader manager . restart loader()来覆盖此行为。稍后,当我们谈到提供基于搜索的过滤功能来定位联系人的子选择时,您将看到对这个调用的解释。

探究列表活动的结构

图 26-1 中的 ListActivity 正在通过 setContentView() 扩展一个带有自定义布局的内容视图的列表活动。这给了我们更多的灵活性,可以在活动上放置除列表视图之外的其他控件。例如,我们提供了一个页眉视图、一个页脚视图以及一个进度条来显示我们正在获取数据。由 ListActivity 设置的唯一约束是使用保留的@ Android:id/listview 来命名控件,以标识列表活动将使用的列表视图。除了 listview ID 之外,如果列表为空,我们还可以提供列表活动使用的视图。该视图由预定义的 ID@ Android:ID/empty 标识。

使用数据的异步加载

加载器异步加载数据。因此,我们在 activity . oncreate()中增加了一个责任,隐藏列表视图并显示进度指示器,直到列表数据准备好。为此,我们在清单 26-3 的布局中有一个 ProgressBar 组件。在 Activity.onCreate() 方法中,我们设置了布局的初始状态,以便隐藏列表视图并显示进度条。该功能编码在清单 26-2 中的 showProgressbar() 方法中。在同一个清单 26-2 中,当数据准备好时,我们调用 hideProgressbar() 来隐藏进度条并显示已填充的列表视图,如果没有数据,则显示空列表视图。

步骤 3:实现 onCreateLoader()

onCreateLoader() 由加载程序的初始化触发。你可以在清单 26-2 中看到这个方法的签名和实现。该方法为相应的加载器 ID 构造一个加载器对象,该加载器 ID 是通过调用 Loader manager . init Loader()进行初始化而传入的。该方法还接收在加载器初始化期间为该加载器 ID 提供的参数包。

该方法将一个正确的类型化的(通过 Java 泛型)加载器对象返回给加载器管理器。在我们的例子中,这个类型是加载器<光标> 。 LoaderManager 缓存 Loader 对象并将重用它。这很有用,因为当设备旋转并且加载器由于 Activity.onCreate() , LoaderManager 识别加载器 ID 和现有加载器的存在。然后 LoaderManager 将不会触发对 onCreateLoader() 的重复调用。但是,如果活动要意识到加载器的输入数据已经改变,活动代码可以调用 loader manager . restart loader(),这将再次触发对 onLoaderCreate() 的调用。在这种情况下, LoaderManager 将首先销毁旧的加载程序,并使用 onLoaderCreate() 返回的新加载程序。 LoaderManager 确实保证了旧的加载程序会一直存在,直到新的加载程序被创建并可用。

onCreateLoader() 方法可以完全访问活动的局部变量。因此它可以以任何可以想到的方式使用它们来构造所需的装载器。在光标加载器的情况下,这种构造仅限于光标加载器的构造器可用的参数,该构造器是专门为允许来自 Android 内容供应器的光标而构建的。

在我们的例子中,我们使用了 contacts 内容供应器提供的内容 URIs。关于如何使用内容 URIs 从内容供应器数据源中检索光标,请参考第 25 章。这非常简单:只需指出您想要从中获取数据的 URI,按照 contacts 内容提供者可用的文档,在该 URI 上将过滤器字符串作为参数或路径段提供,指定您想要的列,将 where 子句指定为字符串,并构造 CursorLoader 。

步骤 4:实现 onLoadFinished()

一旦光标加载器返回到加载器管理器中,光标加载器将被指示在工作线程上开始工作,主线程将继续 UI 杂务。稍后,当数据准备好时,调用这个方法 onLoadFinished() 。

这个方法可以被多次调用。当来自内容供应器的数据发生变化时,因为光标加载器已经向数据源注册了自己,所以它将被警告。 CursorLoader 然后会再次触发 onLoadFinished() 。

在 onLoadFinished() 方法中,您需要做的就是交换列表适配器持有的数据光标。列表适配器最初是用空游标初始化的。与填充的光标交换将在列表视图中显示新数据。由于我们在 Activity.onCreate() 中隐藏了 listview,我们需要显示 listview 并隐藏进度条。随后,当数据发生变化时,我们可以继续用新游标替换旧游标。这些更改将自动反映在列表视图中。

当设备旋转时,会发生一些事情。将再次调用 Activity.onCreate() 。这将把列表光标设置为空,并隐藏列表视图。 Activity.onCreate() 中的代码也会再次初始化加载器。对 LoaderManager 进行编程,这样重复初始化是无害的。onCreateLoader () 不会被调用。将不会重新查询光标。然而, onLoadFinished() 再次被调用,这是我们需要打破的难题:首先将数据初始化为 null,并想知道如果我们不重新查询,它将如何以及何时被填充。随着 onLoadFinished() 在循环中再次被调用,我们能够移除进度条,显示列表视图,并从空光标中交换有效光标。所有作品。是的,这是偷偷摸摸和迂回,但它的工作。

步骤 5:实现 onLoaderReset()

当以前注册的加载程序不再需要并因此被销毁时,调用这个回调函数。当一个活动由于后退按钮而被销毁或者被代码明确指示完成时,就会发生这种情况。在这种情况下,这个回调允许关闭不再需要的资源或引用。然而,重要的是不要关闭光标,因为它们由相应的加载器管理,并且将由框架为您关闭。这可能意味着 loader manager . restart loader()可能导致调用 onLoaderReset() ,因为旧加载器的参数不再有效。但是测试表明事实并非如此。方法 loader manager . restart loader()不会触发对方法 onLoaderReset() 的调用。 onLoaderReset() 方法仅在加载程序被不再需要的活动主动销毁时调用。您也可以通过调用 loader manager . destroy loader(loader id)显式指示 LoaderManager 销毁加载程序。

使用加载器搜索

我们将在示例应用中使用 search 来演示加载器的动态特性。我们在菜单上附加了一个搜索视图。您可以在清单 26-2 中的方法 onCreateOptionsMenu()中看到这一点。这里,我们将一个 SearchView 附加到菜单上,并在 SearchView 中提供新文本时,将该活动作为对 SearchView 的回调。在清单 26-2 的方法 onQueryTextchange()中处理 SearchView 回调。

在 onQueryTextChange() 方法中,我们获取新的搜索文本并设置局部变量 mCurFilter 。然后我们调用 loader manager . restart loader(),使用与 loader manager . initialize loader()相同的参数。这将再次触发 onCreateLoader() ,然后它将使用 mCurFilter 来改变 CursorLoader 的参数,从而产生一个新的光标。这个新的光标将替换 onLoadFinished() 方法中的旧光标。

了解 LoaderManager 回调的顺序

因为 Android 编程很大程度上是基于事件的,所以知道事件回调的顺序很重要。为了帮助您理解 LoaderManager 回调的时间,我们在示例程序中添加了日志消息。以下是一些显示回调顺序的结果。

清单 26-5 显示了第一次创建活动时的调用顺序。

清单 26-5 。活动创建时的加载程序回调

Application.onCreate()
Activity.onCreate()
  LoaderManager.LoaderCallbacks.onCreateLoader()
  Activity.onStart()
  Activity.onResume()
  LoaderManager.LoaderCallbacks.onLoadFinished()

当搜索视图通过回调触发一个新的搜索标准时,回调的顺序如清单 26-6 所示。

清单 26-6 。由 RestartLoader 触发的新搜索标准的加载程序回调

RestartLoader //log message from onQueryTextChange
LoaderManager.LoaderCallbacks.onCreateLoader()
LoaderManager.LoaderCallbacks.onLoadFinished()
//Notice, no call to onLoaderReset()

清单 26-7 显示了配置变更的调用顺序。

清单 26-7 。配置更改时的加载程序回调

Application:config changed
Activity: onCreate
  Activity.onStart
  [No call to the onCreateLoader]
  LoaderManager.LoaderCallbacks.onLoadFinished
  [optionally if searchview has text in it]
    SearchView.onQueryChangeText
    RestartLoader //just a log message
    LoaderManager.LoaderCallbacks.onCreateLoader
    LoaderManager.LoaderCallbacks.onLoadFinished

清单 26-8 显示了当活动中的那些动作结果被销毁时,导航返回或导航回家的回调顺序。

清单 26-8 。当活动被销毁时加载器回调

ActivityonStop()
Activity.onDestroy()
LoaderManager.LoaderCallbacks.onLoaderReset() //Notice this method is called

编写自定义加载程序

正如您在游标加载器、加载器中看到的,它们是特定于数据源的。因此,您可能需要编写自己的加载程序。很可能你需要从 AsyncTaskLoader 中派生出来,并使用 Loader 协议规定的原则和契约对其进行专门化。参见 SDK 文档中的加载器类以获得更多细节。您也可以使用 CursorLoader 源代码作为编写自己的加载器的指南。源代码可以从网上的多个来源获得(你可以谷歌一下),或者作为 Android 源代码下载的一部分。

资源

以下是本章所涵盖主题的附加资源:

摘要

从时间的角度以及处理活动和片段的托管生命周期的能力来看,加载器对于从数据源加载数据是必不可少的。在本章中,您已经看到了使用加载器从内容供应器加载数据是多么容易。生成的代码响应迅速,能够处理配置更改,并且简单。