如何在android中正确关闭光标

Posted

技术标签:

【中文标题】如何在android中正确关闭光标【英文标题】:How to properly close a cursor in android 【发布时间】:2012-10-08 16:18:25 【问题描述】:

我有这个使用 sqlite 的数据库,我在关闭游标时遇到问题,它说 Application did not close the cursor or database object that was opened here 这是 logcat

10-18 08:40:56.354: E/Cursor(331): android.database.sqlite.DatabaseObjectNotClosedException: Application did not close the cursor or database object that was opened here
10-18 08:40:56.354: E/Cursor(331):  at android.database.sqlite.SQLiteCursor.<init>(SQLiteCursor.java:210)
10-18 08:40:56.354: E/Cursor(331):  at android.database.sqlite.SQLiteDirectCursorDriver.query(SQLiteDirectCursorDriver.java:53)
10-18 08:40:56.354: E/Cursor(331):  at android.database.sqlite.SQLiteDatabase.rawQueryWithFactory(SQLiteDatabase.java:1345)
10-18 08:40:56.354: E/Cursor(331):  at android.database.sqlite.SQLiteDatabase.queryWithFactory(SQLiteDatabase.java:1229)
10-18 08:40:56.354: E/Cursor(331):  at android.database.sqlite.SQLiteDatabase.query(SQLiteDatabase.java:1184)
10-18 08:40:56.354: E/Cursor(331):  at android.database.sqlite.SQLiteDatabase.query(SQLiteDatabase.java:1264)
10-18 08:40:56.354: E/Cursor(331):  at standard.internet.marketing.mymovingfriend.SQLHandler.checkMove(SQLHandler.java:1094)
10-18 08:40:56.354: E/Cursor(331):  at standard.internet.marketing.mymovingfriend.ListMovingNames$3.onKey(ListMovingNames.java:98)
10-18 08:40:56.354: E/Cursor(331):  at android.view.View.dispatchKeyEvent(View.java:3735)
10-18 08:40:56.354: E/Cursor(331):  at android.view.ViewGroup.dispatchKeyEvent(ViewGroup.java:788)
10-18 08:40:56.354: E/Cursor(331):  at android.view.ViewGroup.dispatchKeyEvent(ViewGroup.java:788)
10-18 08:40:56.354: E/Cursor(331):  at android.view.ViewGroup.dispatchKeyEvent(ViewGroup.java:788)
10-18 08:40:56.354: E/Cursor(331):  at android.view.ViewGroup.dispatchKeyEvent(ViewGroup.java:788)
10-18 08:40:56.354: E/Cursor(331):  at com.android.internal.policy.impl.PhoneWindow$DecorView.superDispatchKeyEvent(PhoneWindow.java:1667)
10-18 08:40:56.354: E/Cursor(331):  at com.android.internal.policy.impl.PhoneWindow.superDispatchKeyEvent(PhoneWindow.java:1102)
10-18 08:40:56.354: E/Cursor(331):  at android.app.Activity.dispatchKeyEvent(Activity.java:2063)
10-18 08:40:56.354: E/Cursor(331):  at com.android.internal.policy.impl.PhoneWindow$DecorView.dispatchKeyEvent(PhoneWindow.java:1643)
10-18 08:40:56.354: E/Cursor(331):  at android.view.ViewRoot.deliverKeyEventToViewHierarchy(ViewRoot.java:2471)
10-18 08:40:56.354: E/Cursor(331):  at android.view.ViewRoot.handleFinishedEvent(ViewRoot.java:2441)
10-18 08:40:56.354: E/Cursor(331):  at android.view.ViewRoot.handleMessage(ViewRoot.java:1735)
10-18 08:40:56.354: E/Cursor(331):  at android.os.Handler.dispatchMessage(Handler.java:99)
10-18 08:40:56.354: E/Cursor(331):  at android.os.Looper.loop(Looper.java:123)
10-18 08:40:56.354: E/Cursor(331):  at android.app.ActivityThread.main(ActivityThread.java:4627)
10-18 08:40:56.354: E/Cursor(331):  at java.lang.reflect.Method.invokeNative(Native Method)
10-18 08:40:56.354: E/Cursor(331):  at java.lang.reflect.Method.invoke(Method.java:521)
10-18 08:40:56.354: E/Cursor(331):  at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:868)
10-18 08:40:56.354: E/Cursor(331):  at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:626)
10-18 08:40:56.354: E/Cursor(331):  at dalvik.system.NativeStart.main(Native Method)

这个问题困扰了我 3 天。

这里有一些相关代码:

public class SQLHandler 

    public static final String KEY_MOVENAME = "movename";
    public static final String KEY_ID1 = "_id";
    public static final String KEY_ID5 = "_id";
    public static final String KEY_MOVEDATE = "movedate";
    public static final String KEY_TOTALMOVEWEIGHT = "totalmoveweight";
    public static final String KEY_TOTALITEM = "totalitem";

    private static final String DATABASE_NAME = "mymovingfriend";
    private static final int DATABASE_VERSION = 1;

    public static final String KEY_LISTITEMNAME = "listitemname";
    public static final String KEY_LISTITEMWEIGHT = "listitemweight";
    public static final String KEY_LISTITEMROOM = "listitemroom";

    private static final String DATABASE_TABLE1 = "movingname";
    private static final String DATABASE_TABLE5 = "listitem";

    public static final String CREATE_TABLE_1 = "CREATE TABLE " + DATABASE_TABLE1 + " (" + 
            KEY_ID1 + " INTEGER PRIMARY KEY AUTOINCREMENT," + 
            KEY_MOVEDATE + " TEXT NOT NULL, " + 
            KEY_TOTALMOVEWEIGHT + " TEXT NOT NULL, " + 
            KEY_TOTALITEM + " INTEGER NOT NULL, " +
            KEY_MOVENAME + " TEXT NOT NULL);";

    public static final String CREATE_TABLE_2 = "CREATE TABLE " + DATABASE_TABLE2 + " (" + 
            KEY_ID2 + " INTEGER PRIMARY KEY AUTOINCREMENT," + 
            KEY_ROOMMOVEHOLDER + " TEXT NOT NULL, " + 
            KEY_ROOMWEIGHT + " TEXT NOT NULL, " + 
            KEY_ROOM + " TEXT NOT NULL);";

    public static final String CREATE_TABLE_5 = "CREATE TABLE " + DATABASE_TABLE5 + " (" + 
            KEY_ID5 + " INTEGER PRIMARY KEY AUTOINCREMENT," + 
            KEY_LISTITEMNAME + " TEXT NOT NULL, " + 
            KEY_LISTITEMWEIGHT + " TEXT NOT NULL, " +
            KEY_LISTITEMROOM + " TEXT NOT NULL);";

    private DbHelper ourHelper;
    private final Context ourContext;
    private SQLiteDatabase ourDatabase;

    private static class DbHelper extends SQLiteOpenHelper

        public DbHelper(Context context) 
            super(context, DATABASE_NAME, null, DATABASE_VERSION);
            // TODO Auto-generated constructor stub
        

        @Override
        public void onCreate(SQLiteDatabase db) 
            // TODO Auto-generated method stub
            db.execSQL(CREATE_TABLE_1);
            db.execSQL(CREATE_TABLE_2);
            db.execSQL(CREATE_TABLE_3);
            db.execSQL(CREATE_TABLE_4);
            db.execSQL(CREATE_TABLE_5);
        

        @Override
        public void onUpgrade(SQLiteDatabase db, int oldversion, int newversion) 
            // TODO Auto-generated method stub
            db.execSQL("DROP TABLE IF EXISTS " + DATABASE_TABLE1);
            db.execSQL("DROP TABLE IF EXISTS " + DATABASE_TABLE2);
            db.execSQL("DROP TABLE IF EXISTS " + DATABASE_TABLE3);
            db.execSQL("DROP TABLE IF EXISTS " + DATABASE_TABLE4);
            db.execSQL("DROP TABLE IF EXISTS " + DATABASE_TABLE5);
            onCreate(db);
        
    

    public SQLHandler(Context c)
        ourContext = c;
    

    public SQLHandler open() throws SQLException
        ourHelper = new DbHelper(ourContext);
        ourDatabase = ourHelper.getWritableDatabase();
        return this;
    

    public void close()
        ourDatabase.close();
        ourHelper.close();
    

    public long createMove(String smovename)
        ContentValues cv = new ContentValues();
        cv.put(KEY_MOVENAME, smovename);
        cv.put(KEY_MOVEDATE, "Not yet set");
        cv.put(KEY_TOTALMOVEWEIGHT, "0");
        cv.put(KEY_TOTALITEM, 0);
        return ourDatabase.insert(DATABASE_TABLE1, null, cv);
    

    public void createList()

        String[] sroom = new String[]"Kitchen", "Bedroom", "Dinning Room";
        String[] sitem = new String[]"Dishwasher", "Bed", "Table";
        String[] sweight = new String[]"40", "25", "15";

        for (int i = 0; i < sroom.length; i++) 
            cv.put(KEY_LISTITEMROOM, sroom[i]);
            cv.put(KEY_LISTITEMNAME, sitem[i]);
            cv.put(KEY_LISTITEMWEIGHT, sweight[i]);
            ourDatabase.insert(DATABASE_TABLE5, null, cv);
        
    

    public void setMoveDate(String smovedate, String smovename)
        ContentValues cv = new ContentValues();
        cv.put(KEY_MOVEDATE, smovedate);
        ourDatabase.update(DATABASE_TABLE1, cv, KEY_MOVENAME + "='" + smovename + "'", null);
    

    public void setMoveWeight(String smoveweight, String smovename)
        ContentValues cv = new ContentValues();
        cv.put(KEY_TOTALMOVEWEIGHT, smoveweight);
        ourDatabase.update(DATABASE_TABLE1, cv, KEY_MOVENAME + "='" + smovename + "'", null);
    

    public void setTotalItem(String smovename, int imoveitem)
        ContentValues cv = new ContentValues();
        cv.put(KEY_TOTALITEM, imoveitem);
        ourDatabase.update(DATABASE_TABLE1, cv, KEY_MOVENAME + "='" + smovename + "'", null);
    

    public void renameRoom(String movename, String roomname, String currentroom)
        ContentValues cv = new ContentValues();
        cv.put(KEY_ROOM, roomname);
        ourDatabase.update(DATABASE_TABLE2, cv, KEY_ROOMMOVEHOLDER + "='" + movename + "'" + " AND " + KEY_ROOM + "='" + currentroom + "'", null);
    

    public void setRoomWeight(String sroomweight, String smovename, String sroomname)
        ContentValues cv = new ContentValues();
        cv.put(KEY_ROOMWEIGHT, sroomweight);
        ourDatabase.update(DATABASE_TABLE2, cv, KEY_ROOMMOVEHOLDER + "='" + smovename + "'" + " AND " + KEY_ROOM + "='" + sroomname + "'", null);
    

    public long addRooms(String sroommoveholder, String sroom)
        ContentValues cv = new ContentValues();
        cv.put(KEY_ROOMMOVEHOLDER, sroommoveholder);
        cv.put(KEY_ROOM, sroom);
        cv.put(KEY_ROOMWEIGHT, "0");
        return ourDatabase.insert(DATABASE_TABLE2, null, cv);
    

    public long addNewItems(String sitemmoveholder, String sroomholder, String sitemname, String sitemvalue, String sitemweight)
        ContentValues cv = new ContentValues();
        cv.put(KEY_ITEMMOVEHOLDER, sitemmoveholder);
        cv.put(KEY_ROOMHOLDER, sroomholder);
        cv.put(KEY_ITEMNAME, sitemname);
        cv.put(KEY_ITEMVALUE, sitemvalue);
        cv.put(KEY_ITEMWEIGHT, sitemweight);
        return ourDatabase.insert(DATABASE_TABLE3, null, cv);
    

    public void updateItems(String sitemmoveholder, String sroomholder, String sitemname, String sitemvalue, String sitemweight)
        ContentValues cv = new ContentValues();
        cv.put(KEY_ITEMVALUE, sitemvalue);
        cv.put(KEY_ITEMWEIGHT, sitemweight);
        ourDatabase.update(DATABASE_TABLE3, cv, KEY_ITEMMOVEHOLDER + "='" + sitemmoveholder + "'" + " AND " + 
                KEY_ROOMHOLDER + "='" + sroomholder + "'" + " AND " + KEY_ITEMNAME + "='" + sitemname + "'", null);
    

    public Cursor getMove()
        String[] columns = new String[]KEY_ID1, KEY_MOVENAME;

        Cursor c = null;
        try 
            c = ourDatabase.query(DATABASE_TABLE1, columns, null, null, null, null, null);
         catch (Exception e) 
            c.close();
        
        return c;
    

    public String getTotalWeight(String m) throws SQLException
        String[] columns = new String[]KEY_ID1, KEY_MOVENAME, KEY_MOVEDATE, KEY_TOTALMOVEWEIGHT;

        Cursor c = null;
        try 
            c = ourDatabase.query(DATABASE_TABLE1, columns, KEY_MOVENAME + "= '" + m + "'", null, null, null, null);
            if (c != null) 
                c.moveToFirst();
                String totalWeight = c.getString(3);
                return totalWeight;
            
         catch (Exception e) 
            c.close();
        
        return null;

    

    public String getTotalWeightLBS(String m) throws SQLException
        String[] columns = new String[]KEY_ID1, KEY_MOVENAME, KEY_MOVEDATE, KEY_TOTALMOVEWEIGHT;

        Cursor c = null;
        try 
            c = ourDatabase.query(DATABASE_TABLE1, columns, KEY_MOVENAME + "= '" + m + "'", null, null, null, null);
            if (c != null) 
                c.moveToFirst();
                int x = Integer.parseInt(c.getString(3)) * 7;
                String totalWeight = "" + x + " lbs";
                return totalWeight;
            
         catch (Exception e) 
            c.close();
        
        return null;

    

    public String getDateMove(String md)
        String[] columns = new String[]KEY_ID1, KEY_MOVENAME, KEY_MOVEDATE;

        Cursor c = null;try 
            c = ourDatabase.query(DATABASE_TABLE1, columns, KEY_MOVENAME + "= '" + md + "'", null, null, null, null);

            if (c != null) 
                c.moveToFirst();
                String moveDate = c.getString(2);
                return moveDate;
            
         catch (Exception e) 
            c.close();
        
        return null;
    

    public ArrayList<String> loadRooms(String mn) throws SQLException
        String[] columns = new String[]KEY_ID2, KEY_ROOMMOVEHOLDER, KEY_ROOM;

        ArrayList<String> array = new ArrayList<String>();

        Cursor c = null;
        try 
            c = ourDatabase.query(DATABASE_TABLE2, columns,KEY_ROOMMOVEHOLDER + "='" + mn + "'", 
                    null, null, null, null);
            int iroom = c.getColumnIndex(KEY_ROOM);

            for (c.moveToFirst(); !c.isAfterLast(); c.moveToNext()) 
                array.add(c.getString(iroom));
            
         catch (Exception e) 
            c.close();
        
        return array;
    

    public void deleteMove(String m) throws SQLException
        ourDatabase.delete(DATABASE_TABLE1, KEY_MOVENAME + "='" + m + "'", null);
        ourDatabase.delete(DATABASE_TABLE2, KEY_ROOMMOVEHOLDER + "='" + m + "'", null);
        ourDatabase.delete(DATABASE_TABLE3, KEY_ITEMMOVEHOLDER + "='" + m + "'", null);
        ourDatabase.delete(DATABASE_TABLE4, KEY_TODOMOVE + "='" + m + "'", null);
    

    public ArrayList<String> getitems()
        String[] columns = new String[]KEY_ID5, KEY_ITEMNAME;
        ArrayList<String> items;
        items = new ArrayList<String>();

        Cursor c = null;
        try 
            c = ourDatabase.query(DATABASE_TABLE5, columns, null, null, null, null, null);

            for (c.moveToFirst(); !c.isAfterLast(); c.moveToNext()) 
                items.add(c.getString(1));
            
         catch (Exception e) 
            c.close();
        

        return items;
    

    public ArrayList<String> getitemweight()
        String[] columns = new String[]KEY_ID5, KEY_ITEMWEIGHT;
        ArrayList<String> items = new ArrayList<String>();

        Cursor c = null;
        try 
            c = ourDatabase.query(DATABASE_TABLE5, columns, null, null, null, null, null);
            for (c.moveToFirst(); !c.isAfterLast(); c.moveToNext()) 
                items.add(c.getString(1));
            
         catch (Exception e) 
            c.close();
        

        return items;
    

    public ArrayList<String> getitemclass()
        String[] columns = new String[]KEY_ID5, KEY_LISTITEMROOM;
        ArrayList<String> items = new ArrayList<String>();

        Cursor c = null;
        try 
            c = ourDatabase.query(DATABASE_TABLE5, columns, null, null, null, null, null);

            for (c.moveToFirst(); !c.isAfterLast(); c.moveToNext()) 
                items.add(c.getString(1));
            
         catch (Exception e) 
            c.close();
        

        return items;
    


【问题讨论】:

【参考方案1】:

getDateMovegetTotalWeightLBSloadRooms、...中没有关闭光标。

在不再需要光标的地方关闭它。在这些方法中使用try-finally,即使抛出异常,也会保证在finally块中执行代码。

从这里更改方法中的代码:

try
  // get data from cursor
 catch (Exception e) 
    c.close();

到这里:

try 
   // get data from cursor
 catch (Exception e) 
   // exception handling
 finally 
   if(c != null)
       c.close();
   
 

【讨论】:

等一下,我打错字了,请稍等 该异常是由checkMove 方法引发的,该方法未包含在您的代码中。 如果你已经读完了 Cursor,关闭它 - 就这样。【参考方案2】:

如果一个元素正在实现 AutoCloseable(就像 Cursor.class 所做的那样),我建议执行 try-with-resources,就像描述的 here 一样。 如果你使用 Retrolambda,它有 try-with-resources 向后移植。

所以你的代码:

Cursor cursor = db.query("tableName", columns, null, null, null, null, null);
try 
    if (cursor.moveToFirst()) return cursor.getString(3);
    else return null;
 finally 
   cursor.close();

只会变成:

try (Cursor cursor = db.query("tableName", columns, null, null, null, null, null)) 
    if (cursor.moveToFirst()) return cursor.getString(3);
    else return null;

【讨论】:

请注意,此方法需要 API 19 我在 API 11 中使用它,并且在过去几个月有大约 3k 用户时从未遇到过崩溃。 Android Studio 的检查器用“Try-with-resources 需要 API 级别 19(当前最小值为 15)”以红色突出显示 try,但是我进行了测试,它在手机 API 级别 17 上编译和运行良好. 显然 minAPI***.com/questions/20480090/… 虽然它可能适用于某些设备,但不建议这样做。 为什么要在 try 块内打开光标? @Malachiasz 因为它是最小化局部变量范围的好习惯(有效的 Java 项目 #45)。委托关闭光标也是一个好主意,因为它不容易出错,而且你不能忘记调用.close【参考方案3】:

finally 中关闭光标将保证它会被关闭;

public void myfunc() 

    Cursor c = null;

    try 

      c = ... // Open cursor here
      return .... // maybe return something

     finally 
       if(c != null)  c.close(); 
     

【讨论】:

为什么要在 try 块内打开光标? try-with-resources 更加优雅【参考方案4】:

如果您使用 Kotlin(也可以在 Java 中以稍微不同的方式使用)和 Android 4.1 及更高版本,您可以使用这个:

    cursor?.use  
       //..do stuff
    

例如,您可以查看here

如果您想使用 Java,请执行以下操作:

        try (Cursor cursor = ...) 
            // do something
        

【讨论】:

【参考方案5】:

onStop()onDestroy() 活动的方法中关闭您的数据库!

【讨论】:

【参考方案6】:

您正在尝试使用

关闭 catch 块内的光标
catch (Exception e) 
            c.close();
        

但是如果你没有得到任何异常,那么它将如何关闭

所以把它放在 finally 块中

finally 
            c.close();
        

【讨论】:

哦,然后在您从查询中获得光标后尝试调用startManagingCursor() 已经这样做了,一直给出错误接缝的光标来自数据库,而不是来自活动的光标【参考方案7】:

试试这个,

onStop()onDestroy()

检查光标是否为空

@Override
public void onStop()
 Cursor cursor;

 if(cursor!=null)
  cursor.close();
 

如果没有则关闭光标。

编辑

关闭数据库对象

DatabaseObject db;
if(db!=null)
 db.close();

希望对你有帮助。

【讨论】:

哦,我明白了。尝试关闭你的数据库对象

以上是关于如何在android中正确关闭光标的主要内容,如果未能解决你的问题,请参考以下文章

如何在 Android 中正确使用 AsyncTask [关闭]

Android:如何在“强制关闭”后自动重启应用程序?

如何提高终端中的光标速度? [关闭]

如何在 android 中更改 EditText 气泡颜色(光标下)?

如何在 Android 中使用光标数据创建列表数组

如何在 Xamarin.android 中更改条目光标颜色