web-dev-qa-db-ja.com

Androidエラー:Java.lang.IllegalStateException:すでに閉じているカーソルを再クエリしようとしています

環境(HoneyComb3.0.1を実行しているXoomTablet用のLinux/Eclipse Dev)

私のアプリでは、カメラ(startIntentForResult())を使用して写真を撮っています。写真が撮られた後、onActivityResult()コールバックを取得し、「写真を撮る」インテントを介して渡されたURIを使用してビットマップをロードできます。その時点でアクティビティが再開され、画像をギャラリーにリロードしようとするとエラーが発生します。

FATAL EXCEPTION: main
ERROR/AndroidRuntime(4148): Java.lang.RuntimeException: Unable to resume activity {...}: 
 Java.lang.IllegalStateException: trying to requery an already closed cursor
     at Android.app.ActivityThread.handleResumeActivity(ActivityThread.Java:2243)
     at Android.app.ActivityThread$H.handleMessage(ActivityThread.Java:1019)
     at Android.os.Handler.dispatchMessage(Handler.Java:99)
     at Android.os.Looper.loop(Looper.Java:126)
     at Android.app.ActivityThread.main(ActivityThread.Java:3997)
     at Java.lang.reflect.Method.invokeNative(Native Method)
     at Java.lang.reflect.Method.invoke(Method.Java:491)
     at com.Android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.Java:841)
     at com.Android.internal.os.ZygoteInit.main(ZygoteInit.Java:599)
     at dalvik.system.NativeStart.main(Native Method)
 Caused by: Java.lang.IllegalStateException: trying to requery an already closed cursor
     at Android.app.Activity.performRestart(Activity.Java:4337)
     at Android.app.Activity.performResume(Activity.Java:4360)
     at Android.app.ActivityThread.performResumeActivity(ActivityThread.Java:2205)
     ... 10 more

私が使用している唯一のカーソルロジックは、画像が撮影された後、次のロジックを使用してUriをファイルに変換することです。

String [] projection = {
    MediaStore.Images.Media._ID, 
    MediaStore.Images.ImageColumns.ORIENTATION,
    MediaStore.Images.Media.DATA 
};

Cursor cursor = activity.managedQuery( 
        uri,
        projection,  // Which columns to return
        null,        // WHERE clause; which rows to return (all rows)
        null,        // WHERE clause selection arguments (none)
        null);       // Order-by clause (ascending by name)

int fileColumnIndex = cursor.getColumnIndexOrThrow(MediaStore.Images.Media.DATA);
if (cursor.moveToFirst()) {
    return new File(cursor.getString(fileColumnIndex));
}
return null;

私が間違っていることについて何か考えはありますか?

18
cyber-monk

ManagedQuery()呼び出しはHoneycombAPIで非推奨になっているようです。

ManagedQuery()のドキュメントは次のとおりです。

This method is deprecated.
Use CursorLoader instead.

Wrapper around query(Android.net.Uri, String[], String, String[], String) 
that the resulting Cursor to call startManagingCursor(Cursor) so that the
activity will manage its lifecycle for you. **If you are targeting HONEYCOMB 
or later, consider instead using LoaderManager instead, available via 
getLoaderManager()**.

また、私が推測するクエリの後に、cursor.close()を呼び出していることに気づきました。これを見つけました 本当に役立つリンク も。いくつか読んだ後、私はうまくいくように見えるこの変更を思いついた。

// causes problem with the cursor in Honeycomb
Cursor cursor = activity.managedQuery( 
        uri,
        projection,  // Which columns to return
        null,        // WHERE clause; which rows to return (all rows)
        null,        // WHERE clause selection arguments (none)
        null);       // Order-by clause (ascending by name)

// -------------------------------------------------------------------

// works in Honeycomb
String selection = null;
String[] selectionArgs = null;
String sortOrder = null;

CursorLoader cursorLoader = new CursorLoader(
        activity, 
        uri, 
        projection, 
        selection, 
        selectionArgs, 
        sortOrder);

Cursor cursor = cursorLoader.loadInBackground();
23
cyber-monk

記録のために、これをコードで修正した方法は次のとおりです(Android 1.6以降で実行):私の場合の問題は、CursorAdapter.changeCursor(を呼び出して管理カーソルを誤って閉じていたということでした)カーソルを変更する前にアダプタのカーソルでActivity.stopManagingCursor()を呼び出すと、問題が解決しました。

// changeCursor() will close current one for us: we must stop managing it first.
Cursor currentCursor = ((SimpleCursorAdapter)getListAdapter()).getCursor(); // *** adding these lines
stopManagingCursor(currentCursor);                                          // *** solved the problem
Cursor c = db.fetchItems(selectedDate);
startManagingCursor(c);
((SimpleCursorAdapter)getListAdapter()).changeCursor(c);
7
Martin Stone

修正:_activity.managedQuery_の代わりにcontext.getContentResolver().queryを使用してください。

_Cursor cursor = null;
try {
    cursor = context.getContentResolver().query(uri, PROJECTION, null, null, null);
} catch(Exception e) {
    e.printStackTrace();
}
return cursor;
_
5
Vincent Czz

最後の回答にコメントできなかったため、ここでこの質問を作成しました(何らかの理由でコメントが無効になっています)。これについて新しいスレッドを開くと、事態が複雑になるだけだと思いました。

アクティビティAからアクティビティBに移動してから、アクティビティAに戻ると、アプリケーションがクラッシュします。これは常に発生するわけではありません。たまにしか発生せず、これが発生する場所を正確に見つけるのに苦労しています。すべてが同じデバイス(Nexus S)で発生しますが、これはデバイスの問題ではないと思います。

@MartinStineの回答に関していくつか質問があります。

  • ドキュメントでは、changeCursor(c);について次のように述べています。「基になるカーソルを新しいカーソルに変更します。既存のカーソルがある場合は、閉じられます」。では、なぜstopManagingCursor(currentCursor); --冗長ではないにする必要があるのでしょうか。
  • @Martin Stineが提供するコードを使用すると、nullポインター例外が発生します。その理由は、アプリケーションの最初の「実行」で、カーソルがまだ作成されていないため、_((SimpleCursorAdapter)getListAdapter())_がNULLと評価されるためです。確かに、nullを取得しないかどうかを確認してから、カーソルを管理するために停止しようとしましたが、最終的に `stopManagingCursor(currentCursor);を配置することにしました。このアクティビティのonPause()メソッドで。このようにすると、管理を停止するカーソルが確実にあり、アクティビティを別のアクティビティに任せる直前に実行する必要があると思いました。問題-アクティビティで複数のカーソル(1つはEditTextフィールドのテキストを埋めるため、もう1つはリストビュー用)を使用していますが、すべてがListAdapterカーソルに関連しているわけではないと思います-
    • 管理を停止するものをどのように知ることができますか? 3つの異なるリストビューがある場合はどうなりますか?
    • onPause()の間にそれらすべてを閉じる必要がありますか?
    • 開いているすべてのカーソルのリストを取得するにはどうすればよいですか?

非常に多くの質問...誰かが助けてくれることを願っています。

onPause()に到達すると、管理を停止するカーソルがありますが、このエラーが散発的に表示されるため、これで問題が解決するかどうかはまだわかりません。

どうもありがとう!


いくつかの調査の後:

私はこの問題の「神秘的な」側面に答えを与えるかもしれない何か面白いものを見つけました:

アクティビティAは2つのカーソルを使用します。1つはEditTextフィールドに入力します。もう1つは、ListViewにデータを入力することです。

アクティビティAからアクティビティBに移動して戻ってくるときは、アクティビティAのフィールド+ ListViewを再度入力する必要があります。 EditTextフィールドで問題が発生することはないようです。 EditTextフィールドの現在のカーソルを取得する方法が見つかりませんでした(Cursor currentCursor = ((SimpleCursorAdapter)getListAdapter()).getCursor();のように)。理由は、EditTextフィールドがそれを保持しないことを示しています。一方、ListViewは、前回から(アクティビティA->アクティビティBの前から)カーソルを「記憶」します。さらに、これは奇妙なことですが、Cursor currentCursor = ((SimpleCursorAdapter)getListAdapter()).getCursor();はアクティビティB->アクティビティAの後に異なるIDを持ち、このすべて[〜#〜] without [〜#〜] =私は今までに電話しました

_Cursor currentCursor = ((SimpleCursorAdapter)getListAdapter()).getCursor();
stopManagingCursor(currentCursor);  
_

場合によっては、システムがリソースを解放する必要があるときにカーソルが強制終了され、アクティビティB->アクティビティAのときに、システムはこの古いデッドカーソルを使用しようとするため、例外が発生することがあります。また、他の場合には、システムはまだ生きている新しいカーソルを思い付くので、例外は発生しません。これは、これがたまにしか表示されない理由を説明している可能性があります。 ORアプリケーションのデバッグ)を実行すると、アプリケーションの速度が異なるため、これをデバッグするのは難しいと思います。デバッグする場合は、時間がかかるため、システムが新しいカーソルまたはその逆。

私の理解では、これは

_Cursor currentCursor = ((SimpleCursorAdapter)currentListAdapter).getCursor();
stopManagingCursor(currentCursor);
_

@Martin Stineが推奨するように、場合によっては必須、その他の場合は冗長:メソッドに戻り、システムがデッドカーソルを使用しようとしている場合は、作成する必要があります。新しいカーソルをListAdapterに置き換えます。そうしないと、アプリのユーザーがクラッシュして怒ります。システムが新しいカーソルを検出する別のケースでは、上記の行は適切なカーソルを無効にして新しいカーソルを作成するため、冗長です。

この冗長性を防ぐには、次のようなものが必要になると思います。

_ListAdapter currentListAdapter = getListAdapter();
Cursor currentCursor = null;
 Cursor c = null;

//prevent Exception in case the ListAdapter doesn't exist yet
if(currentListAdapter != null)
    {
        currentCursor = ((SimpleCursorAdapter)currentListAdapter).getCursor();

                    //make sure cursor is really dead to prevent redundancy
                    if(currentCursor != null)
                    {
                        stopManagingCursor(currentCursor);

                        c = db.fetchItems(selectedDate);

                        ((SimpleCursorAdapter)getListAdapter()).changeCursor(c);
                    }
                    else
                    {
                      c = db.fetchItems(selectedDate);

                    }
    }
            else
            {
              c = db.fetchItems(selectedDate);

            }

startManagingCursor(c);
_

私はあなたがこれについてどう思うか聞いてみたいです!

3
roysch

カーソルブロックの最後に次のコードを追加するだけです。

   try {
                Cursor c = db.displayName(number);

                startManagingCursor(c);
                if (!c.moveToFirst()) {
                    if (logname == null)
                        logname = "Unknown";
                    System.out.println("Null " + logname);
                } else {
                    logname = c.getString(c
                            .getColumnIndex(DataBaseHandler.KEY_NAME));
                    logdp = c.getBlob(c
                            .getColumnIndex(DataBaseHandler.KEY_IMAGE));
                    // tvphoneno_oncall.setText(logname);
                    System.out.println("Move name " + logname);
                    System.out.println("Move number " + number);
                    System.out.println("Move dp " + logdp);
                }

                stopManagingCursor(c);
            } 
2
Exceptional

この問題は長い間私を悩ませていました、そして私はついにAndroidのすべてのバージョンで魅力のように働く簡単な解決策を思いつきました。まず、startManagingCursor()は明らかにバグがあり、いずれの場合も非推奨であるため、使用しないでください。次に、カーソルを使い終わったら、できるだけ早くカーソルを閉じます。私はtryとfinallyを使用して、すべての状況でカーソルが閉じられるようにします。メソッドがカーソルを返す必要がある場合、呼び出し元のルーチンはできるだけ早くカーソルを閉じる責任があります。

以前はアクティビティの存続期間中はカーソルを開いたままにしていましたが、このトランザクションアプローチではそれを放棄しました。現在、私のアプリは非常に安定しており、同じデータベースにアクセスしていても、アクティビティを切り替えるときに「Androidエラー:Java.lang.IllegalStateException:すでに閉じているカーソルを再クエリしようとしています」という問題は発生しません。

   static public Boolean musicReferencedByOtherFlash(NotesDB db, long rowIdImage)
   {
      Cursor dt = null;
      try
      {
         dt = db.getNotesWithMusic(rowIdImage);
         if (   (dt != null)
             && (dt.getCount() > 1))
            return true;
      }
      finally
      {
         if (dt != null)
            dt.close();
      }
      return false;
   }
0
cdavidyoung