まえがき
一度、異なるタイプの1つのListViewカードに表示する必要があり、さらに異なるAPIを使用してサーバーから受け取りました。 同様に、ユーザーに喜ばせて、1つのニュースフィードで次の情報を見てみましょう。
- スティックと説明付きのビデオカード。
- 作成者またはタグのカード、大きな「購読」ボタン付き。
明らかに、考えられるすべてのカードのオプションを考慮に入れる1つの大きなレイアウトをいじるのは悪いことであり、まあまあ拡大します。

2番目の難点は、カードのデータソースが完全に異なるサーバーリソースになる可能性があり、異なるタイプのデータを返す複数の異なるAPIへの同時リクエストを使用してリストを収集する必要があったことです。

まあ、人生が蜜のように見えないように、サーバーAPIは変更できません。
APIからListViewへ
Google I / O 2010の Virgil Dobjanschiは、REST APIとの対話を実装する方法を完全に説明しました。 最初のパターンは次のとおりです。
- アクティビティは、REST APIへのリクエストを行うサービスを作成します。
- サービスは応答を解析し、ContentProviderを介してデータベースにデータを保存します。
- アクティビティはデータ変更の通知を受け取り、ビューを更新します。
UPDここでは、サービスの使用というトピックに関する小さなホリバーが発生したため、この言葉を「HTTPリクエストを実装するライブラリ」に置き換えた方がよいでしょう。どちらの方法でもかまいません。
したがって、最終的にはすべてが機能します:APIに対して大量のリクエストを作成し、ContentProviderを使用してRESTリソースのタイプに関連付けられた別のテーブルにデータを挿入し、notifyChangeを使用してフィード内の新しいデータの可用性を通知します。 しかし、いつものように、2つの問題があります。
- カードのリストを表示する方法は?
- テープのリクエストを収集する方法は?
さまざまな種類のカードを表示します
最初に、より単純なものに対処しましょう。 ソリューションはGoogleで簡単に見つけられるので、簡単に説明します。
カードリストアダプターでは 、メソッドを再定義します。
@Override int getViewTypeCount() { // , return VIEW_TYPE_COUNT; } @Override int getItemViewType(int position) { // Cursor c = (Cursor)getItem(position); int columnIndex = c.getColumnIndex(VIEW_TYPE_COLUMN); return c.getInt(columnIndex); } @Override void bindView(View view, Context context, Cursor c) { // int columnIndex = c.getColumnIndex(VIEW_TYPE_COLUMN); int viewType = c.getInt(columnIndex); switch(viewType) { case VIEW_TYPE_VIDEO: bindVideoView(view); break; case VIEW_TYPE_SUBSCRIPTION: // } } @Override View newView(Context context, Cursor cursor, ViewGroup parent) { // int columnIndex = c.getColumnIndex(VIEW_TYPE_COLUMN); int viewType = c.getInt(columnIndex); switch(viewType) { case VIEW_TYPE_VIDEO: return newVideoView(cursor); case VIEW_TYPE_SUBSCRIPTION: // } }
さらに、すばらしい
CursorAdapter
クラスはそれ自体ですべてを実行します。異なるタイプのビューの個別のビューキャッシュを初期化し、新しいビューを作成するか、古いビューを再利用するかを判断します...一般に、すべてが素晴らしいです。カーソルで
VIEW_TYPE_COLUMN
列を取得するだけです。
テープのSQLクエリを収集します
明確にするために、データベースにテーブルがあります:
- videos-フィードのビデオのリストが含まれています。
列ID、タイトル、画像、更新。 - 作成者、タグ -サブスクライブできるエンティティのリストが含まれます(1対1がサーバーAPIに表示されます)。
列ID、名前、画像、更新されました。
合計、次の列を返すクエリを作成する必要があります。
コラム | ビデオ | 著者 | タグ付け | 解説 |
---|---|---|---|---|
id | video_id | author_id | tag_id | 対応するテーブルの主キー |
view_type | ビデオ | サブスクリプション | サブスクリプション | 表示するカードの種類 |
content_type | 動画 | 著者 | タグ | コンテンツタイプ-またはテーブル名(より便利な場合) |
タイトル | video_title | ヌル | ヌル | ビデオタイトル |
お名前 | ヌル | author_name | tag_name | 著者名またはタグ名 |
絵 | リンク | リンク | リンク | 写真へのリンク |
更新しました | タイムスタンプ | タイムスタンプ | タイムスタンプ | サーバー更新時間 |
もう少し説明します。
- view_type-ディスプレイのタイプを担当します。 著者とタグの表示タイプは同じであることに注意してください。
- content_type-データソースを担当します。 作成者とタグについては、すでに異なっているため、必要に応じて、目的のテーブルまたは追加のデータの目的のAPIを参照できます。
- タイトル、名前、写真 -すべてのテーブルに共通のテーブル列、または特定の各テーブルに固有の列
- 更新 -結果として行がソートされるフィールド。
sqliteでは、クエリは非常に簡単です。
SELECT 0 as view_type, 'videos' as content_type, title, NULL as name, picture, updated FROM videos UNION ALL SELECT 1 as view_type, 'authors' as content_type, NULL as title, name, picture, updated FROM authors UNION ALL SELECT 1 as view_type, 'tags' as content_type, NULL as title, name, picture, updated FROM tags ORDER BY updated
もちろん、このようなクエリを「手作業で」作成することもできますが、 SQLiteQueryBuilderには少しバグがありますが、そのようなクエリを作成するための作業メソッドがあります。
そのため、ActivityはContentProviderからフィードを要求しています:
Cursor c = getContext().getContentResolver().query(Uri.parse("content://MyProvider/feed/"));
同時に、
MyProvider.query
メソッド
MyProvider.query
、要求がUriテープに対して行われたことを判断し、「インテリジェント」クエリ構築モードに切り替える必要があります。
Cursor query(Uri contentUri, String[] projection, String selection, String[] selectionArgs, String sortOrder) { if (isFeedUri(contentUri)) return buildFeedUri(); // // ... } Cursor buildFeedUri() { // "-" HashSet<String> unionColumnsSet = new HashSet<String>(); // Uri , (videos, authors tags) List<Uri>contentUriList = getSubqueryContentUriList(); // viewType String[] viewTypeColumns = new String[contentUriList.size()]; // contentType String[] contentTypeColumns = new String[contentUriList.size()]; for (int i=0; i<contentUriList.size(); i++) { Uri contentUri = contentUriList.get(i); // viewTypeColumns[i] = getViewTypeExpr(contentUri); // "0 as view_type" // content_type contentTypeColumns[i] = getContentTypeExpr(contentUri); // "'videos' as content_type" // List<String> projection = getProjection(contentUri); // unionColumnsSet.addAll(projection); } // , , : , // content-type , . String[] subqueries = new String[contentUriList.size()]; for (int i=0; i<contentUriList.size(); i++) { Uri contentUri = contentUriList.get(i); SQLiteQueryBuilder builder = new SQLiteQueryBuilder(); builder.setTables(getTable(contentUri)); // "1 as content_type" // , builder // "SELECT X as Y" String[] unionColumns = prependContentTypeExpr(contentTypeColumns[i], unionColumnSet); // "" "0 as view_type" // , Set<String> projection = prependViewTypeExpr(viewTypeColumns[i], getProjection(contentUri)); // , String selection = computeWhere(contentUri); subqueries[i] = builder.buildUnionSubQuery( "content_type", // typeDiscriminatorColumn - , // unionColumns, projection, 0, getTable(contentUri), // content_type // ( ) selection, null, // selectionArgs - buildUnionSubQuery // ( API level 1, API level 11 - ) null, // groupBy null // having ); } // , . SQLiteQueryBuilder builder = new SQLiteQueryBuilder() String orderBy = "updated DESC"; String query = builder.buildUnionQuery( subqueries, orderBy, null // limit - , . ); return getDBHelper().getReadableDatabase().rawQuery( query, null // selectionArgs - ); }
一般に、例が正しく記述されている場合、
content://MyProvider/feed/
にアクセスするときに
content://MyProvider/feed/
ContentProviderは必要なUNION要求を生成し、必要なデータをアダプターに提供します。
サーバーからデータの更新を受け取ります
しかし、それは何ですか? APIビデオの2ページ目をリクエストします。ログから判断して、データはデータベースに保存されますが、ListViewは更新されません...
ポイントはLoaderCallbacksの実装です
@Override public Loader<Cursor> onCreateLoader(int loaderId, Bundle params) { return new CursorLoader( getContext(), Uri.parse("content://MyContentProvider/feed/"), ... ); }
アクティビティがContentProviderを要求すると、CursorLoaderはUri
content://MyProvider/feed/
を監視するContentObserverを作成します
content://MyProvider/feed/
; サービスがリクエストの結果をサーバーのAPIに保存すると、ContentProviderは別のUri、
content://MyProvider/videos/
データ変更を自動的に通知します
content://MyProvider/videos/
。
この問題を正しく最終的に解決する方法はわかりません。 私のアプリケーションでは、クエリの結果をデータベースに保存するコードでは、フィードのデータの変更について明示的に通知するのに十分でした(特定のテーブルの変更の通知はプロバイダーに当てはまります)。
getContext.getContentResolver().notifyChange(Uri.parse("content://MyProvider/feed/", null));
代替ソリューション
- MergeCursor-カーソルインターフェイスでカーソルのリストをラップし 、反復するとき、最初のカーソルからすべての行を返し、次に2番目のカーソルなどを返します。
リクエスト内の行の順序が重要でない場合は、コードを大幅に簡素化できます。 - MatrixCursor-データベースにアクセスせずに、任意の2次元配列へのカーソルインターフェイスを提供できます。 MergeCursor +ソート+ MatrixCursor-それほど多くない行をソートして表示する必要がある場合に利益をもたらします。