联系人数据库查询和显示
数据库 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”,下一篇文章将继续研究联系人存储
希望大家多多交流,不断学习和进步