深入Understanding Android ContentProvider详解
程序员文章站
2023-12-09 22:35:27
1. 什么是contentprovider也即内容提供者,是对所有数据访问的一层抽象,为数据访问提供了统一的接口。它有以下优点:a. 对数据的抽象,为所有的组件提供统一的访...
1. 什么是contentprovider
也即内容提供者,是对所有数据访问的一层抽象,为数据访问提供了统一的接口。它有以下优点:
a. 对数据的抽象,为所有的组件提供统一的访问数据的方式,从而让组件不必关心具体数据的呈现形式(文件or数据库)。数据,也可以只关心自身的管理,而不用去管使用者的访问问题。这样就达到了很好的封装。
b. 接口更加方便,更加方便的让组件之间传送数据
contentprovider的访问标识为uri,通过统一的contentresolver进行访问,而contentresolver和uri跟application的上下文context以及组件之间的信息传送工具intent都是无缝接合,这就让组件之间进行数据共享和数据传递更加的方便和快捷。
所以,contentprovider的最大好处在于它可以在不同组件之间方便的共享。所以,如果你的应用里面用到的数据需要在不同的组件之间共享,那么实现一个contentprovider无疑是最佳方案。
2. 实现方式
contentprovider的实现方式非常简单,只需要根据需求实现一些接口即可,比如:query, insert, delete, update, openfile等。但是具体的数据的呈现形式则是根据不同的目的进行*选择,比如对于结构化数据,选择sqlitedatabase可能是比较好的方案,大量的字节流可能文件是首选等等。
需要注意一点的是,虽然android中百分之九十的contentprovider内部都是用sqlitedatabase来存储结构化数据,但这并不意味着contentprovider只能从sqlitedatabase来管理数据。contentprovider定义了一些接口,你只需要按照需要返回正确的数据即可,具体 的实现方式则由你*选择。
比如,contacts的contentprovider能提供以vcard的方式输出,也就是说当读取一个vcard的uri时,这个流是一个vcard形式的文件流,实现起来的思路就是这样:
cursor query(uri, ....) {
if (uri is for vcard) {
query the contact's infomation
create a cursor with two columns name and size
put contact's name into cursor
sum all contact's field and get size
put that size into cursor
return the cursor
}
}
这样通过query就能得到这个vcard的相关信息文件名字和大小,再通过openinputstream就可以读取这个vcard文件流,但是实际上contentprovider是没有vcard形式的数据,也没有一个vcard的文件,它只是在openfile的时候,识别出vcard的uri,把contact数据转化成vcard形式写入输出流中:
parcelfiledescriptor openfile(uri...) {
if (uri is for vcard) {
generate vcard with vcardcomposer
write to output stream
}
}
3. 其他替代方案
contentprovider不是必须的,每个应用必然用到数据,但是可以选择用创建一个contentprovider来管理,也可以直接使用文件或数据库,如下面的例子:
package com.android.effective;
import android.app.activity;
import android.content.contentvalues;
import android.content.context;
import android.database.cursor;
import android.database.sqlite.sqlitedatabase;
import android.database.sqlite.sqliteopenhelper;
import android.database.sqlite.sqlitedatabase.cursorfactory;
import android.os.bundle;
import android.util.log;
public class sqlitedatabasedemo extends activity {
private static final string tag = "sqlitedatabasedemo";
@override
public void oncreate(bundle savedinstancestate) {
super.oncreate(savedinstancestate);
mydatabase db = new mydatabase(this);
int id = db.setname("michael jordan");
log.e(tag, "id of " + id + " is " + db.getname(id));
}
private class mydatabase {
private static final string name = "demo.db";
private static final string table = "demo";
private final string[] projection = new string[] {"_id", "name" };
private mydatabasehelper helper;
public mydatabase(context context) {
helper = new mydatabasehelper(context, name, null, 1);
}
public string getname(int id) {
final cursor c = helper.getreadabledatabase().query("demo", projection, "_id=" + id,
null, null, null, null);
if (c == null || !c.movetofirst()) {
return null;
}
return c.getstring(1);
}
public int setname(string name) {
contentvalues cv = new contentvalues();
cv.put("name", name);
return (int) helper.getwritabledatabase().insert(table, "name", cv);
}
}
private class mydatabasehelper extends sqliteopenhelper {
public mydatabasehelper(context context, string name,
cursorfactory factory, int version) {
super(context, name, factory, version);
}
@override
public void oncreate(sqlitedatabase db) {
db.execsql("create table demo (_id integer primary key, name text);");
}
@override
public void onupgrade(sqlitedatabase db, int old, int newver) {
}
}
}
这个例子中就没有使用contentprovider而是让activity直接操作sqlitedatabase来实现数据的管理,或者不用数据库而直接使用文件进行管理数据。
这种方式实现起来可能更简单,对于需求不大,数据量不大,且只有单一组件使用的情况下,完全可以用这种方式。但是它的缺点也很明显,就是在组件之间传递会十分麻烦,甚至不能够在组件之间共享。为了共享,就要把数据层进行抽象,使其独立于任何一个activity,以满足不同的组件对数据进行读写,但是这样一来跟实现一个contentprovider就没有区别了,还不如实现一个contentprovider来的方便。
所以,规则就是如果某些数据只在一个activity中使用,那么没有必要创建contentprovider,直接使用文件或直接操作database就可以达到目的。但是如果需要跟其他的组件进行共享和传递数据,就必须使用contentprovider。
另外,有了contentprovider也可以方便跟其他应用进行交互,把数据传递给其他应用的组件。
在使用sqliteopenhelper一定要注意线程同步问题,保证每一个sqlitedatabase的方法(如execsql)的线程安全性,否则可能会引起十分罕见的异常。曾遇到一个sqlitestatement报出的npe(nullpointerexception),就是由于有多个线程在操作同一个sqliteopenhelper,而且没有同步。
也即内容提供者,是对所有数据访问的一层抽象,为数据访问提供了统一的接口。它有以下优点:
a. 对数据的抽象,为所有的组件提供统一的访问数据的方式,从而让组件不必关心具体数据的呈现形式(文件or数据库)。数据,也可以只关心自身的管理,而不用去管使用者的访问问题。这样就达到了很好的封装。
b. 接口更加方便,更加方便的让组件之间传送数据
contentprovider的访问标识为uri,通过统一的contentresolver进行访问,而contentresolver和uri跟application的上下文context以及组件之间的信息传送工具intent都是无缝接合,这就让组件之间进行数据共享和数据传递更加的方便和快捷。
所以,contentprovider的最大好处在于它可以在不同组件之间方便的共享。所以,如果你的应用里面用到的数据需要在不同的组件之间共享,那么实现一个contentprovider无疑是最佳方案。
2. 实现方式
contentprovider的实现方式非常简单,只需要根据需求实现一些接口即可,比如:query, insert, delete, update, openfile等。但是具体的数据的呈现形式则是根据不同的目的进行*选择,比如对于结构化数据,选择sqlitedatabase可能是比较好的方案,大量的字节流可能文件是首选等等。
需要注意一点的是,虽然android中百分之九十的contentprovider内部都是用sqlitedatabase来存储结构化数据,但这并不意味着contentprovider只能从sqlitedatabase来管理数据。contentprovider定义了一些接口,你只需要按照需要返回正确的数据即可,具体 的实现方式则由你*选择。
比如,contacts的contentprovider能提供以vcard的方式输出,也就是说当读取一个vcard的uri时,这个流是一个vcard形式的文件流,实现起来的思路就是这样:
复制代码 代码如下:
cursor query(uri, ....) {
if (uri is for vcard) {
query the contact's infomation
create a cursor with two columns name and size
put contact's name into cursor
sum all contact's field and get size
put that size into cursor
return the cursor
}
}
这样通过query就能得到这个vcard的相关信息文件名字和大小,再通过openinputstream就可以读取这个vcard文件流,但是实际上contentprovider是没有vcard形式的数据,也没有一个vcard的文件,它只是在openfile的时候,识别出vcard的uri,把contact数据转化成vcard形式写入输出流中:
复制代码 代码如下:
parcelfiledescriptor openfile(uri...) {
if (uri is for vcard) {
generate vcard with vcardcomposer
write to output stream
}
}
3. 其他替代方案
contentprovider不是必须的,每个应用必然用到数据,但是可以选择用创建一个contentprovider来管理,也可以直接使用文件或数据库,如下面的例子:
复制代码 代码如下:
package com.android.effective;
import android.app.activity;
import android.content.contentvalues;
import android.content.context;
import android.database.cursor;
import android.database.sqlite.sqlitedatabase;
import android.database.sqlite.sqliteopenhelper;
import android.database.sqlite.sqlitedatabase.cursorfactory;
import android.os.bundle;
import android.util.log;
public class sqlitedatabasedemo extends activity {
private static final string tag = "sqlitedatabasedemo";
@override
public void oncreate(bundle savedinstancestate) {
super.oncreate(savedinstancestate);
mydatabase db = new mydatabase(this);
int id = db.setname("michael jordan");
log.e(tag, "id of " + id + " is " + db.getname(id));
}
private class mydatabase {
private static final string name = "demo.db";
private static final string table = "demo";
private final string[] projection = new string[] {"_id", "name" };
private mydatabasehelper helper;
public mydatabase(context context) {
helper = new mydatabasehelper(context, name, null, 1);
}
public string getname(int id) {
final cursor c = helper.getreadabledatabase().query("demo", projection, "_id=" + id,
null, null, null, null);
if (c == null || !c.movetofirst()) {
return null;
}
return c.getstring(1);
}
public int setname(string name) {
contentvalues cv = new contentvalues();
cv.put("name", name);
return (int) helper.getwritabledatabase().insert(table, "name", cv);
}
}
private class mydatabasehelper extends sqliteopenhelper {
public mydatabasehelper(context context, string name,
cursorfactory factory, int version) {
super(context, name, factory, version);
}
@override
public void oncreate(sqlitedatabase db) {
db.execsql("create table demo (_id integer primary key, name text);");
}
@override
public void onupgrade(sqlitedatabase db, int old, int newver) {
}
}
}
这个例子中就没有使用contentprovider而是让activity直接操作sqlitedatabase来实现数据的管理,或者不用数据库而直接使用文件进行管理数据。
这种方式实现起来可能更简单,对于需求不大,数据量不大,且只有单一组件使用的情况下,完全可以用这种方式。但是它的缺点也很明显,就是在组件之间传递会十分麻烦,甚至不能够在组件之间共享。为了共享,就要把数据层进行抽象,使其独立于任何一个activity,以满足不同的组件对数据进行读写,但是这样一来跟实现一个contentprovider就没有区别了,还不如实现一个contentprovider来的方便。
所以,规则就是如果某些数据只在一个activity中使用,那么没有必要创建contentprovider,直接使用文件或直接操作database就可以达到目的。但是如果需要跟其他的组件进行共享和传递数据,就必须使用contentprovider。
另外,有了contentprovider也可以方便跟其他应用进行交互,把数据传递给其他应用的组件。
在使用sqliteopenhelper一定要注意线程同步问题,保证每一个sqlitedatabase的方法(如execsql)的线程安全性,否则可能会引起十分罕见的异常。曾遇到一个sqlitestatement报出的npe(nullpointerexception),就是由于有多个线程在操作同一个sqliteopenhelper,而且没有同步。