Android联系人数据中的隐式连接列是如何工作的?

20

我正在查询ContactsContract.Data表以查找电话记录。

当我创建一个新的CursorLoader时,出现了错误:

java.lang.IllegalArgumentException: Invalid column deleted

我的代码:

import android.provider.ContactsContract.CommonDataKinds.Phone;
import android.provider.ContactsContract.Data;

...

String[] projection = {
    Phone.DELETED,
    Phone.LOOKUP_KEY,
    Phone.NUMBER,
    Phone.TYPE,
    Phone.LABEL,
    Data.MIMETYPE,
    Data.DISPLAY_NAME_PRIMARY
};

// "mimetype = ? AND deleted = ?"
String selection = Data.MIMETYPE + " = ? AND " Phone.DELETED + " = ?";  
String[] args = {Phone.CONTENT_ITEM_TYPE, "0"};

return new CursorLoader(
    this,
    Data.CONTENT_URI,
    projection,
    selection,
    args,
    null);
任何想法为什么 Phone.DELETED 列没有包含在游标中?文档确实说明了 -

一些与原始联系人相关的列可以通过隐式连接进行访问。


这是在多个设备之间吗? - Michael Alan Huff
@MichaelAlanHuff - 是的,我已经在两个设备上尝试过了。Android 5.0和5.1。 - Gautam
1个回答

5

看起来你找到了很多文档中都有但尚未实现的功能。我已经开了一个问题来跟踪这个问题 - 让我们看看AOSP的人对这个问题有什么说法(错误报告)。

同时,您可以使用以下解决方法:

Uri uri = ContactsContract.RawContactsEntity.CONTENT_URI;

String[] projection = {
    Phone._ID,
    Phone.DELETED,
    //Phone.LOOKUP_KEY,
    Phone.NUMBER,
    Phone.TYPE,
    Phone.LABEL,
    Data.MIMETYPE,
    Data.DISPLAY_NAME_PRIMARY
};

String selection = Data.MIMETYPE + " = ? AND " + Data.DELETED + " = ?";
String[] args = {
    Phone.CONTENT_ITEM_TYPE, "0"
};

return new CursorLoader(
this,
uri,
projection,
selection,
args,
null);

变更:

  1. 使用RawContactsEntity的URI。
  2. LOOKUP_KEY无法通过上述URI访问,如果您绝对需要此列,则必须执行其他查询。
  3. 如果要在CursorAdapter中使用结果Cursor,则需要_ID列。

编辑:根据@MichaelAlanHuff的请求,我将发布此答案所基于的代码部分

来自com.android.providers.contacts.ContactsProvider2#queryLocal()ContactsProvider2的源代码):

protected Cursor queryLocal(final Uri uri, final String[] projection, String selection,
String[] selectionArgs, String sortOrder, final long directoryId,
final CancellationSignal cancellationSignal) {


    final SQLiteDatabase db = mDbHelper.get().getReadableDatabase();

    SQLiteQueryBuilder qb = new SQLiteQueryBuilder();
    String groupBy = null;
    String having = null;
    String limit = getLimit(uri);
    boolean snippetDeferred = false;

    // The expression used in bundleLetterCountExtras() to get count.
    String addressBookIndexerCountExpression = null;

    final int match = sUriMatcher.match(uri);
    switch (match) {


        ...

        case DATA:
        case PROFILE_DATA:
            {
                final String usageType = uri.getQueryParameter(DataUsageFeedback.USAGE_TYPE);
                final int typeInt = getDataUsageFeedbackType(usageType, USAGE_TYPE_ALL);
                setTablesAndProjectionMapForData(qb, uri, projection, false, typeInt);
                if (uri.getBooleanQueryParameter(Data.VISIBLE_CONTACTS_ONLY, false)) {
                    qb.appendWhere(" AND " + Data.CONTACT_ID + " in " + Tables.DEFAULT_DIRECTORY);
                }
                break;
            }


            ...

    }



    qb.setStrict(true);

    // Auto-rewrite SORT_KEY_{PRIMARY, ALTERNATIVE} sort orders.
    String localizedSortOrder = getLocalizedSortOrder(sortOrder);
    Cursor cursor = query(db, qb, projection, selection, selectionArgs, localizedSortOrder, groupBy,
    having, limit, cancellationSignal);

    if (readBooleanQueryParameter(uri, Contacts.EXTRA_ADDRESS_BOOK_INDEX, false)) {
        bundleFastScrollingIndexExtras(cursor, uri, db, qb, selection,
        selectionArgs, sortOrder, addressBookIndexerCountExpression,
        cancellationSignal);
    }
    if (snippetDeferred) {
        cursor = addDeferredSnippetingExtra(cursor);
    }

    return cursor;


}

正如您所看到的,有两种额外的方法可以更改SQLiteQueryBuilder用于构建查询的方式:setTablesAndProjectionMapForData() 和额外的 query() 方法。

com.android.providers.contacts.ContactsProvider2#setTablesAndProjectionMapForData() 的源代码:

private void setTablesAndProjectionMapForData(SQLiteQueryBuilder qb, Uri uri,
        String[] projection, boolean distinct, boolean addSipLookupColumns, Integer usageType) {
    StringBuilder sb = new StringBuilder();
    sb.append(Views.DATA);
    sb.append(" data");

    appendContactPresenceJoin(sb, projection, RawContacts.CONTACT_ID);
    appendContactStatusUpdateJoin(sb, projection, ContactsColumns.LAST_STATUS_UPDATE_ID);
    appendDataPresenceJoin(sb, projection, DataColumns.CONCRETE_ID);
    appendDataStatusUpdateJoin(sb, projection, DataColumns.CONCRETE_ID);

    appendDataUsageStatJoin(
            sb, usageType == null ? USAGE_TYPE_ALL : usageType, DataColumns.CONCRETE_ID);

    qb.setTables(sb.toString());

    boolean useDistinct = distinct || !ContactsDatabaseHelper.isInProjection(
            projection, DISTINCT_DATA_PROHIBITING_COLUMNS);
    qb.setDistinct(useDistinct);

    final ProjectionMap projectionMap;
    if (addSipLookupColumns) {
        projectionMap =
                useDistinct ? sDistinctDataSipLookupProjectionMap : sDataSipLookupProjectionMap;
    } else {
        projectionMap = useDistinct ? sDistinctDataProjectionMap : sDataProjectionMap;
    }

    qb.setProjectionMap(projectionMap);
    appendAccountIdFromParameter(qb, uri);
}

在这里,您可以看到使用StringBuilder构建最终查询的table参数的构造方式,并将其传递给多个append*()方法。我不会发布它们的源代码,但它们确实join了出现在方法名称中的表。如果rawContacts表也加入进来,我希望在这里看到类似于appendRawContactJoin()的调用...
为了完整起见:我提到的另一个query()方法不修改table参数:
private Cursor query(final SQLiteDatabase db, SQLiteQueryBuilder qb, String[] projection,
        String selection, String[] selectionArgs, String sortOrder, String groupBy,
        String having, String limit, CancellationSignal cancellationSignal) {
    if (projection != null && projection.length == 1
            && BaseColumns._COUNT.equals(projection[0])) {
        qb.setProjectionMap(sCountProjectionMap);
    }
    final Cursor c = qb.query(db, projection, selection, selectionArgs, groupBy, having,
            sortOrder, limit, cancellationSignal);
    if (c != null) {
        c.setNotificationUri(getContext().getContentResolver(), ContactsContract.AUTHORITY_URI);
    }
    return c;
}

我检查了上述方法链,得出结论:存在一项官方文件记录的功能尚未实现。

谢谢您的回答。没想到有些东西被记录了下来,但并没有被实现。通常情况下是相反的。 :) - Gautam
顺便问一下,你有没有想法这里可能出了什么问题:https://dev59.com/9Yvda4cB1Zd3GeqPfN4Y - Gautam
2
@Gautam,我同意这很不寻常。我在源代码中来回查看了几次,因为我简直无法相信这是真的。不过请继续关注我打开的错误报告 - 也许我还是弄错了什么。 - Vasiliy
@Vasiliy,你能否发布一个链接,指出在源代码中你看到它没有被实现的地方? - Michael Alan Huff
@MichaelAlanHuff,我在答案中添加了相关的源代码部分。 - Vasiliy
@Vasiliy,太棒了。这是一个真正优秀的答案。 - Michael Alan Huff

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接