联系人数据库查询和显示

联系人数据库查询和显示

数据库 SQLite

简单来说 SQLite 就是轻量级数据库特别适合嵌入式操作系统 ,如果数据库不懂没关系,只要会最基本的就可以了,每次看到了就自己查资料,这里讲用到的基本数据库操作和使用

SQLiteOpenHelper 介绍

源码和 google 介绍,SQLiteOpenHelper 主要功能是 创建、升级、打开数据库和获取数据库对象
如果想使用 SQLiteOpenHelper 必须继承类,重写方法实现功能


// 初始化数据库 
    public SQLiteOpenHelper(Context context, String name, CursorFactory factory, int version) {
        this(context, name, factory, version, null);
    }

// 获取不同权限的数据库的对象 可写和可读 (可写包含可读)

    public SQLiteDatabase getWritableDatabase() {
        synchronized (this) {
            return getDatabaseLocked(true);
        }
    }


    public SQLiteDatabase getReadableDatabase() {
        synchronized (this) {
            return getDatabaseLocked(false);
        }
    }

// 第一次创建数据库的时候执行
    public abstract void onCreate(SQLiteDatabase db);
// 数据库根据版本升级
    public abstract void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion);
//打开数据库
    public void onOpen(SQLiteDatabase db) {}

详细介绍上面的方法,当子类继承 SQLiteOpenHelper ,会传递context 数据库名称name CursorFactory 一般使用默认 传值null,version很重要,涉及到会不会升级数据库。 当我们获取数据库对象的时候,如果版本高于原先版本就会自己执行 onUpgrade 。

当new 一个SQLiteOpenHelper 的子类,就会执行oncreate 方法 一般我们在这里进行创建表
db.execSQL(sql); sql 指的是sql语句

(详细的数据库原理我们也可以在后面探究下)

ContentResolver 介绍

ContentResolver 可以获取数据库,进行原语操作,ACID 即增删改查

//数据库查询
Cursor query (Uri uri, 
                String[] projection, 
                String selection, 
                String[] selectionArgs, 
                String sortOrder);

//插入数据
Uri insert (Uri url, 
                ContentValues values);

//删除数据
int delete (Uri url, 
                String where, 
                String[] selectionArgs);

//更新数据库
int update (Uri uri, 
                ContentValues values, 
                String where, 
                String[] selectionArgs);

需要解释的是 添加的参数的意义 Uri 代表需要解析的数据 统一资源标识符
projection 是所有需要查询的列表
selection 数据库查询条件
selectionArgs 数据库查询需要替换的值
sortOrder 只选出来的排序方法 null 就是使用默认Desc
ContentValues 类似hashtable 但是只能存存数据不能存储对象

有了基本概念,就可以接下来分析,数据库还有很多知识点

startQuery 分析

    case EVENT_ARG_QUERY:
        Cursor cursor;
        try {

            cursor = resolver.query(args.uri, args.projection,
                    args.selection, args.selectionArgs,
                    args.orderBy);
        } 

上一篇文章 关于InCallUI的联系人显示 讲述这里是查询数据库的关键,最后得到cursor对象

从log 中看到所有的查询条件:

url=content://com.android.contacts/phone_lookup_enterprise/12345678?sip=false projection=[[contact_id, display_name, lookup, number, normalized_number, label, type, photo_uri, custom_ringtone, send_to_voicemail]]
selection=null
args=[null]

所以我们分析查询条件是如何封装的:
com.android.incallui.CallerInfoAsyncQuery#startDefaultDirectoryQuery
创建uri,我们继续跟进代码,在下面的代码片段很容易发现其实是固定的字符串

    private static CallerInfoAsyncQuery startDefaultDirectoryQuery(int token, Context context,
            CallerInfo info, OnQueryCompleteListener listener, Object cookie) {
        // Construct the URI object and query params, and start the query.
        Uri uri = ContactInfoHelper.getContactInfoLookupUri(info.phoneNumber);
        return startQueryInternal(token, context, info, listener, cookie, uri);
    }


    Uri uri = PhoneLookup.ENTERPRISE_CONTENT_FILTER_URI;
public static final Uri ENTERPRISE_CONTENT_FILTER_URI = Uri.withAppendedPath(AUTHORITY_URI,
        "phone_lookup_enterprise");

而这里的 withAppendedPath 就是将 AUTHORITY_URI 和 “phone_lookup_enterprise”拼接
我们可以查看 AUTHORITY_URI (主机名地址),又由AUTHORITY组成(俗称主机名),“content://”被成为约束scheme,很容易 拼接到这里是“content://com.android.contacts”

public static final Uri AUTHORITY_URI = Uri.parse("content://" + AUTHORITY);

public static final String AUTHORITY = "com.android.contacts";

所以最后 ENTERPRISE_CONTENT_FILTER_URI 拼接字符串 为“content://com.android.contacts/phone_lookup_enterprise” 当返回给uri时候将 12345678?sip=false 信息拼接上去,log显示最后的结果

再来看看projection ,其实直接就是使用 com.android.incallui.CallerInfo#BACKWARD_COMPATIBLE_NON_SIP_DEFAULT_PHONELOOKUP_PROJECTION 已经定义的数组

    // In pre-N, contact id is stored in {@link PhoneLookup._ID} in non-sip query.
    private static final String[] BACKWARD_COMPATIBLE_NON_SIP_DEFAULT_PHONELOOKUP_PROJECTION =
            new String[] {
                    PhoneLookup._ID,
                    PhoneLookup.DISPLAY_NAME,
                    PhoneLookup.LOOKUP_KEY,
                    PhoneLookup.NUMBER,
                    PhoneLookup.NORMALIZED_NUMBER,
                    PhoneLookup.LABEL,
                    PhoneLookup.TYPE,
                    PhoneLookup.PHOTO_URI,
                    PhoneLookup.CUSTOM_RINGTONE,
                    PhoneLookup.SEND_TO_VOICEMAIL
    };

其他的条件都是null 没啥分析的

接下来我们关注 查询出来的cursor ,最后放在哪里处理

根据 上一篇文章 关于InCallUI的联系人显示的流程我们会在

com.android.incallui.CallerInfoAsyncQuery.CallerInfoAsyncQueryHandler#onQueryComplete

protected void onQueryComplete(int token, Object cookie, Cursor cursor) {
    mCallerInfo = CallerInfo.getCallerInfo(mQueryContext, mQueryUri, cursor);

    //notify the listener that the query is complete.
    if (cw.listener != null) {

        cw.listener.onQueryComplete(token, cw.cookie, mCallerInfo);
    }


}

getCallerInfo 会根据cursor 获取 联系人信息
然后 继续通过listener 回调onQueryComplete 传递mCallerInfo

现在分析 com.android.incallui.CallerInfo#getCallerInfo(android.content.Context, android.net.Uri, android.database.Cursor)

    public static CallerInfo getCallerInfo(Context context, Uri contactRef, Cursor cursor) {
        //初始化 info的信息
        CallerInfo info = new CallerInfo();
        info.photoResource = 0;
        info.phoneLabel = null;
        info.numberType = 0;
        info.numberLabel = null;
        info.cachedPhoto = null;
        info.isCachedPhotoCurrent = false;
        info.contactExists = false;
        info.userType = ContactsUtils.USER_TYPE_CURRENT;

        Log.v(TAG, "getCallerInfo() based on cursor...");
        //数据库开始查询
        if (cursor != null) {
            //游标 移动到第一个地址
            if (cursor.moveToFirst()) {

                long contactId = 0L;
                int columnIndex;

                // Look for the name
                columnIndex = cursor.getColumnIndex(PhoneLookup.DISPLAY_NAME);
                if (columnIndex != -1) {
                    info.name = cursor.getString(columnIndex);
                }

                // Look for the number
                columnIndex = cursor.getColumnIndex(PhoneLookup.NUMBER);
                if (columnIndex != -1) {
                    info.phoneNumber = cursor.getString(columnIndex);
                }

                // Look for the normalized number
                columnIndex = cursor.getColumnIndex(PhoneLookup.NORMALIZED_NUMBER);
                if (columnIndex != -1) {
                    info.normalizedNumber = cursor.getString(columnIndex);
                }

                // Look for the label/type combo
                columnIndex = cursor.getColumnIndex(PhoneLookup.LABEL);
                if (columnIndex != -1) {
                    int typeColumnIndex = cursor.getColumnIndex(PhoneLookup.TYPE);
                    if (typeColumnIndex != -1) {
                        info.numberType = cursor.getInt(typeColumnIndex);
                        info.numberLabel = cursor.getString(columnIndex);
                        info.phoneLabel = Phone.getTypeLabel(context.getResources(),
                                info.numberType, info.numberLabel)
                                .toString();
                    }
                }

                // Look for the person_id.
                columnIndex = getColumnIndexForPersonId(contactRef, cursor);
                if (columnIndex != -1) {
                    contactId = cursor.getLong(columnIndex);
                    // QuickContacts in M doesn't support enterprise contact id
                    if (contactId != 0 && (ContactsUtils.FLAG_N_FEATURE
                            || !Contacts.isEnterpriseContactId(contactId))) {
                        info.contactIdOrZero = contactId;
                        Log.v(TAG, "==> got info.contactIdOrZero: " + info.contactIdOrZero);

                        // cache the lookup key for later use with person_id to create lookup URIs
                        columnIndex = cursor.getColumnIndex(PhoneLookup.LOOKUP_KEY);
                        if (columnIndex != -1) {
                            info.lookupKeyOrNull = cursor.getString(columnIndex);
                        }
                    }
                } else {
                    // No valid columnIndex, so we can't look up person_id.
                    Log.v(TAG, "Couldn't find contactId column for " + contactRef);
                    // Watch out: this means that anything that depends on
                    // person_id will be broken (like contact photo lookups in
                    // the in-call UI, for example.)
                }

                // Display photo URI.
                columnIndex = cursor.getColumnIndex(PhoneLookup.PHOTO_URI);
                if ((columnIndex != -1) && (cursor.getString(columnIndex) != null)) {
                    info.contactDisplayPhotoUri = Uri.parse(cursor.getString(columnIndex));
                } else {
                    info.contactDisplayPhotoUri = null;
                }

                // look for the custom ringtone, create from the string stored
                // in the database.
                columnIndex = cursor.getColumnIndex(PhoneLookup.CUSTOM_RINGTONE);
                if ((columnIndex != -1) && (cursor.getString(columnIndex) != null)) {
                    if (TextUtils.isEmpty(cursor.getString(columnIndex))) {
                        // make it consistent with frameworks/base/.../CallerInfo.java
                        info.contactRingtoneUri = Uri.EMPTY;
                    } else {
                        info.contactRingtoneUri = Uri.parse(cursor.getString(columnIndex));
                    }
                } else {
                    info.contactRingtoneUri = null;
                }

                // look for the send to voicemail flag, set it to true only
                // under certain circumstances.
                columnIndex = cursor.getColumnIndex(PhoneLookup.SEND_TO_VOICEMAIL);
                info.shouldSendToVoicemail = (columnIndex != -1) &&
                        ((cursor.getInt(columnIndex)) == 1);
                info.contactExists = true;

                // Determine userType by directoryId and contactId
                final String directory = contactRef == null ? null
                        : contactRef.getQueryParameter(ContactsContract.DIRECTORY_PARAM_KEY);
                final Long directoryId = directory == null ? null : Longs.tryParse(directory);
                info.userType = ContactsUtils.determineUserType(directoryId, contactId);

                info.nameAlternative = ContactInfoHelper.lookUpDisplayNameAlternative(
                        context, info.lookupKeyOrNull, info.userType);
            }
            cursor.close();
        }

        info.needUpdate = false;
        info.name = normalize(info.name);
        info.contactRefUri = contactRef;

        return info;
    }

上面很容易看懂,查询数据,12345678是“C”,下一篇文章将继续研究联系人存储

author:james
联系方式:[email protected]

希望大家多多交流,不断学习和进步


你可能感兴趣的:(Android)