概述 ContentProvider 的作用是为不同的应用之间数据共享,提供统一的接口。安卓系统中应用内部的数据是对外隔离的,要想让其它应用能使用自己的数据(例如通讯录),这个时候就用到了 ContentProvider。
原理 ContentProvider 通过 uri 来标识其它应用要访问的数据,通过 ContentResolver 的增、删、改、查方法实现对共享数据的操作。还可以通过注册 ContentObserver 来监听数据是否发生了变化来对应的刷新页面。
分析 ContentProvider ContentProvider 是一个抽象类,如果我们需要开发自己的内容提供器我们就需要继承这个类并复写其方法,需要实现的主要方法如下:
方法
注释
public boolean onCreate()
在创建 ContentProvider 时使用
public Cursor query()
用于查询指定 uri 的数据返回一个 Cursor
public Uri insert()
用于向指定 uri 的 ContentProvider 中添加数据
public int delete()
用于删除指定 uri 的数据
public int update()
用户更新指定 uri 的数据
public String getType()
用于返回指定的 Uri 中的数据 MIME 类型
数据访问的方法 insert,delete 和 update 可以被多个线程同时调用,此时必须是线程安全的
Uri 其它应用可以通过 ContentResolver 来访问 ContentProvider 提供的数据,而 ContentResolver 通过 uri 来定位自己要访问的数据,所以我们要先了解 uri。URI(Universal Resource Identifier)统一资源定位符,如果你使用过安卓的隐式启动就会发现,在隐式启动的过程中我们也是通过 uri 来定位我们需要打开的 Activity 并且可以在 uri 中传递参数。Uri 的格式如下:
1 [scheme: ][//host:port ][path ][?query ]
举个例子更便于理解
格式
注释
安卓中获取方法
scheme
即协议,这里的协议为 https
getScheme()
host
主机名,这里为 www.baidu.com
getHost()
port
端口,这里为 443
getPost()
path
资源路径,这里为 s
getPath()
query
查询,问号之后的每一个键值对都是一个查询规则,由 & 隔开,这里为 wd=Uri
getQuery()
ContentProvider 示例 定义一个 StudentContentProvider 类用来共享该应用的数据
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 public class StudentContentProvider extends ContentProvider { private static final String AUTHORITY = "cn.hoshinosuzumi.studentProvider" ; private static final int MATCH_CODE = 100 ; private static UriMatcher uriMatcher; private StudentDB studentDB; private static final Uri NOTIFY_URI = Uri.parse("content://" + AUTHORITY + "/student" ); static { uriMatcher = new UriMatcher(UriMatcher.NO_MATCH); uriMatcher.addURI(AUTHORITY, "student" , MATCH_CODE); } @Override public boolean onCreate () { studentDB = StudentDB.getInstance(getContext()); return false ; } @Nullable @Override public Cursor query (@NonNull Uri uri, @Nullable String[] projection, @Nullable String selection, @Nullable String[] selectionArgs, @Nullable String sortOrder) { int match = uriMatcher.match(uri); if (match == MATCH_CODE) { Cursor cursor = studentDB.queryStudent(); return cursor; } return null ; } @Nullable @Override public String getType (@NonNull Uri uri) { return null ; } @Nullable @Override public Uri insert (@NonNull Uri uri, @Nullable ContentValues values) { if (uriMatcher.match(uri) == MATCH_CODE) { studentDB.insertStudent(values); notifyChange(); } return null ; } @Override public int delete (@NonNull Uri uri, @Nullable String selection, @Nullable String[] selectionArgs) { if (uriMatcher.match(uri) == MATCH_CODE) { int delCount = studentDB.deleteStudent(); notifyChange(); return delCount; } return 0 ; } @Override public int update (@NonNull Uri uri, @Nullable ContentValues values, @Nullable String selection, @Nullable String[] selectionArgs) { return 0 ; } private void notifyChange () { getContext().getContentResolver().notifyChange(NOTIFY_URI, null ); } }
ContentProvider 是安卓四大组件之一,所以它和 Activity 一样也需要我们在 xml 文件中声明
1 2 3 4 5 <provider android:name =".StudentContentProvider" android:authorities ="cn.hoshinosuzumi.studentProvider" android:enabled ="true" android:exported ="true" />
这里的 authorities 唯一标识该内容提供者,这样其它的应用才可以找到该内容提供者并操作它的数据;exported 为 true 当前内容提供者可以被其它应用使用,默认为 true。
在 query、insert 和 delete 方法中都是先调用 uriMatcher.match(uri) 判断当前 uri 是不是匹配,如果匹配才能操作数据(该例子没有添加 update 功能,方式与其它三个方法一样),MATCH_CODE是我们在调用 public void addURI (String authority, String path, int code)
方法添加 uri 时设置的,当外部应用传递过来的uri与对应 add 的 uri 一致时,会返回我们设置的 code。
这个例子中整个数据的操作都是通过 SQLite 数据库完成的,在 onCreate 方法中先获得数据库的操作对象,通过该对象完成数据库的增、删、查、改,数据库的实现代码比较简单就不贴了。
在数据库发生变化的时候调用 notifyChange 方法。
ContentObserver 示例 来吧,新建一个项目,在这个项目中来操作刚才自定义的 ContentProvider 中的数据
1 ContentResolver contentResolver = getContentResolver();
注册 ContentObserver 来监听对应 uri 的数据变化,这步时可选的,如果不需要监听数据变化也当然也可以不注册啦~
1 2 3 4 private static final String AUTHORITY = "cn.hoshinosuzumi.studentProvider" ;private static final Uri STUDENT_URI = Uri.parse("content://" + AUTHORITY + "/student" );contentResolver.registerContentObserver(STUDENT_URI, true , new MyContentObserver(handler));
这里在注册 ContentObserver 的方法中需要传递一个 ContentObserver 对象,下面是自定义的 StudentContentObserver 类
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 public class StudentContentObserver extends ContentObserver { Handler mHandler; public StudentContentObserver (Handler handler) { super (handler); mHandler = handler; } @Override public void onChange (boolean selfChange) { super .onChange(selfChange); } @Override public void onChange (boolean selfChange, Uri uri) { super .onChange(selfChange, uri); Message message = Message.obtain(); message.obj = uri; mHandler.sendMessage(message); } }
要自定义 ContentObserver 类,必须实现构造函数,在构造函数中需要传递一个 Handler,即 ContentObserver 在收到数据变化的通知后通过 Handler 机制来通知主线程更新 UI
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 @Override public void onClick (View v) { switch (v.getId()) { case R.id.btn_query: Cursor cursor = contentResolver.query(STUDENT_URI, null , null , null , null , null ); if (cursor != null && cursor.getCount() > 0 ) { while (cursor.moveToNext()) { String name = cursor.getString(cursor.getColumnIndex("name" )); int age = cursor.getInt(cursor.getColumnIndex("age" )); String desc = cursor.getString(cursor.getColumnIndex("desc" )); Log.e(getClass().getSimpleName(), "Student:" + "name = " + name + ",age = " + age + ",desc = " + desc); } cursor.close(); } break ; case R.id.btn_insert: ContentValues contentValues = new ContentValues(); contentValues.put("name" , "无罪不犯的张三" ); contentValues.put("age" , "-1" ); contentValues.put("desc" , "张三XX不给钱" ); contentResolver.insert(STUDENT_URI, contentValues); break ; case R.id.btn_delete: contentResolver.delete(STUDENT_URI, null , null ); break ; } }