🔥码云GVP开源项目 12k star Uniapp+ElementUI 功能强大 支持多语言、二开方便! 广告
Android四大组件之Content Provider **一、概念** Content Provider 作为Android应用程序四大组件之一,为存储和查询数据提供统一的接口,实现程序间数据的共享。Android系统内一些常见的数据如音乐、视频、图像等都内置了一系列的Content Provider。      应用程序间共享数据有两种方式: 一是创建子类继承于Content Provider,重写该类用于数据存储和查询的方法。 二是直接使用已经存在的Content Provider,如联系人等。 在Content Provider中数据的是以表的形式存储,在数据表中每一行为一条记录,每一列为类型和数据。每一条记录都包括一个唯一的名叫_ID的数值字段,这个字段唯一标识一条数据记录,在创建表的时候用 INTEGER PRIMARY KEY AUTOINCREMENT来标识此字段。        **二、相关类介绍** **类URI:** 每一个Content Provider为其管理的多个数据集,分配一个URI,这个URI对外提供了一个能够唯一标识自己数据集的字符串。这样别的应用程序就可以通过这个URI来访问这个数据集。Android中所有的Content Provider的URI都有固定格式:content://**开头,一般可分为4个部分: **标准前缀:**用来标识一个Content Provider,固定Content:// **URI标识:**定义了是哪个Content Provider提供这些数据。一般是定义该ContentProvider的包类的名字。和AndroidManifest.xml中定义的authorities属性相同。 **路径:**标识URI下的某一个Item。 **记录的ID:**如果URI中包含表示某个记录的ID,则返货该id对应的数据。否则表示返回全部。 注意:"content://com.android.people.provider/contacts/#" 这里#表示匹配任意数字"content://com.android.people.provider/contacts/*"表示匹配任意文本 **UriMatcher:**Uri标识了要操作的数据,而UriMatcher即使Android提供给我们用于操作Uri这个数据的工具类。 常用方法:         public void addURI(String authority, String path, int code) 往UriMatcher对象里面添加Uri。         public int match(Uri uri) 与UriMatcher对象的Uri进行匹配,如果成功返回上面传入的code值,否则返回-1. **类ContentUris:**类似于UriMatcher,也是一个操作Uri数据的工具类,用于在Uri后面追加一个ID或解析出传入的Uri对象的ID值。 常用方法:         public static Uri withAppendId(Uri contentUri, long id)  为前面contentUri加上ID部分         public static long parseId(Uri contentUri) 从contentUri中获取ID部分 **类ContentProvider:**常用方法: ~~~ public abstract boolean onCreate(); public abstract Uri insert(Uri uri, ContentValues values) public abstract int delete(Uri uri, String selection, String[] selectionArsg); public abstract int update(Uri uri, ContentValues values, String selection, String[] selectionArgs); public abstract Cursor query(Uri uri, String[] peojection, String selection, String[] selectionArgs, String sortOrder) public abstract String getType(Uri uri) ~~~ 这些都是抽象方法需要子类去实现。**类 ContentValues:**        android.content.ContentValues 这个用于存储ContentResolver能处理的数据的集合。 构造函数:        ContentValues()         // 创建一个空的ContentValues对象,初始化默认大小        ContentValues(int size)        ContentValues(ContentValues from) 常用方法:         ~~~ void clear() boolean containKey(String key) Object get(String key) void put(String key, Type value) // Type: Byte Integer Float Short byte[] String Double Long Boolean int size() Type getAsTypeArray(String key) // Type: Object Boolean Byte byte[] Double Float Integer Long Short String ~~~ **类android.content.ContentResolver:** 一个程序可以通过实现一个ContentProvider的抽象接口将自己的数据以类似数据库中表的方式完全暴露出去,那么外界其他应用程序怎么从数据库中获取数据呢,这就需要ContentResolver了,通过URI表示外界访问需要的数据库。           这个类为我们定义了一系列的方法包括:插入、删除、修改、查询等,与ContentProvider基本类似,主要根据传入的参数Uri找到对应的Content Provider,调用其相应的方法。 构造函数:         public ContentResolver(Context context) 一般在代码中我们直接通过: MainActivity.this.getContentResolver()获得当前应用程序的一个ContentResolver实例。       这里我们需要考虑一个问题,就是如果多个程序同时通过ContentResolver共享访问一个ContentProvider,会不会不同步,尤其是数据写入的时候这就需要在AndroidManifest.xml中定义ContentProvider的时候加上:<provider>元素的multiprocess属性。同时Android在ContentResolver中为我们提供了 notifyChange()接口,在数据发生改变时通知其他的ContentObserver。 ~~~ final void registerContentObserver(Uri uri, boolean notifyForDescendents, ContentObserver observer) final void unregisterContentObserver(ContentObserver observer) void notifyChange(Uri uri, ContentObserver observer, boolean syncToNetwork) void notifyChange(Uri uri, ContentObserver observer) ~~~ **三、使用已经存在的ContentProvider** 使用已经存在的ContentProvider包括两种情况:一是从已经存在的ContentProvider里面读取数据,如获得 手机里面联系人的信息。二是将自己的数据加入到ContentProvider里面,让其他程序能共享次数据,这就需要获得对这个 ContentProvider的写权限了。 Android中电话簿就是通过ContentProvider实现数据共享的,系统中有很多已经存在的共享URI,我们可以使用ContentResolver通过Uri来操作不同的标的数据。如Contacts.People.CONTENT_URI 在Android中为我们提供了两种方法来查询Content Provider:一是使用ContentResolver的query()方法,二是使用Activity对象的manageQuery()方法,他们的参数都相同,而且都返回Cursor对象。但是使用manageQuery()方法返回的Cursor对象的生命周期自动被Activity来管理,被管理的Cursor对象在Activity进入暂停状态的时候调用自己的deactivate()方法卸载,在Activity回到运行状态的时候调用自己的requery()方法重新查询生成的Cursor。而使用ContentResolver的query()方法返回的Cursor对象需要手动加入Activity来管理,这是通过Activity的startManagingCursor()方法来实现的。 **四、创建自己的ContentProvider** (1) 创建一个继承于ContentProvider的类MyContentProvider (2) 定义一个public static final Uri 类型变量CONTENT_URI 如:public static final Uri CONTENT_URI = Uri.parse("content://com.android.MyContentProvider") (3) 定义需要返回给客户端的数据列名,如果使用到数据库SQLite,必须定义一个_id,表示记录的唯一性。 (4) 创建数据存储系统,如文件系统或数据库SQLite系统。 (5) 如果存储字节型数据,如位图文件等,数据列其实是一个表示实际保存文件的URI字符串,用来读取对应的实际文件数据。 处理这种数据类型的Content Provider需要实现一个名为_data的字段,该字段列出了该文件在Android系统的实际路 径,客户端可以通过调用方法ContentResolver. openOutputStream()来处理该URI指向的文件资源。 (6) 查询返回一个Cursor类型对象,所有执行写操作的方法如insert()、update()及delete()都将被监听,可以通过Content Resolver().notifyChange()来通过监听器关于数据更新的消息。 (7) 在AndroidManifest.xml中使用<provider>标签来设置Content Provider信息,如:android:authorities、android:name android:permission等。 **Demo:** **数据库类DatabaseHelper用来存储个人信息,名字(name)对应年龄(age)** ~~~ public class DatabaseHelper extends SQLiteOpenHelper { private static final String DB_NAME = "personInfo.db"; private static final String TB_NAME = "person"; private static final int VERSION = 1; private static final String creat_cmd = "create table IF NOT EXISTS " + TB_NAME + " (_id integer PRIMARY KEY autoincrement, name text, age integer)"; private static final String upgrade_cmd = "alert table " + TB_NAME + " add sex varchar(8)"; public DatabaseHelper(Context context) { super(context, DB_NAME, null, VERSION); // TODO Auto-generated constructor stub } @Override public void onCreate(SQLiteDatabase db) { // TODO Auto-generated method stub db.execSQL(creat_cmd); } @Override public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) { // TODO Auto-generated method stub db.execSQL(upgrade_cmd); } } ~~~ **创建自己的ContentProvider** ~~~ // 创建一个MyProvider继承于ContentProvider public class MyProvider extends ContentProvider { private DatabaseHelper dbHelper; // 数据库类 private static final UriMatcher URI_MATCHER = new UriMatcher(UriMatcher.NO_MATCH); private static final int ALL_PERSON = 1; private static final int PERSON = 2; private static final String TAG = "MyProvider"; private static final String TABLE_NAME = "person"; // 定义自己的URI private static final String AUTHORITY = "com.myAndroid.myProvider"; public static final Uri CONTENT_URI = Uri.parse("content://"+AUTHORITY+"/"+TABLE_NAME); static { URI_MATCHER.addURI(AUTHORITY, "person", ALL_PERSON); URI_MATCHER.addURI(AUTHORITY, "person/#", PERSON); } // ContentProvider的入口,初始化 @Override public boolean onCreate() { // TODO Auto-generated method stub Log.i(TAG, "----onCreate----"); dbHelper = new DatabaseHelper(this.getContext()); return false; } @Override public int delete(Uri arg0, String arg1, String[] arg2) { // TODO Auto-generated method stub Log.i(TAG, "----delete----"); SQLiteDatabase db = dbHelper.getWritableDatabase(); int count = 0; switch (URI_MATCHER.match(arg0)) { case ALL_PERSON: count = db.delete(TABLE_NAME, arg1, arg2); case PERSON: long id = ContentUris.parseId(arg0); String where = "_id=" + id; if(arg1 != null && !"".equals(arg1)) { where += " and " + arg1; } count = db.delete(TABLE_NAME, where, arg2); default: throw new IllegalArgumentException("Unknown Uri:" + arg0.toString()); } getContext().getContentResolver().notifyChange(arg0, null); // 通知注册客户端数据发生改变 return count; } @Override public String getType(Uri arg0) { // TODO Auto-generated method stub Log.i(TAG, "----getType----"); switch (URI_MATCHER.match(arg0)) { case ALL_PERSON: return "com.android.cursor.dir/person"; case PERSON: return "com.android.cursor.item/person"; default: throw new IllegalArgumentException("Unknow Uri" + arg0.toString()); } } @Override public Uri insert(Uri uri, ContentValues arg1) { // TODO Auto-generated method stub Log.i(TAG, "----insert----"); SQLiteDatabase db = dbHelper.getWritableDatabase(); Uri insertUri = null; switch (URI_MATCHER.match(uri)) { case ALL_PERSON: long rowId = db.insert(TABLE_NAME, "name", arg1); Log.d(TAG, "insert:"+arg1.toString()+" Id:"+rowId); insertUri = ContentUris.withAppendedId(uri, rowId); // this.getContext().getContentResolver().notifyChange(insertUri, null); break; default: throw new IllegalArgumentException("Unknown Uri:" + uri.toString()); } getContext().getContentResolver().notifyChange(insertUri, null); return insertUri; } // 处理查询,返回Cursor @Override public Cursor query(Uri arg0, String[] arg1, String arg2, String[] arg3, String arg4) { // TODO Auto-generated method stub Log.i(TAG, "----query----"); SQLiteDatabase db = dbHelper.getReadableDatabase(); Cursir cursor = null; switch (URI_MATCHER.match(arg0)) { case ALL_PERSON: // query all the person info cursor = db.query(TABLE_NAME, arg1, arg2, arg3, null, null, arg4); case PERSON: // query only one person info from a given ID long id = ContentUris.parseId(arg0); String where = " _id=" + id; if( (!"".equals(arg2)) && (arg2 != null)) { where += " and " + arg2 ; } cursor = db.query(TABLE_NAME, arg1, where, arg3, null, null, arg4); default: throw new IllegalArgumentException("unknow uri" + arg0.toString()); } if(cursor != null) { // 注册该Uri对应的数据发生改变时,向客户端发送通知 cursor.setNotificationUri(getContext().getContentResolver(), uri); } return cursor; } @Override public int update(Uri arg0, ContentValues arg1, String arg2, String[] arg3) { // TODO Auto-generated method stub Log.i(TAG, "----update----"); SQLiteDatabase db = dbHelper.getWritableDatabase(); int count = 0; switch (URI_MATCHER.match(arg0)) { case ALL_PERSON: count = db.update(TABLE_NAME, arg1, arg2, arg3); break; case PERSON: long id = ContentUris.parseId(arg0); String where = "_id=" + id; if( (arg2 != null) && (!"".equals(arg2))) { where += " and " + arg2; } count = db.update(TABLE_NAME, arg1, where, arg3); break; default: throw new IllegalArgumentException("Unknow Uri:"+arg0.toString()); } getContext().getContentResolver().notifyChange(arg0, null); return count; } } ~~~ **客户端使用Content Provider:** ~~~ // 定义自己的URI private static final String TABLE_NAME = "person"; private static final String AUTHORITY = "com.myAndroid.myProvider"; public static final Uri CONTENT_URI = Uri.parse("content://"+AUTHORITY+"/"+TABLE_NAME); // 插入 ContentResolver contenResolver = MainActivity.this.getContentResolver(); ContentValues values = new ContentValues(); values.put("name", "hello"); values.put("age", 25); Uri resultUri = contentResolver.insert(CONTENT_URI, values); if(ContentUris.parseId(resultUri) > 0) { Log.i(TAG, "OK"); } // 查询 String columns[] = new String[] ("_id", "name", "age"); Cursor cursor = contentResolver.query(CONTENT_URI, columns, null, null, "_id"); if(cursor.moveToFirst()) { do { Log.i(TAG, "_id:"+cursor.getInt(cursor.getColumnIndex("_id"))); Log.i(TAG, "name:"+cursor.getString(cursor.getColumnIndex("name"))); Log.i(TAG, "age:"+cursor.getInt(cursor.getColumnIndex("age"))); } while(cursor.moveToNext()); cursor.close(); } // 删除 ID为1的记录 contentResolver.update(Uri.parse("content://"+AUTHORITY+"/"+TABLE_NAME+"/1"), null, null); // 修改ID为 1 的记录 ContentValues values = new ContentValues(); values.put("name", "world"); values.put("age", 32); contentResolver.update(Uri.parse("content://"+AUTHORITY+"/"+TABLE_NAME+"/1"), values, null, null); ~~~ 最后我们还需要在AndroidManifest.xml中定义我们的provider属性: ~~~ <provider android:name=".MyProvider" android:authorities="com.myAndroid.myProvider" /> ~~~ **使用游标:** 这里我们关注下在query()方法中,我们查询数据库的时候使用到了一个Cursor对象,这个Cursor对象就是游标,它包含0个或多个记录。列名称、顺序和类型都是特定于ContentProvider的,但是返回的每行都包涵啊一个名为_id的默认咧,表示该行的唯一ID。  1、游标是一个行集合       2、读取数据之前,需要使用moveToFirst()将游标移动到第一行之前  3、需要知道列名称和列类型  4、所有字段的访问都是基于列编号,所有必须首先将该列名称转换为列编号  5,、游标可以随意移动(往前、往后,跳过一段距离等) 主要方法: ~~~ boolean moveToFirst() boolean isBeforeFirst() boolean isAfterLast() boolean isClosed() ~~~ **使用Where查询:** 上面代码中使用的manageQuery()的签名为: public final Cursor manageQuery(Uri uri, String[] projectin,  String selection, String[] selectionArgs, String sortOrder); 参数: Uri: 给定的URI projection: 声明要返回的行属性,传递null 返回给定URI的所有行 selection 表示过滤器,以SQL Where字句(不含Where本身)的格式声明,可以使用?,将被替换为selectionArgs中的值,按照在列表中出现的顺序显示。 selectionArgs: 过滤器残数 sortOrder: 排序方式 如:查询id为23的笔记: ~~~ manageQuery("Content://com.google.provider.NotePad/notes/23", null, null, null, null); manageQuery("Content://com.google.provider.Notepad/notes", null, "_id=?", new String[] {23}, null); ~~~