ローダ

ローダは Android 9(API レベル 28)で非推奨になりました。推奨されるオプション ActivityFragment のライフサイクルを処理しながらデータの読み込みに対応するには、 ViewModel オブジェクトの組み合わせ および LiveData。 ビューモデルはローダーなどの構成変更後も保持されますが、 ボイラープレート コードが減ります。LiveData は、ライフサイクルを意識した方法でデータを読み込むことができ、 複数のビューモデルがあります次を使用して LiveData を組み合わせることもできます。 MediatorLiveData。 観測可能なクエリ Room データベース: 変更を監視するために使用できます。 必要があります。

ViewModelLiveData は、アクセス権がない場合でも利用できます LoaderManager に対して( Service。この 2 つの tandem を使用すると、UI に触れることなく、アプリに必要なデータに簡単にアクセスできます。 説明します。LiveData について詳しくは、以下をご覧ください。 LiveData の概要。詳細情報 ViewModel については、ViewModel の概要をご覧ください。

Loader API を使用すると、 コンテンツ プロバイダ FragmentActivity に表示する他のデータソース または Fragment

ローダを使用しない場合は、次のような問題が発生する可能性があります。

  • アクティビティまたはフラグメントで直接データを取得すると、 パフォーマンスが遅くなる可能性があるため、 UI スレッドから呼び出せるようになりました
  • AsyncTask などを使用して別のスレッドからデータを取得する場合は、 そのスレッドとスレッドを アクティビティやフラグメントのライフサイクル イベントを通じて onDestroy() と設定の変更。

ローダを使用すると、このような問題を解決できるだけでなく、次のようなメリットがあります。

  • UI の遅延や応答を防ぐため、ローダは個別のスレッドで実行されます。
  • ローダは、イベントの発生時にコールバック メソッドを提供することで、スレッド管理を簡素化します。 発生しません
  • ローダは、構成変更後も結果を保持してキャッシュし、 重複クエリを除外できます
  • ローダはオブザーバーを実装して、基盤となるインフラストラクチャの変更を監視できます。 データソースですたとえば、CursorLoader は自動的に ContentObserver を登録して再読み込みをトリガーします 自動的に適用されます。

Loader API の概要

使用時に関係する可能性のあるクラスとインターフェースが複数ある 使用することもできます。次の表に概要を示します。

クラス / インターフェース 説明
LoaderManager FragmentActivity または関連付けられた抽象クラス 1 つまたは複数のアカウントを管理するための Fragment Loader 個のインスタンス。1 つのみ アクティビティまたはフラグメントごとに LoaderManager、ただし LoaderManager は複数のローダーを管理できます。

LoaderManager を取得するには、getSupportLoaderManager() を呼び出します。 呼び出すことができます。

ローダからのデータの読み込みを開始するには、次のいずれかを呼び出します。 initLoader() または restartLoader()。 同じ整数 ID を持つローダーがすでに存在しているかどうかは、 新しいローダを作成するか、既存のローダを再利用します。

LoaderManager.LoaderCallbacks このインターフェースには、Google Cloud の実行時に呼び出されるコールバック メソッドが イベントが発生します。このインターフェースでは、次の 3 つのコールバック メソッドを定義します。 <ph type="x-smartling-placeholder">
    </ph>
  • onCreateLoader(int, Bundle): 新しいローダを作成する必要がある場合に呼び出されます。コードでは、 Loader オブジェクトを作成して、 制御します。
  • onLoadFinished(Loader<D>, D): ローダがデータの読み込みを完了すると呼び出されます。通常は コード内でユーザーにデータを表示します
  • onLoaderReset(Loader<D>): 以前作成したローダをリセットするときに呼び出されます。 destroyLoader(int)、またはアクティビティによって またはフラグメントが破棄され、データが利用不可になります。コードでは、 ローダのデータへの参照をすべて削除します。
で確認できます。 通常、アクティビティやフラグメントはこのインターフェースを実装しており、 呼び出すときに登録されます initLoader() または restartLoader()
Loader ローダはデータの読み込みを実行します。このクラスは抽象クラスで、 すべてのローダーの基本クラスとして使用します。直接サブクラス化し、 Loader、または次のいずれかの組み込み 実装を簡素化できます。 <ph type="x-smartling-placeholder">

以降のセクションでは、これらのリソースの使用方法について説明します。 クラスとインターフェースです。

アプリケーションでローダーを使用する

このセクションでは、Android アプリでローダを使用する方法について説明します。「 ローダーを使用するアプリケーションには、通常、次のものが含まれます。

ローダを開始する

LoaderManager は、FragmentActivity 内で 1 つ以上の Loader インスタンスを管理します。 FragmentLoaderManager は、アクティビティまたはフラグメントごとに 1 つだけです。

通常は アクティビティの onCreate() メソッドまたはフラグメントのLoader 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() メソッドは、 次のパラメータを指定します。

  • ローダを識別する一意の ID。この例では、ID は 0 です。
  • ローダーに提供するオプションの引数です。 構築します(この例では null)。
  • LoaderManager.LoaderCallbacks の実装。これは、 ローダー イベントを報告する LoaderManager 呼び出し。この たとえば、ローカルクラスは LoaderManager.LoaderCallbacks インターフェースを実装しているため、 自体、this

initLoader() 呼び出しにより、ローダーの 初期化されてアクティブになります。これには、次の 2 つの結果が考えられます。

  • ID で指定されたローダーがすでに存在する場合、最後に作成されたローダー 再利用されます。
  • ID で指定されたローダーが存在しない場合、 initLoader() は、 LoaderManager.LoaderCallbacks メソッド onCreateLoader()。 ここでは、新しいローダをインスタンス化して返すコードを実装します。 詳しくは、onCreateLoader に関するセクションをご覧ください。

いずれの場合も、指定された LoaderManager.LoaderCallbacks この実装はローダーに関連付けられており、ローダーが 自動的に検出されます。この通話の時点で、発信者が リクエストされたローダがすでに存在し、そのローダを生成しました その場合、システムは onLoadFinished() を呼び出します。 initLoader() の即時実行です。そのためには、事前の準備が必要です。このコールバックの詳細については、 onLoadFinished

initLoader() メソッドは、作成された Loader を返します。 その参照をキャプチャする必要はありませんLoaderManager が管理 自動的に維持されるようにします。LoaderManager 必要に応じて読み込みを開始および停止し、ローダの状態を維持 および関連するコンテンツです。

このことからわかるとおり ローダーとやり取りすることは 直接渡されます。 読み込みに介入するには、LoaderManager.LoaderCallbacks メソッドを使用します。 自動的に処理されます。このトピックについて詳しくは、LoaderManager コールバックを使用するのセクションをご覧ください。

ローダを再起動する

initLoader() を使用する場合: 指定した ID の既存のローダがある場合は、それを使用します。 存在しない場合は作成します。場合によっては、古いデータを破棄して 最初からやり直してください。

古いデータを破棄するには、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 に含まれるサービス メソッド:

  • onCreateLoader(): は、指定された ID の新しい Loader をインスタンス化して返します。
  • onLoadFinished(): 以前作成したローダが読み込みを完了したときに呼び出されます。
  • onLoaderReset(): 以前に作成されたローダがリセットされたときに呼び出されるため、 データを利用できません。

これらのメソッドについては、以降のセクションで詳しく説明します。

onCreateLoader

ローダにアクセスしようとすると(initLoader() などを介して)アクセスしようとすると、 ローダが存在することを確認します。トリガーされていない場合は、LoaderManager.LoaderCallbacks メソッド onCreateLoader() をトリガーします。この ここから新しいローダを作成します。通常、これは CursorLoader ですが、独自の Loader サブクラスを実装することもできます。

次の例では、onCreateLoader() コールバック メソッドは、そのコンストラクタ メソッドを使用して CursorLoader を作成します。 には、ContentProvider に対するクエリを実行するために必要なすべての情報が必要です。具体的には、次のものが必要です。

  • uri: 取得するコンテンツの URI。
  • projection: 返される列のリスト。合格 null はすべての列を返すため、非効率的です。
  • selection: 返される行を宣言するフィルタ SQL WHERE 句としてフォーマットされます(WHERE 自体は除く)。合格 null は、指定された URI のすべての行を返します。
  • selectionArgs: 選択に ? を含めると、 表示順に、selectionArgs の値に置き換えられます 選択します。値は文字列としてバインドされます。
  • 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");
}

onLoadFinished

このメソッドは、以前に作成したローダが読み込みを完了すると呼び出されます。 このメソッドは、最後のデータがリリースされる前に呼び出されることが保証されています。 指定します。この時点で、 古いデータは解放されますしかし、データは公開しないでください。 ローダが自身を所有し、処理します。

ローダは、アプリケーションが終了したことを認識すると、データを解放します。 できます。たとえば、データが 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);
}

例として、以下を含む ListView を表示する Fragment の完全な実装を次に示します。 連絡先のコンテンツ プロバイダに対するクエリの結果。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);
    }
}

その他の例

次の例は、ローダの使用方法を示しています。

  • <ph type="x-smartling-placeholder"></ph> LoaderCursor: 上記のスニペットの完全なバージョン。
  • 連絡先のリストを取得する CursorLoader を使用してコードを取得するチュートリアル 連絡先プロバイダのデータを取得します
  • <ph type="x-smartling-placeholder"></ph> LoaderThrottle: スロットリングを使用してスロットリング数を減らす方法の例 データが変更されたときにコンテンツ プロバイダが実行するクエリの数。