在Android应用开发中我们常常需要和其他应用进行交互,之前对这些问题没有仔细了解过,现在来做一下总结。
Android应用之间数据的交互方式:
- 获取系统应用的数据
- 提供数据给其他应用
- 应用之间的分享
下面介绍获取系统应用的数据
实例分析
以获取联系人数据为例,代码如下:
Cursor cursor=getContentResolver().query(ContactsContract.CommonDataKinds.Phone.CONTENT_URI,null,null,null,null);
if (cursor!=null){
while(cursor.moveToNext()){
//获取联系人的名字
String name=cursor.getString(cursor.getColumnIndex(ContactsContract.CommonDataKinds.Phone.DISPLAY_NAME));
//获取联系人的电话号码
String number=cursor.getString(cursor.getColumnIndex(ContactsContract.CommonDataKinds.Phone.NUMBER));
Log.d("============"," name: "+name+" number="+number);
}
cursor.close();
}
复制代码
ContentResolver
类的query(Uri uri,String[] projection,String selection,String[] selectionArgs,String sortOrder)
方法参数的介绍:uri
用来指明要访问的数据的位置projection
访问的列,如果为null,则表示访问所有列selection
用于指定查询条件selectionArgs
查询参数sortOrder
排序顺序
基本流程
那么ContentResolver
是怎么通过query
方法来获取联系人的数据的呢?下面我们来分析ContentResolver
的源码:
//1
public final @Nullable Cursor query(@RequiresPermission.Read @NonNull Uri uri,
@Nullable String[] projection, @Nullable String selection,
@Nullable String[] selectionArgs, @Nullable String sortOrder) {
return query(uri, projection, selection, selectionArgs, sortOrder, null);
}
//2
public final @Nullable Cursor query(@RequiresPermission.Read @NonNull Uri uri,
@Nullable String[] projection, @Nullable String selection,
@Nullable String[] selectionArgs, @Nullable String sortOrder,
@Nullable CancellationSignal cancellationSignal) {
//把查询参数封装在Bundle中
Bundle queryArgs = createSqlQueryBundle(selection, selectionArgs, sortOrder);
return query(uri, projection, queryArgs, cancellationSignal);
}
//3
public final @Nullable Cursor query(final @RequiresPermission.Read @NonNull Uri uri,
@Nullable String[] projection, @Nullable Bundle queryArgs,
@Nullable CancellationSignal cancellationSignal) {
Preconditions.checkNotNull(uri, "uri");
//获取不稳定的提供者 IContentProvider是IPC接口用来与内容提供者进行数据交互
IContentProvider unstableProvider = acquireUnstableProvider(uri);
if (unstableProvider == null) {
return null;
}
IContentProvider stableProvider = null;
Cursor qCursor = null;
try {
...//省略些不重要的源码
try {
qCursor = unstableProvider.query(mPackageName, uri, projection,
queryArgs, remoteCancellationSignal);
} catch (DeadObjectException e) {
// The remote process has died... but we only hold an unstable
// reference though, so we might recover!!! Let's try!!!!
// This is exciting!!1!!1!!!!1
unstableProviderDied(unstableProvider);
stableProvider = acquireProvider(uri);//第一次创建的是不稳定的,如果失败就重试
if (stableProvider == null) {
return null;
}
qCursor = stableProvider.query(
mPackageName, uri, projection, queryArgs, remoteCancellationSignal);
}
if (qCursor == null) {
return null;
}
// Force query execution. Might fail and throw a runtime exception here.
qCursor.getCount();//调用这个方法判断是否查询出错
long durationMillis = SystemClock.uptimeMillis() - startTime;
maybeLogQueryToEventLog(durationMillis, uri, projection, queryArgs);
// Wrap the cursor object into CursorWrapperInner object.
final IContentProvider provider = (stableProvider != null) ? stableProvider
: acquireProvider(uri);
final CursorWrapperInner wrapper = new CursorWrapperInner(qCursor, provider);//CursorWrapperInner是内部类
stableProvider = null;
qCursor = null;
return wrapper;
} catch (RemoteException e) {
// Arbitrary and not worth documenting, as Activity
// Manager will kill this process shortly anyway.
return null;
} finally {//释放资源
...
}
}
public final IContentProvider acquireUnstableProvider(Uri uri) {
if (!SCHEME_CONTENT.equals(uri.getScheme())) {//SCHEME_CONTENT为content,如果uri的scheme为null则返回null
return null;
}
String auth = uri.getAuthority();
if (auth != null) {
return acquireUnstableProvider(mContext, uri.getAuthority());//这里调用的是ApplicationContentResolver的acquireUnstableProvider方法
}
return null;
}
复制代码
在ContentResolver
的源码中query
有三个重载方法1,2,3,在实例中我们调用重载方法1,方法1中则直接调用方法2。在方法2中,封装了数据到Bundle
中,并调用了方法3,真正的实现在方法3中。方法3中的核心方法是acquireUnstableProvider(Uri uri)
,它获取IContentProvider
(一个IPC 接口),并通过IContentProvider
来获取联系人的数据。而acquireUnstableProvider(Uri uri)
调用的是acquireUnstableProvider(Context c, String name)
方法,它是个抽象方法,具体的实现类是ApplicationContentResolver
。
ApplicationContentResolver
的acquireUnstableProvider
方法源码如下:
@Override
protected IContentProvider acquireUnstableProvider(Context c, String auth) {
return mMainThread.acquireProvider(c,
ContentProvider.getAuthorityWithoutUserId(auth),
resolveUserIdFromAuthority(auth), false);
}
复制代码
在ApplicationContentResolver
的acquireUnstableProvider
方法调用了ActivityThread
的acquireProvider
方法,源码如下:
public final IContentProvider acquireProvider(
Context c, String auth, int userId, boolean stable) {
// 判断缓存中是否有对应的IContentProvider,有则直接返回
final IContentProvider provider = acquireExistingProvider(c, auth, userId, stable);
if (provider != null) {
return provider;
}
// There is a possible race here. Another thread may try to acquire
// the same provider at the same time. When this happens, we want to ensure
// that the first one wins.
// Note that we cannot hold the lock while acquiring and installing the
// provider since it might take a long time to run and it could also potentially
// be re-entrant in the case where the provider is in the same process.
ContentProviderHolder holder = null;
try {
//通过AMS获取ContentProviderHolder, 处理在不同进程的情况,
//如果在同一进程就让`holder.provider = null;`
holder = ActivityManager.getService().getContentProvider(
getApplicationThread(), auth, userId, stable);
} catch (RemoteException ex) {
throw ex.rethrowFromSystemServer();
}
//失败
if (holder == null) {
Slog.e(TAG, "Failed to find provider info for " + auth);
return null;
}
// Install provider will increment the reference count for us, and break
// any ties in the race.
//如果在同一进程,则进行相应的处理
holder = installProvider(c, holder, holder.info,
true /*noisy*/, holder.noReleaseNeeded, stable);
return holder.provider;
}
复制代码
ApplicationContentResolver
通过调用ActivityThread的acquireProvider
方法来获取ContentProvider
;ActivityThread
先会判断缓存中是否存在要求的IContentProvider
, 如果存在就直接返回,如果不存在就调用AMS的getContentProviderImpl()
和installProvider
来获取。
ContactsContract详解
ContactsContract
是存储联系人的数据表的常量字段,它有几个常用的内部类如下:
- ContactsContract.Data:数据表的常量,包含与原始联系人关联的数据点。 数据表的每一行通常用于存储单条联系信息(例如电话号码)及其相关元数据(例如,它是工作号码还是家庭号码)
- ContactsContract.CommonDataKinds:用于定义存储在ContactsContract.Data表中的公共数据类型的容器。
- ContactsContract.Contacts:联系人表的常量,包含代表同一个人的每个原始联系人聚合的记录
联系人数据是存储在数据库中(具体位置在data/data/com.android.providers.contacts/databases中),根据其MIME类型来判断其位置所代表的意义。如
ContactsContract.CommonDataKinds.Phone.NUMBER
和ContactsContract.CommonDataKinds.Email.ADDRESS
都表示data1
,但是在数据库中不同的MIME类型的data1
表示不同的数据。
数据库中data
表的字段和数据如下(字段太多,只截取了部分)
MIME类型如下
通过MIME
类型来获取所需要的数据
Uri p = ContactsContract.Data.CONTENT_URI;
Cursor id = getActivity().getContentResolver().query(p,new String[]{ContactsContract.Data.CONTACT_ID},null,null,null);
while (id.moveToNext()){
Cursor cursor=getActivity().getContentResolver().query(p, null,ContactsContract.Data.CONTACT_ID+"= ?", new String[]{id.getString(id.getColumnIndex(ContactsContract.Data.CONTACT_ID))}, null);
Person person = new Person();
byte[] image = null;
StringBuilder builder = new StringBuilder();
while (cursor.moveToNext()){
String mime = cursor.getString(cursor.getColumnIndex(ContactsContract.Data.MIMETYPE));
Log.d("===========","mime = "+mime);
if (mime.equals(ContactsContract.CommonDataKinds.Phone.CONTENT_ITEM_TYPE)){
builder.append("name = "+cursor.getString(cursor.getColumnIndex(ContactsContract.CommonDataKinds.Phone.DISPLAY_NAME)));
builder.append("number = "+cursor.getString(cursor.getColumnIndex(ContactsContract.CommonDataKinds.Phone.NUMBER)));
}else if (mime.equals(ContactsContract.CommonDataKinds.Photo.CONTENT_ITEM_TYPE)){
//二进制数据一般放在data15中
image=cursor.getBlob(cursor.getColumnIndex(ContactsContract.CommonDataKinds.Photo.DATA15));
}else if (mime.equals(ContactsContract.CommonDataKinds.Email.CONTENT_ITEM_TYPE)){
builder.append("email = "+cursor.getString(cursor.getColumnIndex(ContactsContract.CommonDataKinds.Email.ADDRESS)));
}else if (mime.equals(ContactsContract.CommonDataKinds.StructuredPostal.CONTENT_ITEM_TYPE)){
builder.append("address = "+cursor.getString(cursor.getColumnIndex(ContactsContract.
CommonDataKinds.StructuredPostal.STREET)));
}
}
String con=builder.toString();
Log.d("===========",con);
cursor.close();
}
id.close();
复制代码
ContentResolver()
还有insert
、delete
、update
方法,其用法与query
类似,这里不再介绍。
获取其他系统应用数据
CalendarContract
CalendarContract
是存储日历和事件相关信息字段表,与ContactsContract
类似。CalendarContract
也有几个内部类,常用的是CalendarContract.Events
,包含诸如事件标题,位置,开始时间,结束时间等信息。CalendarContract.Events
的使用如下:
Uri p = CalendarContract.Events.CONTENT_URI;
Cursor cursor=getActivity().getContentResolver().query(p, null,null, null, null);
while (cursor.moveToNext()){
StringBuilder builder = new StringBuilder();
builder.append("标题 : "+cursor.getString(cursor.getColumnIndex(CalendarContract.Events.TITLE))+"\n")
.append("起始时间 :"+cursor.getString(cursor.getColumnIndex(CalendarContract.Events.DTSTART))+"\n")
.append("结束时间 :"+cursor.getString(cursor.getColumnIndex(CalendarContract.Events.DTEND))+"\n")
.append("描述 : "+cursor.getString(cursor.getColumnIndex(CalendarContract.Events.DESCRIPTION))+"\n");
contents.add(builder.toString());
}
cursor.close();
复制代码
更多关于CalendarContract
的使用,可以查看官方文档。
MediaStore
MediaStore
包含内部和外部存储设备上所有可用媒体的元数据。其内部类如下:
- MediaStore.Audio:集装箱所有的音频内容。
- MediaStore.Files:媒体提供程序表,包含媒体存储中所有文件的索引,包括非媒体文件。
- MediaStore.Images:包含所有可用图像的元数据
- interface MediaStore.MediaColumns:大多数MediaProvider表的公共字段
- MediaStore.Video:包含所有可用视频的元数据
List<String> contents = new ArrayList<>();
Uri p1 = MediaStore.Images.Media.EXTERNAL_CONTENT_URI;
Cursor cursor1=getActivity().getContentResolver().query(p1, null,null, null, null);
while (cursor1.moveToNext()){
StringBuilder builder = new StringBuilder();
builder.append("文件名 : "+cursor1.getString(cursor1.getColumnIndex(MediaStore.Images.Media.TITLE))+"\n")
.append("描述 :"+cursor1.getString(cursor1.getColumnIndex(MediaStore.Images.Media.DESCRIPTION))+"\n")
.append("大小 :"+cursor1.getString(cursor1.getColumnIndex(MediaStore.Images.Media.SIZE))+"\n")
.append("位置 : "+cursor1.getString(cursor1.getColumnIndex(MediaStore.Images.Media.DATA))+"\n")
.append("文件修改时间:"+cursor1.getString(cursor1.getColumnIndex(MediaStore.Images.Media.DATE_MODIFIED))+"\n")
.append("DISPLAY_NAME :"+cursor1.getString(cursor1.getColumnIndex(MediaStore.Images.Media.DISPLAY_NAME))+"\n")
.append("时间 : "+cursor1.getString(cursor1.getColumnIndex(MediaStore.Images.Media.DATE_TAKEN))+"\n");
contents.add(builder.toString());
}
cursor1.close();
复制代码
Settings
Settings
与XXXContract
不同,它是通过xml文件来存储数据,在文件/data/system/users/0/
目录下,获取设置的方式如下:
StringBuilder builder = new StringBuilder();
ContentResolver contentResolver=getActivity().getContentResolver();
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) {
builder.append("wifi是否开启:"+Settings.Global.getString(contentResolver,Settings.Global.WIFI_ON)+"\n")
.append("数据流量是否开启:"+Settings.Global.getString(contentResolver,Settings.Global.DATA_ROAMING)+"\n")
.append(Settings.System.NOTIFICATION_SOUND+" "+Settings.System.getString(contentResolver,Settings.System.NOTIFICATION_SOUND)).append("\n")
.append(Settings.System.SCREEN_BRIGHTNESS+" "+Settings.System.getString(contentResolver,Settings.System.SCREEN_BRIGHTNESS)).append("\n")
.append(Settings.System.TEXT_SHOW_PASSWORD+" "+Settings.System.getString(contentResolver,Settings.System.TEXT_SHOW_PASSWORD)).append("\n");
}
Log.d("==============",builder.toString());
复制代码
注意:以上的操作都是需要申请权限的
异步处理
如果请求数据的操作太过耗时,可能造成ANR,因此需要异步处理。异步处理的方式有:
- Thread + Handler
- AsyncTask
- Loader
- RxJava