多数の RSS フィードを表示するための Android アプリを開発中です (そうです、このようなアプリは既にたくさんあることを知っています)。表示するデータはコンテンツプロバイダーがバックアップしており、API レベル 4 との下位互換性を維持したい。
を使用してExpandableListView
、3 つの異なる RSS フィードのコンテンツを表示しています。のExpandableListView
アダプタは、次のサブクラスとして実装されていSimpleCursorTreeAdapter
ます。
private class RssFeedLatestListAdapter extends SimpleCursorTreeAdapter {
public static final String FEED_NAME_COLUMN = "feedName";
public RssFeedLatestListAdapter(Context ctx, Cursor groupCursor, int groupLayout,
String[] groupFrom, int[] groupTo, int childLayout, String[] childFrom,
int[] childTo) {
super(ctx, groupCursor, groupLayout, groupLayout, groupFrom, groupTo, childLayout, childFrom, childTo);
}
@Override
protected Cursor getChildrenCursor(final Cursor groupCursor) {
final String feedName = groupCursor.getString(groupCursor.getColumnIndex(FEED_NAME_COLUMN));
return managedQuery(LATEST_URI, null,
FeedItemColumns.CATEGORY + " = ? AND " + FeedItemColumns.FEED + " = ?",
new String[] { mCategory.getId(), feedName }, null);
}
@Override
protected void bindGroupView(View view, Context context, Cursor cursor, boolean isExpanded) {
super.bindGroupView(view, context, cursor, isExpanded);
// Bind group view (impl details not important) ...
}
@Override
protected void bindChildView(View view, Context context, Cursor cursor, boolean isLastChild) {
super.bindChildView(view, context, cursor, isLastChild);
// Bind child view (impl details not important) ...
}
}
このセットアップでは、すべてのコンテンツが期待どおりに読み込まれます。ただし、リスト コンテンツのロード中に、UI がランダムにハングまたはスタッターします。通常、ANR を取得するのに十分ではありませんが、それでも目立ち、非常に迷惑です!
この問題をデバッグするために、android.os.StrictMode を有効にし (すばらしい新機能/ツールです!)、エミュレーターを起動しました (Android 2.3 で)。リストのコンテンツが読み込まれると、次の StrictMode 出力が得られます。
D/StrictMode(395): StrictMode ポリシー違反。~期間 = 1522 ミリ秒: android.os.StrictMode$StrictModeDiskReadViolation: ポリシー = 23 違反 = 2 D/StrictMode(395): android.os.StrictMode$AndroidBlockGuardPolicy.onReadFromDisk(StrictMode.java:745) で D/StrictMode (395): android.database.sqlite.SQLiteDatabase.rawQueryWithFactory (SQLiteDatabase.java:1345) で D/StrictMode(395): android.database.sqlite.SQLiteQueryBuilder.query(SQLiteQueryBuilder.java:330) D/StrictMode (395): com.mycompany.myapp.provider.RSSFeedProvider.query (RSSFeedProvider.java:128) で D/StrictMode(395): android.content.ContentProvider$Transport.query(ContentProvider.java:187) で D/StrictMode(395): android.content.ContentResolver.query(ContentResolver.java:262) で D/StrictMode(395): android.app.Activity.managedQuery(Activity.java:1550) で D/StrictMode(395): com.mycompany.myapp.activity.MultipleFeedsActivity$RssFeedLatestListAdapter.getChildrenCursor(MultipleFeedsActivity.java:388) D/StrictMode(395): android.widget.CursorTreeAdapter.getChildrenCursorHelper(CursorTreeAdapter.java:106) で D/StrictMode(395): android.widget.CursorTreeAdapter.getChildrenCount(CursorTreeAdapter.java:178) で D/StrictMode(395): android.widget.ExpandableListConnector.refreshExpGroupMetadataList(ExpandableListConnector.java:561) で D/StrictMode(395): android.widget.ExpandableListConnector.expandGroup(ExpandableListConnector.java:682) で D/StrictMode(395): android.widget.ExpandableListConnector.expandGroup(ExpandableListConnector.java:636) で D/StrictMode(395): android.widget.ExpandableListView.expandGroup(ExpandableListView.java:608) で D/StrictMode (395): com.mycompany.myapp.activity.MultipleFeedsActivity.onReceiveResult (MultipleFeedsActivity.java:335) で D/StrictMode (395): com.mycompany.myapp.service.FeedResultReceiver.onReceiveResult (FeedResultReceiver.java:40) で D/StrictMode(395): android.os.ResultReceiver$MyRunnable.run(ResultReceiver.java:43) で D/StrictMode(395): android.os.Handler.handleCallback(Handler.java:587) で D/StrictMode(395): android.os.Handler.dispatchMessage(Handler.java:92) で D/StrictMode(395): android.os.Looper.loop(Looper.java:123) D/StrictMode(395): android.app.ActivityThread.main(ActivityThread.java:3647) で D/StrictMode(395): java.lang.reflect.Method.invokeNative(ネイティブ メソッド) で D/StrictMode(395): java.lang.reflect.Method.invoke(Method.java:507) で D/StrictMode(395): com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:839) で D/StrictMode (395): com.android.internal.os.ZygoteInit.main (ZygoteInit.java:597) で D/StrictMode(395): dalvik.system.NativeStart.main(ネイティブメソッド)
これは合理的なようです!ではSimpleCursorTreeAdapter.getChildrenCursor()
、コンテンツ プロバイダは UI スレッドで照会されますが、これはもちろん悪い考えであり、UI を確実にハングさせます。
(のスーパークラス)のJavaDoc(APIレベル4)を見て、次のように述べています。 CursorTreeAdapter
SimpleCursorTreeAdapter
getChildrenCursor()
「UI のブロックを防ぐためにプロバイダーに非同期的にクエリを実行する場合は、null を返し、後で setChildrenCursor(int, Cursor) を呼び出すことができます。」.
すごい!それをしましょう!の実装を変更getChildrenCursor()
して、クエリの実行とアダプターでの子カーソルの設定を担当する新しいタスクを開始します。タスクを開始した後、次のように null を返しますgetChildrenCursor()
。
@Override
protected Cursor getChildrenCursor(final Cursor groupCursor) {
final String feedName = groupCursor.getString(groupCursor.getColumnIndex(FEED_NAME_COLUMN));
new RefreshChildrenCursorTask(groupCursor.getPosition()).execute(feedName);
return null;
}
は次のようにRefreshChildrenCursorTask
実装されます。
private class RefreshChildrenCursorTask extends AsyncTask<String, Void, Cursor> {
private int mGroupPosition;
public RefreshChildrenCursorTask(int groupPosition) {
this.mGroupPosition = groupPosition;
}
@Override
protected Cursor doInBackground(String... params) {
String feedName = params[0];
return managedQuery(LATEST_URI, null,
FeedItemColumns.CATEGORY + " = ? AND " + FeedItemColumns.FEED + " = ?",
new String[] { mCategory.getId(), feedName }, null);
}
@Override
protected void onPostExecute(Cursor childrenCursor) {
mLatestListAdapter.setChildrenCursor(mGroupPosition, childrenCursor);
}
}
}
2.3エミュレーターにアプリを再インストールして起動しました。それは魅力のように機能しました。さようなら、非応答 UI! しかし、喜びは長くは続きませんでした...次に行うことは、ターゲット デバイス (この場合は Android 2.2 を実行する Samsung Galaxy S) で同じコードを実行することでした。次にException
、リスト フィード コンテンツの読み込み中に次の情報を取得しました。
E/AndroidRuntime(23191): 致命的な例外: メイン E/AndroidRuntime(23191): java.lang.NullPointerException E/AndroidRuntime(23191): android.widget.SimpleCursorTreeAdapter.initFromColumns(SimpleCursorTreeAdapter.java:194) で E/AndroidRuntime(23191): android.widget.SimpleCursorTreeAdapter.initChildrenFromColumns(SimpleCursorTreeAdapter.java:205) で E/AndroidRuntime(23191): android.widget.SimpleCursorTreeAdapter.init(SimpleCursorTreeAdapter.java:186) で E/AndroidRuntime(23191): android.widget.SimpleCursorTreeAdapter.(SimpleCursorTreeAdapter.java:136) で E/AndroidRuntime (23191): com.mycompany.myapp.activity.MultipleFeedsActivity$RssFeedLatestListAdapter.(MultipleFeedsActivity.java:378) で E/AndroidRuntime (23191): com.mycompany.myapp.activity.MultipleFeedsActivity$2.run (MultipleFeedsActivity.java:150) で E/AndroidRuntime(23191): android.os.Handler.handleCallback(Handler.java:587) で E/AndroidRuntime(23191): android.os.Handler.dispatchMessage(Handler.java:92) で E/AndroidRuntime(23191): android.os.Looper.loop(Looper.java:123) E/AndroidRuntime(23191): android.app.ActivityThread.main(ActivityThread.java:4627) で E/AndroidRuntime(23191): java.lang.reflect.Method.invokeNative(ネイティブ メソッド) で E/AndroidRuntime(23191): java.lang.reflect.Method.invoke(Method.java:521) で E/AndroidRuntime(23191): com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:871) で E/AndroidRuntime (23191): com.android.internal.os.ZygoteInit.main (ZygoteInit.java:629) で E/AndroidRuntime(23191): dalvik.system.NativeStart.main(ネイティブメソッド)
のソース コードをざっと見てみると、SimpleCursorTreeAdapter
から非同期に返される null に対処する方法がないことがわかりますgetChildrenCursor()
。彼らは Android 2.3 でこの問題を解決したようですが、以前のバージョンの Android ではどのように回避すればよいのでしょうか? CursorTreeAdapter
子カーソルを非同期的に更新できるようにするには、独自の実装を作成する必要がありますか?
誰かが同じ問題を抱えていて、おそらく (実行可能な) 回避策を見つけたことがありますか? そうでない場合は、続行する方法についてヒントを教えてもらえますか!? 私が得ることができる助けを本当に感謝します!
前もって感謝します!
よろしく、ジェイコブ