1、定义
ContentProvider是内容提供者,是Android四大组件之一。在Android中,每一个ContentProvider都会用类似于域名的字符串来注册自己,我们称为授权(authority)。这个唯一标识的字符串是此ContentProvider可提供的一组URI的基础,有了这个基础,才能够向外界提供信息的共享服务。
授权是在AndroidManifest.xml中完成的,每一个ContentProvider必须在此声明并授权,方式如下:
1 2 |
|
2、作用
用于进程间数据的交互共享,即跨进程通信。其作为数据源的搬运工的角色。数据源可以是数据库数据如SQLite,文件,XML,网络等等。当应用继承ContentProvider类,并重写该类用于提供数据和存储数据的方法,就可以向其他应用共享其数据。虽然使用其他方法也可以对外共享数据,但数据访问方式会因数据存储的方式而不同,如:采用文件方式对外共享数据,需要进行文件操作读写数据;采用sharedpreferences共享数据,需要使用sharedpreferencesAPI读写数据。而使用ContentProvider共享数据的好处是统一了数据访问方式。
3、原理
底层采用Android中的Binder机制。
4、具体使用
4.1 统一资源标识符(URI)
外界进程通过URI找到对应的ContentProvider和其中的数据,在进行数据操作。
URI分为系统预置和自定义两部分。分别对应系统内置的数据如通讯录日程表等,还有自己定义的数据库。
自定义URI = 主题名 + 授权信息 + 表名 + 记录
通过上述组合就可以定位到具体到数据库中某一表中某一个数据。
Uri代表了要操作的数据,Uri主要包含了两部分信息:1.需要操作的ContentProvider ,2.对ContentProvider中的什么数据进行操作,一个Uri由以下几部分组成:
1.scheme:ContentProvider(内容提供者)的scheme已经由Android所规定为:content://。
2.主机名(或Authority):用于唯一标识这个ContentProvider,外部调用者可以根据这个标识来找到它。
3.路径(path):可以用来表示我们要操作的数据,路径的构建应根据业务而定,如下:
• 要操作contact表中id为10的记录,可以构建这样的路径:/teacher/10
• 要操作contact表中id为10的记录的name字段, teacher/10/name
• 要操作contact表中的所有记录,可以构建这样的路径:/teacher
值得注意的是,URI模式支持通配符匹配,可以使用通配符匹配任意长度的数据
4.2 MIME数据类型
作用:指定某个扩展名的文件用某种应用程序打开。
MIME类型由类型+子类型组成
例如text/css text/xml application/pdf
4.3 ContentProvider类
此类组织数据的方式主要是表格形式,同时也支持文件数据
主要方法如下
insert,delete,update,query
值得注意的是,如果是在多线程并发访问的时候使用,需要实现同步。
4.4 ContentResolver类
此类用于管理不同的ContentProvider间的操作。通过URI可以操作不同ContentProvider种的数据,外部进程通过ContentResolver类从而与ContentProvider类进行交互,交互方法主要有数据进行添加、删除、修改和查询操作时,可以使用ContentResolver 类来完成,要获取ContentResolver 对象,可以使用Activity提供的getContentResolver()方法。 ContentResolver使用insert、delete、update、query方法,来操作数据。
4.5 ContentUris类
此类用于辅助ContentProvider,用于操作URI。可以通过withAppendedId方法向URI追加一个id。
可以通过parse方法从URL中获取ID。
它有两个比较实用的方法:
• withAppendedId(uri, id)用于为路径加上ID部分
• parseId(uri)方法用于从路径中获取ID部分
4.6 UriMatcher类
此类用于辅助ContentProvider,用于在ContentProvider中注册URI。根据URI匹配ContentProvider中对应的数据表。
经过注册后, 就可以在使用uri时通过匹配不同的结果在switch语句里做不同的处理了。
4.7 ContentObserver类
此类用于辅助ContentProvider,用于内容观察者。观察Uri引起ContentProvider中数据变化并通知外界。使用时通过uri注册,当该uri的ContentProvider的数据发生变化时,通知外界。
5、权限设置
可以在代码中通过setReadPermission()和setWritePermission()两个方法来设置ContentProvider的操作权限,也可以在配置文件中通过android:readPermission和android:writePermission属性来控制。 android:protectionlevel=
"normal"
6,demo代码如下:
自定义MyContentProvider类 public class MyContentProvider extends ContentProvider { private DBHelper dbOpenHelper = null; private static UriMatcher uriMatcher; static { // 常量UriMatcher.NO_MATCH表示不匹配任何路径的返回码 uriMatcher = new UriMatcher(UriMatcher.NO_MATCH); // 如果match()方法匹配content://com.example.lcq.myapp.contentProvider/teacher n路径,返回匹配码为TEACHERS uriMatcher.addURI(ContentData.AUTHORITY, "teacher", TEACHERS); // 如果match()方法匹配content://com.example.lcq.myapp.contentProvider/teacher/230,路径,返回匹配码为TEACHER uriMatcher.addURI(ContentData.AUTHORITY, "teacher/#", TEACHER); } @Override public boolean onCreate() { //这里会调用 DBOpenHelper的构造函数创建一个数据库; dbOpenHelper = new DBHelper(this.getContext(), ContentData.DATABASE_NAME, ContentData.DATABASE_VERSION); return true; } @Override public String getType(Uri uri) { switch (uriMatcher.match(uri)) { case TEACHERS: return CONTENT_TYPE; case TEACHER: return CONTENT_TYPE_ITME; default: throw new IllegalArgumentException("Unknown URI " + uri); } } @Override public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder) { SQLiteDatabase db = dbOpenHelper.getReadableDatabase(); switch (uriMatcher.match(uri)) { case TEACHERS: return db.query("teacher", projection, selection, selectionArgs, null, null, sortOrder); case TEACHER: long personid = ContentUris.parseId(uri); String where = "_ID=" + personid;// 获取指定id的记录 where += !TextUtils.isEmpty(selection) ? " and (" + selection + ")" : "";// 把其它条件附加上 return db.query("teacher", projection, where, selectionArgs, null, null, sortOrder); default: throw new IllegalArgumentException("Unknown URI " + uri); } } /** * 当执行这个方法的时候,如果没有数据库,他会创建,同时也会创建表,但是如果没有表,下面在执行insert的时候就会出错 * 这里的插入数据也完全可以用sql语句书写,然后调用 db.execSQL(sql)执行。 */ @Override public Uri insert(Uri uri, ContentValues values) { //多线程加类锁 synchronized (MyContentProvider.class) { //获得一个可写的数据库引用,如果数据库不存在,则根据onCreate的方法里创建; SQLiteDatabase db = dbOpenHelper.getWritableDatabase(); long id = 0; int uu = uriMatcher.match(uri); switch (uriMatcher.match(uri)) { case TEACHERS: id = db.insert("teacher", null, values); // 返回的是记录的行号,主键为int,实际上就是主键值 if(id > 0) { notifyChanged(); } return ContentUris.withAppendedId(uri, id); case TEACHER: id = db.insert("teacher", null, values); if(id > 0 ) { notifyChanged(); } String path = uri.toString(); return Uri.parse(path.substring(0, path.lastIndexOf("/")) + id); // 替换掉id default: throw new IllegalArgumentException("Unknown URI " + uri); } } } //通知指定URI数据已改变 private void notifyChanged() { getContext().getContentResolver().notifyChange(ContentData.UserTableData.CONTENT_URI,null); } @Override public int delete(Uri uri, String selection, String[] selectionArgs) { //多线程加类锁 synchronized (MyContentProvider.class) { SQLiteDatabase db = dbOpenHelper.getWritableDatabase(); int count = 0; switch (uriMatcher.match(uri)) { case TEACHERS: count = db.delete("teacher", selection, selectionArgs); if(count > 0 ) { notifyChanged(); } break; case TEACHER: // 下面的方法用于从URI中解析出id,对这样的路径content://hb.android.teacherProvider/teacher/10 // 进行解析,返回值为10 long personid = ContentUris.parseId(uri); String where = "_ID=" + personid; // 删除指定id的记录 where += !TextUtils.isEmpty(selection) ? " and (" + selection + ")" : ""; // 把其它条件附加上 count = db.delete("teacher", where, selectionArgs); if(count > 0 ) { notifyChanged(); } break; default: throw new IllegalArgumentException("Unknown URI " + uri); } db.close(); return count; } } @Override public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs) { //多线程加类锁 synchronized (MyContentProvider.class) { SQLiteDatabase db = dbOpenHelper.getWritableDatabase(); int count = 0; switch (uriMatcher.match(uri)) { case TEACHERS: count = db.update("teacher", values, selection, selectionArgs); if(count > 0 ) { notifyChanged(); } break; case TEACHER: // 下面的方法用于从URI中解析出id,对这样的路径content://com.ljq.provider.personprovider/person/10 // 进行解析,返回值为10 long personid = ContentUris.parseId(uri); String where = "_ID=" + personid;// 获取指定id的记录 where += !TextUtils.isEmpty(selection) ? " and (" + selection + ")" : "";// 把其它条件附加上 count = db.update("teacher", values, where, selectionArgs); if(count > 0 ) { notifyChanged(); } break; default: throw new IllegalArgumentException("Unknown URI " + uri); } db.close(); return count; } } }
外部调用activity类
public class ThirdActivity extends BaseActivity implements View.OnClickListener { private static final String tag = "ThirdActivity"; private int count = 0; private boolean sex = false; Button insert; Button query; Button update; Button delete; Button querys; Button deleteAll; Uri uri = Uri.parse("content://com.example.lcq.myapp.contentProvider/teacher"); @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.third_layout); initView(); } private Handler handler = new Handler() { @Override public void handleMessage(Message msg) { //update records. requery(); }; }; private void initView() { insert = findViewById(R.id.insert); query = findViewById(R.id.query); update = findViewById(R.id.update); delete = findViewById(R.id.delete); deleteAll = findViewById(R.id.delete_all); querys = findViewById(R.id.querys); insert.setOnClickListener(this); query.setOnClickListener(this); querys.setOnClickListener(this); update.setOnClickListener(this); delete.setOnClickListener(this); deleteAll.setOnClickListener(this); //注册变化通知 getContentResolver().registerContentObserver(ContentData.UserTableData.CONTENT_URI,true,new TeacherObserver(handler)); } @Override public void onClick(View v) { ContentResolver cr = getContentResolver(); Cursor c; ContentValues cv = new ContentValues(); switch (v.getId()) { case R.id.insert: cv.put("title", "教书"+count); cv.put("name", "教师"+count); cv.put("sex", !sex); Uri uri2 = cr.insert(uri, cv); Log.e(tag, uri2.toString()); Toast.makeText(this, "插入的内容是: "+uri2.toString(), Toast.LENGTH_SHORT).show(); break; case R.id.query: // 查找id为1的数据 c = cr.query(uri, null, "_ID=?", new String[]{"1"}, null); //这里必须要调用 c.moveToFirst将游标移动到第一条数据,不然会出现index -1 requested , with a size of 1错误;cr.query返回的是一个结果集。 if (c.moveToFirst() == false) { // 为空的Cursor return; } int name = c.getColumnIndex("name"); System.out.println(c.getString(name)); Toast.makeText(this, c.getString(name), Toast.LENGTH_SHORT).show(); c.close(); break; case R.id.update: cv.put("name", "曾老师"); cv.put("date_added", (new Date()).toString()); int uri3 = cr.update(uri, cv, "_ID=?", new String[]{"3"}); System.out.println("updated" + ":" + uri3); Toast.makeText(this, "updated" + ":" + uri3, Toast.LENGTH_SHORT).show(); break; case R.id.delete: cr.delete(uri, "_ID=?", new String[]{"2"}); break; case R.id.querys: // 查找id为1的数据 c = cr.query(uri, null, null, null, null); Log.e(tag, String.valueOf(c.getCount())); Toast.makeText(this, String.valueOf(c.getCount()), Toast.LENGTH_SHORT).show(); c.close(); break; case R.id.delete_all: cr.delete(uri,null,null); break; default: break; } } /** * 重新查询 */ private void requery() { //实际操作中可以查询集合信息后Adapter.notifyDataSetChanged(); // query(null); }
对外的各种常量类
public class ContentData { public static final String AUTHORITY = "com.example.lcq.myapp.contentProvider"; public static final String DATABASE_NAME = "teacher.db"; //创建 数据库的时候,都必须加上版本信息;并且必须大于4 public static final int DATABASE_VERSION = 4; public static final String USERS_TABLE_NAME = "teacher"; public static final class UserTableData implements BaseColumns { public static final String TABLE_NAME = "teacher"; //Uri,外部程序需要访问就是通过这个Uri访问的,这个Uri必须的唯一的。 public static final Uri CONTENT_URI = Uri.parse("content://"+ AUTHORITY + "/teacher"); // 数据集的MIME类型字符串则应该以vnd.android.cursor.dir/开头 public static final String CONTENT_TYPE = "vnd.android.cursor.dir/vnd.scott.teachers"; // 单一数据的MIME类型字符串应该以vnd.android.cursor.item/开头 public static final String CONTENT_TYPE_ITME = "vnd.android.cursor.item/vnd.scott.teacher"; /* 自定义匹配码 */ public static final int TEACHERS = 1; /* 自定义匹配码 */ public static final int TEACHER = 2; public static final String TITLE = "title"; public static final String NAME = "name"; public static final String DATE_ADDED = "date_added"; public static final String SEX = "SEX"; public static final String DEFAULT_SORT_ORDER = "_id desc"; } }
数据库类
public class DBHelper extends SQLiteOpenHelper { private static final String tag = "DBHelper"; public DBHelper( Context context, String name, SQLiteDatabase.CursorFactory factory, int version) { super(context, name, factory, version); } public DBHelper( Context context, String name, SQLiteDatabase.CursorFactory factory, int version, DatabaseErrorHandler errorHandler) { super(context, name, factory, version, errorHandler); } public DBHelper(Context context, String databaseName, int databaseVersion) { this(context,databaseName,null,databaseVersion); } @Override public void onCreate(SQLiteDatabase db) { Log.e(tag,"create table"); db.execSQL("create table " + ContentData.UserTableData.TABLE_NAME + "(" + ContentData.UserTableData._ID + " INTEGER PRIMARY KEY autoincrement," + ContentData.UserTableData.NAME + " varchar(20)," + ContentData.UserTableData.TITLE + " varchar(20)," + ContentData.UserTableData.DATE_ADDED + " long," + ContentData.UserTableData.SEX + " boolean)" + ";"); } @Override public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) { } }
观察更改通知类
public class TeacherObserver extends ContentObserver { public static final String TAG = "TeacherObserver"; private Handler handler; /** * Creates a content observer. * * @param handler The handler to run {@link #onChange} on, or null if none. */ public TeacherObserver(Handler handler) { super(handler); this.handler = handler; } @Override public void onChange(boolean selfChange) { super.onChange(selfChange); Log.i(TAG, "data changed, try to requery."); //向handler发送消息,更新查询记录 Message msg = new Message(); handler.sendMessage(msg); } }