在Android官方指出的Android的数据存储方式总共有五种,分别是:Shared Preferences、网络存储、文件存储、外储存储、SQLite。但是我们知道一般这些存储都只是在单独的一个应用程序之中达到一个数据的共享,有时候我们需要操作其他应用程序的一些数据,例如我们需要操作系统里的媒体库、通讯录等,这时我们就可能通过ContentProvider来满足我们的需求了
ContentProvider 在android中的作用是对外共享数据,提供了数据访问接口,用于在不同应用程序之间共享数据,同时还能保证被访问数据的安全性,它,也就是说你可以通过ContentProvider把应用中的数据共享给其他应用访问,其他应用可以通过ContentProvider 对你应用中的数据进行添删改查。
关于数据共享,以前我们学习过文件操作模式,知道通过指定文件的操作模式为Context.MODE_WORLD_READABLE 或Context.MODE_WORLD_WRITEABLE同样也可以对外共享数据。那么,这里为何要使用ContentProvider 对外共享数据呢?是这样的,如果采用文件操作模式对外共享数据,数据的访问方式会因数据存储的方式而不同,导致数据的访问方式无法统一,如:采用xml文件对外共享数据,需要进行xml解析才能读取数据;采用sharedpreferences共享数据,需要使用sharedpreferences API读取数据。使用ContentProvider对外共享数据的好处是统一了数据的访问方式。
而且与文件存储和SharedPreference这两种全局可读可写操作模式不同,contentprovider可以选择只对哪一部分数据进行共享,从而保证程序中的隐私数据不会被泄露。
而且与SQLiteDatabase不同,ContentProvider中的CRUD不接收表名参数,而是Uri参数。内容URI是内容提供器中数据的唯一标识符,包括权限和路径。权限Authority用于唯一标识这个ContentProvider,对不同应用程序做区分,外部调用者可以根据这个标识来找到它。它定义了是哪个Content Provider提供这些数据。对于第三方应用程序,为了保证URI标识的唯一性,它必须是一个完整的、小写的类名。一般为了保证唯一性,避免冲突,是定义成该ContentProvider的包.类的名称
路径(path),通俗的讲就是你要操作的数据库中表的名字,或者你也可以自己定义。
当应用需要通过ContentProvider对外共享数据时,第一步需要继承ContentProvider并重写下面方法:
public class PersonContentProvider extends ContentProvider{
public boolean onCreate()
public Uri insert(Uri uri, ContentValues values)
public int delete(Uri uri, String selection, String[] selectionArgs)
public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs)
public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder)
public String getType(Uri uri)}
第二步需要在AndroidManifest.xml使用<provider>对该ContentProvider进行配置,为了能让其他应用找到该ContentProvider , ContentProvider 采用了authorities(主机名/域名)对它进行唯一标识,你可以把 ContentProvider看作是一个网站(想想,网站也是提供数据者),authorities 就是他的域名:
<manifest .... >
<application android:icon="@drawable/icon" android:label="@string/app_name">
<provider android:name=".PersonContentProvider" android:authorities="cn.itcast.providers.personprovider"/>
</application>
</manifest>
调用ContentResolver的query()、insert()、delete()等方法,实质是调用了ContentProvider的响应的方法
ContentProvider是android组件之一,可以提供数据的跨应用程序访问,提供数据的跨进程无缝隙访问,所以是非常重要的东东。使用方法一般是
代码:
getContentResolver().query(Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder);
1. 在应用程序A里面怎么跨进程拿到ContentProvider的对象呢?
2. ContentProvider实例对象是保存在哪里呢?
3. ContentProvider的方法实现要注意线程安全吗?
如果你能很清晰的回答这几个问题,那么下面的你就不需要继续看了,如果还有疑问,咱们一起往下面学习吧~
(二) 怎么跨进程拿到ContentProvider的对象
1. 我们来看ContentResolver.query方法是怎么实现的
a. 首先它会去找ContentProvider对象,是这样写的 b. 然后 acquireUnstableProvider(uri) 方法是这样的:
代码:
public final IContentProvider acquireUnstableProvider(Uri uri) {
if (!SCHEME_CONTENT.equals(uri.getScheme())) {
return null;
}
String auth = uri.getAuthority();//取得ContentProvider名字,拿这个名字去寻找对应的ContentProvider
if (auth != null) {
return acquireUnstableProvider(mContext, uri.getAuthority());
}
return null;
}
如:
代码:
<provider android:name=".TestProvider"
android:authorities="com.android.test"></provider>
c. ok..既然现在我们拿到ContentProvider的名字了,我们就来看看acquireUnstableProvider方法怎么通过名字来找到ContentProvider对象的。
这个acquireUnstableProvider方法会调用到ActivityThread的 acquireProvider 方法,这个方法的实现是:
代码:
public final IContentProvider acquireProvider(Context c, String name, boolean stable) {
IContentProvider provider = acquireExistingProvider(c, name, stable);
if (provider != null) {
return provider;
}
IActivityManager.ContentProviderHolder holder = null;
try {
holder = ActivityManagerNative.getDefault().getContentProvider(
getApplicationThread(), name, stable);
} catch (RemoteException ex) {
}
if (holder == null) {
Slog.e(TAG, "Failed to find provider info for " + name);
return null;
}
holder = installProvider(c, holder, holder.info,
true /*noisy*/, holder.noReleaseNeeded, stable);
return holder.provider;
}
首先,它去找acquireExistingProvider方法,这个方法其实就是根据我们传过来的名称在一个map里面找,如:
ProviderClientRecord pr = mProviderMap .get(name);
由于我们的ActivityThread和我们的应用程序还在一个进程里面,所以这个步骤我们可以理解为:在 本地缓存 中寻找ContentProvider对象
ok...在本地找了之后,如果找到了,就直接返回。
if (provider != null) {
return provider;
}
如果没有找到,就继续往下面走:
holder = ActivityManagerNative.getDefault().getContentProvider(
getApplicationThread(), name, stable);
这个方法就是调用到ActivityManagerService的getContentProvider方法去寻找ContentProvider.这里是一个跨进程调用,因为ActivityThread和ActivityManagerService不在一个进程里面。
而ActivityManagerService会把所有的ContentProvider都实例化出来,并且缓存在一个map里面,所以我们就可以通过
代码:
holder = ActivityManagerNative.getDefault().getContentProvider(
getApplicationThread(), name, stable);
ok..从远程ActivityManagerService得到ContentProvider对象之后,我们继续往下面走。
代码:
holder = installProvider(c, holder, holder.info,
true /*noisy*/, holder.noReleaseNeeded, stable);
return holder.provider;
ok...那么这整个过程,我们就可以理解为这样:
i. 第一步,它从ActivityThread里面本地缓存寻找ContentProvider对象,所以找到了,就一切ok..
ii. 第二步,如果第一步没有找到,那么就去ActivityManagerService远程服务中寻找ContentProvider对象。
iii.第三步,从远程服务中找到ContentProvider对象之后,就把这个对象缓存在本地,那么下次找的话,直接就可以从本地缓存中查找了。
那么,它为什么要有这个机制呢?个人猜测:因为跨进程调用是需要时间和资源消耗的,所以,它才有了本地缓存这么个东东。
(三) ContentProvider实例对象是保存在哪里
那么如果大家看完了上面一篇长篇大论,这个问题就很好回答了。
它储存在两个位置:
1. ActivityThread的本地map缓存中
2. ActivityManagerService的远程服务map缓存中
(四) ContentProvider的方法实现要注意线程安全吗
从上面一段描述来看,我们可以发现一个问题,ContentProvider在某种程度上是单例的,比如我们第一次从本地map缓存里面得到ContentProvider对象,第二次我们在同一个应用程序请求的时候,拿到的肯定是同一个缓存对象。
所以,ContentProvider只能配置进程之间是否是单例,同一个进程里面是不能配置是否是单例的,因为它在同一个进程里面肯定是单例。
配置进程之间是否是单例: 所以 我们的ContentProvider的代码,比如查询,更新,删除等等,必须注意线程安全的问题 。
那么单例下,我们怎么注意线程安全问题呢?
1. ContentProvider尽量少用成员变量,因为我们用的是单例,所以成员变量是共享的。
2. 所以真的用到了共享资源,建议用synchronized或者TheadLocal来解决。
ContentProvider用到的UriMatcher。UriMatcher的一个重要的函数是match(Uri uri)。这个函数可以匹配Uri,根据传入的不同Uri返回不同的自定义整形值,以表明Uri访问的不同资源的类型。
例如:
|
1
2
3
4
5
6
|
public
static
final
UriMatcher uriMatcher;
static
{
uriMatcher =
new
UriMatcher(UriMatcher.NO_MATCH);
uriMatcher.addURI(Book.AUTHORITY,
"item"
, Book.ITEM);
uriMatcher.addURI(Book.AUTHORITY,
"item/#"
, Book.ITEM_ID);
}
|
在 静态代码块 中初始化。这里UriMatcher类型的静态字段是用来匹配传入到ContentProvider中的Uri的类。其构造方法传入的匹配码是使用match()方法匹配根路径时返回的值,这个匹配码可以为一个大于零的数表示匹配根路径或传入-1,即常量UriMatcher.NO_MATCH表示不匹配根路径。
addURI()方法是用来增加其他URI匹配路径的,
第一个参数传入标识ContentProvider的AUTHORITY字符串。
第二个参数传入需要匹配的路径,这里的#号为通配符,代表匹配任意数字,另外还可以用*来匹配任意文本。
第三个参数必须传入一个大于零的匹配码,用于match()方法对相匹配的URI返回相对应的匹配码。 例如:sMatcher.addURI(“com.test.provider.personprovider”, “person”, 1);如果match()方法匹配content://com.test.provider.personprovider/person路径,返回匹配码为1。
监听ContentProvider中数据的变化
如果ContentProvider的访问者需要知道ContentProvider中的数据发生变化,可以在ContentProvider发生数据变化时调用getContentResolver().notifyChange(uri, null)来通知注册在此URI上的访问者,例子如下:
|
1
2
3
4
5
6
|
public
class
PersonContentProvider
extends
ContentProvider {
public
Uri insert(Uri uri, ContentValues values) {
db.insert(
"person"
,
"personid"
, values);
getContext().getContentResolver().notifyChange(uri,
null
);
}
}
|
|
1
2
3
4
5
6
7
8
9
10
|
getContentResolver().registerContentObserver(Uri.parse(
"content://com.ljq.providers.personprovider/person"
),
true
,
new
PersonObserver(
new
Handler()));
public
class
PersonObserver
extends
ContentObserver{
public
PersonObserver(Handler handler) {
super
(handler);
}
public
void
onChange(
boolean
selfChange) {
//此处可以进行相应的业务处理
}
}
|
本文详细解析了Android系统中ContentProvider的作用、如何跨进程获取ContentProvider对象、实例对象存储位置以及方法实现中的线程安全注意事项。此外,还介绍了如何监听ContentProvider中的数据变化,以及使用ContentObserver进行数据变化通知的实现方法。
2645

被折叠的 条评论
为什么被折叠?



