【问题标题】:Cursor.getType() for API Level <11API 级别 <11 的 Cursor.getType()
【发布时间】:2012-07-24 09:17:07
【问题描述】:

我正在查询 CallLog 内容提供程序,需要检测列类型。

在 Honeycomb 和更新版本(API 级别 11+)中,您可以通过调用返回以下类型之一的方法 Cursor.getType(int columnIndex) 来获取列首选数据类型:

  • FIELD_TYPE_NULL (0)
  • FIELD_TYPE_INTEGER (1)
  • FIELD_TYPE_FLOAT (2)
  • FIELD_TYPE_STRING (3)
  • FIELD_TYPE_BLOB (4)

我如何在Honeycomb

我尝试了以下方法:

for ( int i = 0; i < cursor.getColumnCount(); i++ ) {    

    int columnType = -1;
    try {
        cursor.getInt( i );
        columnType = Cursor.FIELD_TYPE_INTEGER;

    } catch ( Exception ignore ) {

        try {
            cursor.getString( i );
            columnType = Cursor.FIELD_TYPE_STRING;

        } catch ( Exception ignore1 ) {

            try {
                cursor.getFloat( i );
                columnType = Cursor.FIELD_TYPE_FLOAT;

            } catch ( Exception ignore2 ) {

                try {                                             
                  cursor.getBlob( i );
                  columnType = Cursor.FIELD_TYPE_BLOB;

                } catch ( Exception ignore3 ) {

                     columnType = Cursor.FIELD_TYPE_NULL;
                }
           }
       }
   }

}

但是,不会引发异常。数据始终以您要检查的第一种类型进行转换,在本例中为 getInt()。这意味着,如果列类型为 Integer 但对于所有其他类型为 0,我会得到正确的值。

为什么我不查看文档来检查存储的类型? 这些列因设备制造商而异,并非所有列都记录在案,请参阅此问题:How to handle manufacturer-dependent differences in ContentProviders?

有什么想法吗?

【问题讨论】:

  • 你能对 SQLite 的元数据表执行 SELECT 吗?
  • 我正在与官方通话记录内容提供商合作。 AFAIK 你不能 rawquery、pragma 或请求这些表的元数据。似乎您已绑定到 ContentResolver.query() 方法...
  • 你试过这个技巧吗? stackoverflow.com/questions/6145876/…
  • 我认为 hack 不能应用于我的问题,因为我处理的不是原始 SQLite 数据库,而是内容提供者。还是我错过了什么?
  • 你真的不知道你的列包含什么吗?

标签: android android-contentprovider android-cursor


【解决方案1】:

有些东西可能会起作用: http://developer.android.com/reference/android/database/DatabaseUtils.html cursorRowToContentValues

将复制 ContentValues 对象中的行。然后,您可以调用 ContentValues.get(),它会为您提供一个对象。然后你可以查看这个对象的类。

编辑

根据DatabaseUtils的源码,对象要么是blob,要么是String。

编辑 2

但是,如果您的光标是 WindowedCursor,则它具有了解对象类型的方法。 (isBlob, isString, isLong...)

【讨论】:

  • 我只是仔细检查了source code,如果光标不是AbstractWindowedCursor的实例,该方法总是返回STRING。所以我尝试将内容提供程序光标转换为 AbstractWindowedCursor,但我得到了一个 ClassCastException。所以不可能吗?
  • 似乎并非如此。尽管如此,您还是应该知道您的数据是什么。 CallLog contentprovider 已记录在案,我认为它可以为您提供获取数据所需的知识
  • 如果我知道列类型,我不会问这个问题。 a) 并非所有内容提供者都记录在案(例如 SMS)。 b)查看我的链接问题(http://stackoverflow.com/questions/11504654/how-to-handle-manufacturer-dependent-differences-in-contentproviders)。这些列因设备而异,并且没有共同的子集。
【解决方案2】:

我过去也遇到过同样的问题。我用一个非常好的解决方案解决了这个问题。 将此与您的需求相匹配。 就我而言,我有大量不同的对象,它们都同步到云中的服务器。它们都有共同的属性,因此它们都继承自一个共同的 BaseObject。 这个对象有一个方法,它接受一个游标作为参数并返回一个相同类型的新对象,因此从它继承的每个对象都用它的扩展属性覆盖这个方法。

*请注意,这种方法不需要继承对象。这只是一种更聪明的方法。只要您在需要从 DB 中获取的所有对象中使用相同的方法,这将起作用,您最终将能够看到。

让我来说明一下:

我们的基础对象。

public class BaseObject{

    protected int number;
    protected String text;

    public <T extends BaseObject> T setObject(Cursor c) {
        number = c.getInt(cur.getColumnIndexOrThrow(COLUMN_NAME_FOR_NUMBER));
        text = c.getString(cur.getColumnIndexOrThrow(COLUMN_NAME_FOR_TEXT));

        return (T) this;
    }
}

从第一个继承的新对象。

public class Contact extends BaseObject{

    private String name;

    @Override
    public <T extends BaseObject> T setObject(Cursor c) {

        super.setObject(c);

        name = c.getString(cur.getColumnIndexOrThrow(COLUMN_NAME_FOR_NAME));

        return (T) this;
    }
}

最后,在您的数据库中,通过调用通用方法“getAllObjects”并传递您想要查询的类类型以及查询的其他参数,就可以很容易地询问您想要的数据:

public synchronized <T extends BaseObject> ArrayList<T> getObjectsForClass(final Class<T> classType,
        String selection, String[] selectionArgs, String sort, String limit) {

    ArrayList<T> objects = null;

    if (db == null || !db.isOpen()) {
        db = getWritableDatabase();
    }

    objects = new ArrayList<T>();

    Cursor c = null;
    T object;
    try {
        object = classType.newInstance();

        String table = object.getTable();

        StringBuilder tableSb = new StringBuilder();
        tableSb.append(table).append(" INNER JOIN ").append(Constants.DB_BASE_OBJECT_TABLE)
                .append(" ON ").append(table).append(".").append(BaseObject.DB_OBJECT_ID_KEY).append(" = ")
                .append(Constants.DB_BASE_OBJECT_TABLE).append(".")
                .append(BaseObject.DB_ID_KEY);

        c = db.query(tableSb.toString(), null, selection, selectionArgs, null, null, sort, limit);

        if (c.getCount() > 0) {
            c.moveToFirst();
            while (!c.isAfterLast()) {

                object = classType.newInstance();
                object.setObject(c);
                objects.add(object);

                c.moveToNext();
            }
        }

    } catch (InstantiationException e) {
        e.printStackTrace();
    } catch (IllegalAccessException e) {
        e.printStackTrace();
    }
    c.close();

    return objects;
}

然后就可以了。一种从数据库中获取任何对象并在运行时成功地将其转换为对象或对象数组的通用方法。

注意事项:

  • 每个对象都应该有一个方法 getTable() 以便能够查询正确的表
  • 如您所见,在这种方法中还有一个 OODB 连接。您可以使用相同的方法,只需查询所有项目 (SELECT * FROM...)

希望对您有所帮助。回答问题或疑问。

【讨论】:

  • 感谢您的详尽回答,但它根本没有解决我的问题。您的代码有助于使用单个通用函数查询数据库并返回指定类型的对象。我想找出数据方案,即内容提供者的列名和类型......如果我错过了链接,请纠正我。
  • 当然,但是如果您将对象类作为参数传递,您将已经知道返回列的类型,因此您无需检查每个列的类型。我之所以选择这种方法,是因为我遇到了同样的问题,并且想让我的应用从 8 开始就兼容。
【解决方案3】:

当光标位于有效行时,您可以使用此代码:

CursorWrapper cw = (CursorWrapper)cursor;

Class<?> cursorWrapper = CursorWrapper.class;
Field mCursor = cursorWrapper.getDeclaredField("mCursor");
mCursor.setAccessible(true);
AbstractWindowedCursor abstractWindowedCursor = (AbstractWindowedCursor)mCursor.get(cw);
CursorWindow cursorWindow = abstractWindowedCursor.getWindow();
int pos = abstractWindowedCursor.getPosition();
for ( int i = 0; i < cursor.getColumnCount(); i++ ) {
    String type = null;
    if (cursorWindow.isNull(pos, i)) {
        type = "Cursor.FIELD_TYPE_NULL";
    } else if (cursorWindow.isLong(pos, i)) {
        type = "Cursor.FIELD_TYPE_INTEGER";
    } else if (cursorWindow.isFloat(pos, i)) {
        type = "Cursor.FIELD_TYPE_FLOAT";
    } else if (cursorWindow.isString(pos, i)) {
        type = "Cursor.FIELD_TYPE_STRING";
    } else if (cursorWindow.isBlob(pos, i)) {
        type = "Cursor.FIELD_TYPE_BLOB";
    }
}

请注意,Cursor.FIELD_TYPE_* 常量值是从 HONEYCOMB 开始定义的。

【讨论】:

  • 感谢@Juan,我已接受这是最佳答案。但是,我想再次强调,此解决方案确实仅在光标位于有效行中时才有效。这是因为 SQLite 是动态类型的,这意味着值的数据类型与值本身相关联,而不是与其容器相关联。
  • 我刚刚意识到 API 级别 11 中的 Cursor.getType() 的行为方式完全相同:没有数据,没有列类型。所以这个答案是完全有效的。
  • 我正在为此苦苦挣扎。出于某种原因,我在这一行得到了 ClassCastException CursorWrapper cw = (CursorWrapper)c;
  • @DiscoS2 请注意Cursor 是一个接口,因此当cursor 对象是CursorWrapper 类或其子类的实例时,此解决方案将起作用。如果您的情况与本文中的情况不同(CallLog 内容提供商查询),那么创建另一篇解释它的帖子可能是一个更好的主意。
  • @JuanSánchez 在我的情况下,我得到了例外,因为 Cursor 是 net.sqlcipher.CrossProcessCursorWrapper 的一个实例,从名称上看它似乎是 CursorWrapper 的子类,不是吗? (定义为public class CrossProcessCursorWrapper extends CursorWrapper implements CrossProcessCursor
【解决方案4】:

扩展 Juan 的回答,这里是我对 API 11 方法 Cursor.getType(int i) 的替换 - 用于由 SQL 查询重新调整的游标

public class DbCompat {

    protected static final int FIELD_TYPE_BLOB = 4;
    protected static final int FIELD_TYPE_FLOAT = 2;
    protected static final int FIELD_TYPE_INTEGER = 1;
    protected static final int FIELD_TYPE_NULL = 0;
    protected static final int FIELD_TYPE_STRING = 3;

    static int getType(Cursor cursor, int i) throws Exception {
        SQLiteCursor sqLiteCursor = (SQLiteCursor) cursor;
        CursorWindow cursorWindow = sqLiteCursor.getWindow();
        int pos = cursor.getPosition();
        int type = -1;
        if (cursorWindow.isNull(pos, i)) {
            type = FIELD_TYPE_NULL;
        } else if (cursorWindow.isLong(pos, i)) {
            type = FIELD_TYPE_INTEGER;
        } else if (cursorWindow.isFloat(pos, i)) {
            type = FIELD_TYPE_FLOAT;
        } else if (cursorWindow.isString(pos, i)) {
            type = FIELD_TYPE_STRING;
        } else if (cursorWindow.isBlob(pos, i)) {
            type = FIELD_TYPE_BLOB;
        }

        return type;
    }
}

要点:https://gist.github.com/kassim/c340cbfc5243db3a4826

【讨论】:

  • 为什么getType方法会抛出异常?
  • 这个非常适合 Android 上的 SQLite,在执行原始查询时,例如喜欢Cursor cursor = msadb.rawQuery(sql, ...)
猜你喜欢
  • 1970-01-01
  • 2012-07-22
  • 2011-11-02
  • 1970-01-01
  • 2011-11-04
  • 1970-01-01
  • 1970-01-01
  • 2013-02-26
  • 1970-01-01
相关资源
最近更新 更多