Android ContentProvider理解与实践

时间:2020-12-17 09:25:43

一、概述

ContentProvider 相信我们大家都很熟悉,我用一句话去描述 ContentProvider ,是不同应用程序之间交换数据的标准的 API ,ContentProvider 以某种 Uri 的形式对外提供数据,其他应用程序可以通过 ContentResolver 根据 Uri 去访问和操作指定的数据。 在网上有一种说法是 ContentProvider 可以存储数据,在我看来,ContentProvider 只是操作我们手机中的一些数据,那下面我们先看一下基本的使用方法


二、常用的使用方法介绍



我们先看一下我们最基本的 ContentProvider 下面看一下代码:

/**
* Created by andy on 2017/7/11.
*/

public class testContentProvider extends ContentProvider{
@Override
public boolean onCreate() {
return false;
}

@Nullable
@Override
public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder) {
return null;
}

@Nullable
@Override
public String getType(Uri uri) {
return null;
}

@Nullable
@Override
public Uri insert(Uri uri, ContentValues values) {
return null;
}

@Override
public int delete(Uri uri, String selection, String[] selectionArgs) {
return 0;
}

@Override
public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs) {
return 0;
}
}


这段代码中我们可以看到,有几个主要的方法分别是 query 、delete、update、insert  四个方法,这四个方法从方法名我们就可以看出来分别是查询,删除,更新,插入,我们可以在四个方法中对我们的数据库进行操作,那当我们调用我们的 ContentResolver 的对应方法的时候,表面上调用的是 ContentProvider 实际上调用的是数据的相关操作。


接下来说一下这些方法中的参数的意义是什么,那这个 Uri 相当于我们要访问哪个 ContentProvider。projection 相当于我们要返回的数据,selection,相当于 SQL 语句中的 where 这个主要是对属性的限制selectionArgs,配合 selection 使用的限制条件也就是这个是限制的值是什么sortOrder,相当于 order by。这个 ContentValues 需要说明一些,这个 ContentValue 相当于一个 HashMap<String,Object> 这个数据也是我们要写入数据库的数据,我们可以通过 contentValue.put 加入数据。


接下来看一下 ContentResolver 下面我们还是先看代码:


ContentResolver contentResolver = getContentResolver();
query(@RequiresPermission.Read @NonNull Uri uri,            @Nullable String[] projection, @Nullable String selection,            @Nullable String[] selectionArgs, @Nullable String sortOrder) 
delete(@RequiresPermission.Write @NonNull Uri url, @Nullable String where,            @Nullable String[] selectionArgs) {        Preconditions.checkNotNull(url, "url");
pdate(@RequiresPermission.Write @NonNull Uri uri,            @Nullable ContentValues values, @Nullable String where,            @Nullable String[] selectionArgs) 
insert(@RequiresPermission.Write @NonNull Uri url,                @Nullable ContentValues values) 



从上面的代码中我们可以看到我们可以通过 ContentResolver 的 getContentResolver 获取我们应用的 ContentResolver

之后我们可以根绝 contentResolver 这个对象去调用下面四个方法完成我们对 ContentProvider 的数据的访问。

在我们上面两节中都有提到这个 Uri ,那这个 Uri 到底是什么东西呢,下面一节我们会做一些大致的介绍。



三、Uri 介绍

在我们平时的工作中,我们通常会听说 Url 这个概念,那我们这个 Uri 是什么东西呢,其实 Url 是我们 Uri 中的一种,我们先看一个 Uri 的例子:

ehuodi://andya.top:80/deleteinformation.html 

从这个域名中我们可以把他分为几部分,第一部分 ehuodi://   这个部分是我们的 scheme 的部分,我们访问 ContentProvider 的 Scheme 规定为 content://

andya.top:相当于域名

:80 表示端口

deleteinformation:表示网站的资源,也就是我们常说的路径。

那这几部分组在一起就组成了我们的 Uri 。

Uri 的工具类

UriMatcher:这个工具类主要是判断我们发送的 Uri 是否会有回应,主要是两个方法下面看代码:

addURI(String authority, String path, int code)
match(Uri uri)

第一个方法主要是想 uriMatcher 中加入 Uri ,authority 和 path 组成一个 Uri ,code 代表返回码。

第二个方法是判断我们的 Uri 是否有返回值,如果有返回值那说明有对应的 ContentProvider。


ContentUris:这个类主要是操作 Uri 

Uri withAppendedId(Uri contentUri, long id)
long parseId(Uri contentUri)

第一个方法主要是在 contentUri 后面加入一个 id 值

然后第二个方法就是获取这个 Uri 中的 id 值。

操作 Uri 的工具类就说道这里了,下面我们说一下监听数据变化。



四、contentObserver 监听数据变化


说道监听 ContentProvider 的数据变化,我们不得不提到我们的 contentObserver 这个类,通过这个类我们可以,根据数据的变化操作我们的 UI ,那现在我们来看一下监听数据的步骤:

1、当我们数据变化的时候调用 getContext.getContentResolver.notifiChange(uri,null)

2、继承 ContentObserver 这个类在 onChange 方法中处理数据变化

下面看代码:


public class ObserverTest1 extends ContentObserver{
/**
* Creates a content observer.
*
* @param handler The handler to run {@link #onChange} on, or null if none.
*/
public ObserverTest1(Handler handler) {
super(handler);
}

@Override
public void onChange(boolean selfChange) {
super.onChange(selfChange);
}
}
contentResolver.registerContentObserver(Words.word.DICT_CONTENT,true,new ObserverTest1(new Handler()));


可以看到我们的注册的时候传递了一个 handler 进去,因为我们的这个操作是在线程中,所以我们需要传递一个 handler 处理 UI。

然后在我们的 ObserverTest1 中的 onchange 方法中处理我们数据变化的 UI 。



五、使用 contentProvider 的优缺点

优点: 提供统一的接口来进行访问。 可以进行跨进程的访问。 缺点: 无法单独使用


六、原理

我们首先说一下我们的 contentResolver 访问 contentProvider 的数据的


首先我们先看一下 query 方法:

public final @Nullable Cursor query(final @RequiresPermission.Read @NonNull Uri uri,
@Nullable String[] projection, @Nullable String selection,
@Nullable String[] selectionArgs, @Nullable String sortOrder,
@Nullable CancellationSignal cancellationSignal) {
Preconditions.checkNotNull(uri, "uri");
IContentProvider unstableProvider = acquireUnstableProvider(uri);
if (unstableProvider == null) {
return null;
}
IContentProvider stableProvider = null;
Cursor qCursor = null;
try {
long startTime = SystemClock.uptimeMillis();

ICancellationSignal remoteCancellationSignal = null;
if (cancellationSignal != null) {
cancellationSignal.throwIfCanceled();

我们可以看到,我们在这个方法中有一个 IContentProvider 这个对象,那这个对象有什么作用呢,我们看一下我们的 ContentProvider 类中有个 Transport 类:

class Transport extends ContentProviderNative {
AppOpsManager mAppOpsManager = null;
int mReadOp = AppOpsManager.OP_NONE;
int mWriteOp = AppOpsManager.OP_NONE;

ContentProvider getContentProvider() {
return ContentProvider.this;
}

@Override
public String getProviderName() {
return getContentProvider().getClass().getName();
}

@Override
public Cursor query(String callingPkg, Uri uri, String[] projection,
String selection, String[] selectionArgs, String sortOrder,
ICancellationSignal cancellationSignal) {
validateIncomingUri(uri);
uri = getUriWithoutUserId(uri);
if (enforceReadPermission(callingPkg, uri, null) != AppOpsManager.MODE_ALLOWED) {
// The caller has no access to the data

如果你点进入 ContentProviderNative 类,这个类实现了其实实现了我们的 IContentProvider ,所以我们调用了 IContentProvider 的方法其实就是调用了 Transport 的方法,这也是多态的一种实现。其实其他方法也是一样的。

那我们在 ContentResolve 中是怎么获取这个 IContentProvider 的呢 ,我们看一张图就知道了

Android ContentProvider理解与实践


从图中我们可以看到,我们的应用程序其实是通过 AcivityManagerService 跨进程去访问的,然后给我们返回了 IContentProvider 对象的。

总结:


四大组件基本上写的差不多了,那这篇总结了 ContentProvider 和 ContentResolver 的基本用法,那监听数据的原理有哪位大神知道请指导一下,下周将总结一下我们的广播的知识,如有兴趣请关注,谢谢。

雅歌不会编代码 2017/0711