ContentProvider和ContentResolver介绍
ContentProvider 是Android的四大组件之一,提供数据的跨应用程序访问。
从ContentProvider源码看,ContentProvider是抽象类,包含6个抽象方法
public abstract class ContentProvider {
public abstract boolean onCreate();
public abstract @Nullable Cursor query(@NonNull Uri uri, @Nullable String[] projection,
@Nullable String selection, @Nullable String[] selectionArgs,
@Nullable String sortOrder);
public abstract @Nullable String getType(@NonNull Uri uri);
public abstract @Nullable Uri insert(@NonNull Uri uri, @Nullable ContentValues values);
public abstract int delete(@NonNull Uri uri, @Nullable String selection,
@Nullable String[] selectionArgs);
public abstract int update(@NonNull Uri uri, @Nullable ContentValues values,
@Nullable String selection, @Nullable String[] selectionArgs);
}
所有子类ContentProvider都要实现其中的抽象方法,并利用SQLiteDatabase,在抽象方法中实现对数据库的创建与增删改查。
我们在app里想要访问系统其他进程的ContentProvider
就需要用到ContentResolver
Context的实现类ContextImpl
的构造方法里创建了ContentResolver子类ApplicationContentResolver
,并且提供了方法“getContentResolver()”来获取这个ContentResolver
mContentResolver = new ApplicationContentResolver(this, mainThread, user);
@Override
public ContentResolver getContentResolver() {
return mContentResolver;
}
所以我们在使用中只需要拥有context,就可以获取到当前应用中的ContentResolver
而ContentResolver则提供了增删改查的接口用来访问各种ContactsProvider里的数据库
public final @Nullable Uri insert(@RequiresPermission.Write @NonNull Uri url,
@Nullable ContentValues values)
public final int delete(@RequiresPermission.Write @NonNull Uri url, @Nullable String where,
@Nullable String[] selectionArgs)
public final int update(@RequiresPermission.Write @NonNull Uri uri,
@Nullable ContentValues values, @Nullable String where,
@Nullable String[] selectionArgs)
public final @Nullable Cursor query(final @RequiresPermission.Read @NonNull Uri uri,
@Nullable String[] projection, @Nullable Bundle queryArgs,
@Nullable CancellationSignal cancellationSignal)
即只需要有context,就可以获取到当前应用的ContentResolver
查询通话记录
通话记录的数据库由CallLogProvider提供
文件数据库存储路径:/data/data/com.android.providers.contacts/databases/calllog.db
- 通话记录的Uri
由于CallLogProvider注册时的authorities
是"call_log",
所以对应的Uri就是
public static final String AUTHORITY = "call_log";
public static final Uri CONTENT_URI =
Uri.parse("content://" + AUTHORITY);
也即
Uri.parse("content://call_log");
通常Uri后面还要跟上表的名字,所以具体通话记录的Uri路径就是
Uri.parse("content://call_log/calls")
这是个常量,定义在CallLog.java的内部类Calls里
CallLog.Calls.CONTENT_URI = Uri.parse("content://call_log/calls");
- 查询通话记录的语法格式
mContext.getContentResolver().query(
CallLog.Calls.CONTENT_URI, //Uri
null,//projection
null,//selection
null,//selectionArgs
null,//sortOrder
)
- Calllog数据库的列的序号
public static final int ID = 0;
public static final int NUMBER = 1;
public static final int DATE = 2;
public static final int DURATION = 3;
public static final int CALL_TYPE = 4;
public static final int COUNTRY_ISO = 5;
public static final int VOICEMAIL_URI = 6;
public static final int GEOCODED_LOCATION = 7;
public static final int CACHED_NAME = 8;
public static final int CACHED_NUMBER_TYPE = 9;
public static final int CACHED_NUMBER_LABEL = 10;
public static final int CACHED_LOOKUP_URI = 11;
public static final int CACHED_MATCHED_NUMBER = 12;
public static final int CACHED_NORMALIZED_NUMBER = 13;
public static final int CACHED_PHOTO_ID = 14;
public static final int CACHED_FORMATTED_NUMBER = 15;
public static final int IS_READ = 16;
public static final int NUMBER_PRESENTATION = 17;
public static final int ACCOUNT_COMPONENT_NAME = 18;
public static final int ACCOUNT_ID = 19;
public static final int FEATURES = 20;
public static final int DATA_USAGE = 21;
public static final int TRANSCRIPTION = 22;
public static final int CACHED_PHOTO_URI = 23;
- Dialer 中封装的CallLogQueryHandler
在Dialer 这个系统app里,封装了一个CallLogQueryHandler
,用于异步查询通话记录。
public CallLogQueryHandler(
Context context, ContentResolver contentResolver, Listener listener, int limit)
构造方法需要传入四个参数 context,contentResolver,CallLogQueryHandler的Listener,以及limit查询的最大数目
用法:
callLogQueryHandler.fetchCalls(CallLogQueryHandler.CALL_TYPE_ALL,-1);
其中两个参数代表的是通话类型,和最新时间,这个时间意思是得到比这个时间值更新的数据。
返回的结果就是一个Cursor,会在Listener的回调方法中返回一个不为空的游标。
@Override
public boolean onCallsFetched(Cursor callLogCursor){
}
查询联系人
联系人的数据库由ContactsProvider2提供
文件数据库存储路径:/data/data/com.android.providers.contacts/databases/contacts2.db
- 联系人的Uri
由于联系人注册的authorities
是contacts;com.android.contacts
所以对应的Uri就是
public static final String AUTHORITY = "com.android.contacts";
/** A content:// style uri to the authority for the contacts provider */
public static final Uri AUTHORITY_URI = Uri.parse("content://" + AUTHORITY);
即
Uri.parse("content://com.android.contacts");
通常Uri后面还要加上表格的名字,
然而contact的数据库是分多个表格组成的,一个表格只含有一部分信息,电话号码与显示名称不在同一个表格里。
public interface Tables {
public static final String CONTACTS = "contacts";
public static final String DELETED_CONTACTS = "deleted_contacts";
public static final String RAW_CONTACTS = "raw_contacts";
public static final String STREAM_ITEMS = "stream_items";
public static final String STREAM_ITEM_PHOTOS = "stream_item_photos";
public static final String PHOTO_FILES = "photo_files";
public static final String PACKAGES = "packages";
public static final String MIMETYPES = "mimetypes";
public static final String PHONE_LOOKUP = "phone_lookup";
public static final String NAME_LOOKUP = "name_lookup";
public static final String AGGREGATION_EXCEPTIONS = "agg_exceptions";
public static final String SETTINGS = "settings";
public static final String DATA = "data";
public static final String GROUPS = "groups";
public static final String PRESENCE = "presence";
public static final String AGGREGATED_PRESENCE = "agg_presence";
public static final String NICKNAME_LOOKUP = "nickname_lookup";
public static final String STATUS_UPDATES = "status_updates";
public static final String ACCOUNTS = "accounts";
public static final String VISIBLE_CONTACTS = "visible_contacts";
public static final String DIRECTORIES = "directories";
public static final String DEFAULT_DIRECTORY = "default_directory";
public static final String SEARCH_INDEX = "search_index";
public static final String METADATA_SYNC = "metadata_sync";
public static final String METADATA_SYNC_STATE = "metadata_sync_state";
public static final String PRE_AUTHORIZED_URIS = "pre_authorized_uris";
....
}
这些表格中包含显示名称的表格是RAW_CONTACTS = "raw_contacts"
包含电话号码的表格是DATA = "data"
联系人的数据序号信息所在表格是CONTACTS = "contacts"
所以对应的Uri分别是
Uri.parse("content://com.android.contacts/raw_contacts");
Uri.parse("content://com.android.contacts/data");
Uri.parse("content://com.android.contacts/contacts");
而这些Uri也是常量,定义在ContactsContract.java
里
分别是
ContactsContract.RawContacts.CONTENT_URI = Uri.withAppendedPath(AUTHORITY_URI, "raw_contacts");
ContactsContract.Data.CONTENT_URI = Uri.withAppendedPath(AUTHORITY_URI, "data");
ContactsContract.Contacts.CONTENT_URI = Uri.withAppendedPath(AUTHORITY_URI, "contacts");
- 查询联系人的语法格式
mContext.getContentResolver().query(
uri, //Uri
null,//projection
null,//selection
null,//selectionArgs
null,//sortOrder
)
其中Uri需要指明具体的Uri,由于每个表的数据结构比较多,所以每个表的列的索引信息需要在projection
,selection
,selectionArgs
,sortOrder
这些参数里进行精确匹配
- 举例根据号码查询联系人名称
String[] projection = { Phone.DISPLAY_NAME, Phone.NUMBER};
Cursor cursor = resolver.query(Phone.CONTENT_URI, projection, Phone.NUMBER + "=?",
new String[]{number}, null);
由于我们需要根据号码来查询,所以我们要查询的Uri是
ContactsContract.Data.CONTENT_URI = Uri.withAppendedPath(AUTHORITY_URI, "data");
然后,由于ContactsProvider2
在data 后面还新建了一些Uri,比如data/phones
,
matcher.addURI(ContactsContract.AUTHORITY, "data/phones", PHONES);
并且指向了Data.DATA1
即DATA1 = "data1"
这一列,就是data1这一列存储的是电话号码
所以我们查询的时候,Uri就可以指向
Phone.CONTENT_URI = Uri.withAppendedPath(Data.CONTENT_URI,"phones")
查询的结果就是data
表中的data1
那一列中的号码,返回的游标就是匹配了电话号码的游标,
而这个游标同时可以得到联系人显示名称,
String name = cursor.getString(cursor.getColumnIndex(Phone.DISPLAY_NAME));
于是name就是对应的DISPLAY_NAME
,有个要注意的地方,这里保存的号码是经过格式化的,11位号码中间有空格。
联系人的数据库构造较复杂,所以由很多Uri,不过只要找对了Uri,就可以查询到我们想要的信息。
查询短信与彩信
短信和彩信数据库路径
/data/data/com.android.providers.telephony/databases/mmssms.db
MmsProvider
SmsProvider
MmsSmsProvider
他们三个ContentProvider都是操作的同一个数据库mmssms.db
- Uri
短信:CONTENT_URI = Uri.parse("content://sms")
彩信:CONTENT_URI = Uri.parse("content://mms")
短信和彩信:CONTENT_URI = Uri.parse("content://mms-sms/")
他们都定义在Telephony.java
Telephony.Sms.CONTENT_URI
Telephony.Mms.CONTENT_URI
Telephony.MmsSms.CONTENT_URI
- 查询未读短信的语法格式
Cursor smscursor = mContentResolver.query(Telephony.Sms.CONTENT_URI, null,
"read = 0", null, null);
其中 "read = 0"
表示未读状态
同样,彩信的也是如此,换个Uri就可以了。
查询到的cursor,可以得到发件人地址,信息内容,以及时间,唯独没有号码对应的联系人名称。
String number = cursor.getString(cursor.getColumnIndex("address"));
String content= cursor.getString(cursor.getColumnIndex("body"));
long time = cursor.getLong(cursor.getColumnIndex("date"));
同样,彩信也包含这些内容,不过更为复杂,具体的要看mms的数据库结构。
总结
经过以上分析,基本上根据contentresolver 查询 通话记录,联系人信息,短信,就都可以很快搞定了,根据Uri,再结合projection
,selection
,就可以查询到想要的具体数据,再根据表格的列来定位即可得到想要的内容。