1.ContentProvider 内容提供者
- 内容提供者:主要用于在不同的应用程序之间实现数据共享的功能,允许一个程序访问另一个程序中的数据,同时还能保证被访问数据的安全性。与其他存储方式不同的是:可以选择哪部分数据分享,从而保证隐私数据的安全性。
1.1ContentProvider 特点
- 四大组件之一,需要在 AndroidMainifest.xml 文件中进行注册。
- 接口的统一,是为数据的获取、添加、修改的操作提供统一的接口。
- 跨进程供多个应用程序共享数据;
- 设备存储数据:通讯录、图片;
- 数据更新监听:UI更新;
1.2ContentProvider 的优缺点
- 数据访问统一接口(存储方式都不用管)优点。
- 跨进程数据的访问,优点。
- 无法单独使用,必须与其他的存储方式结合使用,缺点。
1.3ContentProvider 如何提供数据,实现共享?
- 新建类去继承ContentProvider,然后覆写 query、insert、update、delete 等6个抽象方法。
- 必须在 AndroidManifest 文件中进行注册。
- 把自己的数据通过 uri 的形式共享出去。
- 借助UriMather.match()方法实现匹配内容URI的功能。
- 第三方可以通过 ContentResolver 来访问该 Provider。
1.4ContentProvider工作流程
- 应用程序A(提供者)定义了 ContentProvider,定义了一些数据库和文件来存放数据。
- 应用程序B(访问者)获得 ContentResolver 对象,根据Uri访问指定的 ContentProvider。
- ContentProvider 就会访问底层的存储方式,并返回数据给 ContentProvider。
- ContentProvider 将数据返回给 ContentResolver,ContentResolver 再转到应用B。
2.ContentResolver 内容访问者
- 想要访问内容提供器中的共享数据,就一定要借助 ContentResolver 类,可以通过 getContentResolver() 方法获取该类实例,并且提供了一系列CRUD操作(insert、update、delete、query),不同于其他存储方式的是不接收表名参数,而是用 Uri 参数来代替(由协议声明、authority和path组成)。
2.1ContentResolver如何实现数据的访问
- ContentResolver的Contex.getContentResolver().query()
- 提供了CRUD操作;
- Transport implements;
- IContentProvider;
- Uri,ContentResolver中的增删查改都是用Uri来代替表名参数。
- 概念:统一资源标识符;
- 组成:协议、域名authority、路径path;
- content://com.example.app.authority/path;
- 作用:访问ContentProvider;
- 不同:与其他组件交互不同的地方(Intent);
//简单演示获取数据都代码
Cursor mCursor = getContentResolver().query(uri,projection,selection,selectionArgs,orderBy);
if (mCursor != null) {
while (mCursor.moveToNext()) {
String column1 = mCursor.getString(mCursor.getColumnIndex("column1"));
int column2 = mCursor.getInt(mCursor.getColumnIndex("column2"));
}
mCursor.close();
}
3.ContentObserver 内容观察者
Android 内部提供了一种 ContentObserver 来监听数据库内容的变化。作用是观察(捕捉)特定 Uri 引起的数据库的变化,继而做一些相应的处理。
3.1如何定义ContentObserver
-
第一步:创建一个 ContentObserver 的子类,实现 onChange() 方法。
- 监听的 Uri 中的数据发生变化的时候,会调用 onchange() 方法。
- 构造方法:public void ContentObserver(Handler handler)参数需要一个 Hanlder,因为 ContentObserver 内部使用了一个实现 Runnable 接口的内部类 NotificationRunnable,需要通过 Handler 去做比如 UI 变化。(可以从主线程传入一个 handler。)
-
第二步:通过 registerContentObserver 注册 ContentObserver。
- 第一个参数:需要监听的 uri。
- 第二个参数:为 false 表示精确匹配,即只匹配该 Uri。为 true 表示可以同时匹配其派生的 Uri,如:content://com.qin.cb/student(精确匹配)content://com.qin.cb/student/# (派生)content://com.qin.cb/student/schoolchild(派生)
- 第三个参数:ContentObserver 的实例。
- 第三步:用完后记得取消注册 unregisterContentObserver。
- 第四步:ContentProvider数据源发送改变后,通知ContentObserver。
//第一步: 创建一个 ContentObserver 的子类,实现 onChange() 方法。
public class MyContentObserver extends ContentObserver{
private Handler handler;
public MyContentObserver(Handler handler) {
super(handler);
this.handler=handler;
}
@Override
public void onChange(boolean selfChange) {
super.onChange(selfChange);
// 在onChange方法里面我们就可以执行变化的操作了
}
}
//第二步:将这个监听者与我们所要监听的对象绑定
//通过 registerContentObserver 注册 ContentObserver。
MyContentObserver mObserver = new MyContentObserver();
getContentResolver().registerContentObserver(Uri uri, boolean notifyForDescendants, ContentObserver observer);
//第三步:取消注册 unregisterContentObserver
getContentResolver().unregisterContentObserver(mObserver)
//ContentProvider数据源发送改变后,通知ContentObserver。
getContext().getContentResolver().notifyChange(uri, null);
3.2也可以这样简单写
public class Main2Activity extends AppCompatActivity {
Handler mHandler = new Handler() {
public void handleMessage(Message msg) {
super.handleMessage(msg);
//在主线程更新UI adapter.notifyChangeSet();
}
};
ContentObserver observer = new ContentObserver(mHandler) {
public void onChange(boolean selfChange) {
super.onChange(selfChange);
//Uri数据改变,非Ui线程不能直接更新
//发送消息
}
};
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
//注册内容观察者
this.getContentResolver().registerContentObserver(uri, notifyForDescendants, observer);
}
}
3.3为什么自定义的 ContentProvider 数据源发生改变后,却没有监听到任何反应?
每个ContentProvider数据源发生改变后,如果想通知其监听对象, 例如ContentObserver时,必须在其对应方法 update / insert / delete 时,显式地调用 this.getContentReslover().notifychange(uri,null) 方法,回调监听处理逻辑。否则,我们的 ContentObserver 是不会监听到数据发生改变的。
4.说说ContentProvider、ContentResolver、ContentObserver 之间的关系
- ContentProvider:内容提供者,用于对外提供数据,提供数据的增删改查操作,数据源可以是数据库、文件、XML、网络等,ContentProvider为这些数据的访问提供了统一的接口,可以用来做进程间数据共享。
- ContentResolver:内容解析者,用于获取内容提供者提供的数据。ContentResolver可以不同URI操作不同的ContentProvider中的数据,外部进程可以通过ContentResolver与ContentProvider进行交互。
- ContentObserver:内容监听器,可以监听数据的改变状态,当ContentProvider数据源发生改变时,如果想通知其监听对象, 例如ContentObserver时,就必须在其调用对应方法 update / insert / delete 时,显式地调用 this.getContentReslover().notifychange(uri,null) 方法,回调监听处理逻辑。
5.为什么要用 ContentProvider?它和 sql 的实现上有什么差别?
ContentProvider 屏蔽了数据存储的细节,内部实现对用户完全透明,用户只需要关心操作数据的
uri 就可以了,ContentProvider 可以实现不同 app 之间共享。Sql 也有增删改查的方法,但是 sql 只能操作本应用下的数据库。而 ContentProvider 还可
以去增删改查本地文件. xml 文件的读取等。
6.多个进程同时调用一个ContentProvider的query获取数据,ContentPrvoider是如何反应的呢?
尽管ContentResolver与ContentProvider类隐藏了实现细节,ContentProvider除了onCreate()是在UI线程运行,其余所提供的query(),insert(),delete(),update()都是在ContentProvider进程的线程池中被调用执行的,而不是进程的主线程中。因为那些方法可能同时被多个线程所调用,所以他们都应该是线程安全的。这个线程池是由Binder创建和维护的,其实使用的就是每个应用进程中的Binder线程池。
7.你觉得Android设计ContentProvider的目的是什么呢?
- 隐藏数据的实现方式,对外提供统一的数据访问接口;
- 更好的数据访问权限管理。ContentProvider可以对开发的数据进行权限设置,不同的URI可以对应不同的权限,只有符合权限要求的组件才能访问到ContentProvider的具体操作。
- ContentProvider封装了跨进程共享的逻辑,我们只需要Uri即可访问数据。由系统来管理ContentProvider的创建、生命周期及访问的线程分配,简化我们在应用间共享数据(进程间通信)的方式。我们只管通过ContentResolver访问ContentProvider所提示的数据接口,而不需要担心它所在进程是启动还是未启动。
8.运行在主线程的ContentProvider为什么不会影响主线程的UI操作?
- ContentProvider的onCreate()是运行在UI线程的,而query(),insert(),delete(),update()是运行在线程池中的工作线程的,所以调用这几个方法并不会阻塞ContentProvider所在进程的主线程,但可能会阻塞调用者所在的进程的UI线程!
- 所以,调用ContentProvider的操作仍然要放在子线程中去做。虽然直接的CRUD的操作是在工作线程的,但系统会让你的调用线程等待这个异步的操作完成,你才可以继续线程之前的工作。