1.1. 什么是内容提供者
内容提供者是Android中的四大组件之一,可以将应用中的数据对外进行共享
内容提供者将数据的访问方式统一,不必针对不同数据类型采取不同的访问策略
内容提供者将数据封装,只暴露出我们希望提供给其他程序的数据
内容提供者中数据更改可被监听
1.2. 创建内容提供者
定义类继承ContentProvider,根据需要重写内部方法
在清单文件的<application>节点下进行配置,<provider>标签中需要指定name和authorities属性
name为类名,包名从程序Package开始,以“.”开始
authorities:是访问Provider时的路径,要唯一
URI代表要操作的数据,由scheme、authorites、path三部分组成
content://cn.itcast.provider.itcast/person
scheme:固定为content,代表访问内容提供者
authorites:<provider>节点中的authorites属性
path:程序定义的路径,可根据业务逻辑定义
1.3. 完成CRUD方法
Ÿ 当程序调用CRUD方法时会传入Uri
Ÿ 我们通过Uri判断调用者要操作的数据
可以使用工具类UriMatcher来判断Uri
addURI方法可以添加Uri
match方法可以匹配一个Uri判断其类型
Ÿ 根据业务逻辑操作数据
1.4. 完成getType方法
Ÿ 如果返回数据是单条数据:vnd.android.cursor.item
Ÿ 如果返回数据是多条数据:vnd.android.cursor.dir
public class PersonProvider extends android.content.ContentProvider { private static final int PERSON = 0; private static final int PERSON_ID = 1; private UriMatcher matcher; private DBOpenHelper helper; @Override public boolean onCreate() { matcher = new UriMatcher(UriMatcher.NO_MATCH); matcher.addURI("com.demo.sqlite.provider", "person", PERSON); // 添加一个可以匹配的Uri matcher.addURI("com.demo.sqlite.provider", "person/#", PERSON_ID); helper = new DBOpenHelper(getContext()); return true; } @Override public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder) { switch (matcher.match(uri)) { // 对Uri进行匹配 case PERSON_ID: String idSelection = "id = " + ContentUris.parseId(uri); // Converts the last path segment to a long. // 注意此处字符串拼装 selection = selection == null ? idSelection : idSelection + " AND " + selection; case PERSON: SQLiteDatabase db = helper.getReadableDatabase(); return db.query("person", projection, selection, selectionArgs, null, null, sortOrder); default: throw new IllegalArgumentException("No Match Uri " + uri); } } @Override public Uri insert(Uri uri, ContentValues values) { switch (matcher.match(uri)) { case PERSON: SQLiteDatabase db = helper.getWritableDatabase(); long id = db.insert("person", null, values); // 使用 db.insert() 方法插入数据,返回 id // 而 db.exceSQL(sql)方式插入数据,返回 void // return Uri.parse("content://com.demo.sqlite.provider/person/" + id); // 与下句效果相同 return ContentUris.withAppendedId(uri, id); // Appends the given ID to the end of the path. default: throw new IllegalArgumentException("No Match Uri " + uri); } } @Override public int delete(Uri uri, String selection, String[] selectionArgs) { switch (matcher.match(uri)) { case PERSON_ID: long id = ContentUris.parseId(uri); selection = selection == null ? "id = " + id : "id = " +id + " AND " +selection; case PERSON: SQLiteDatabase db = helper.getWritableDatabase(); // return : The number of rows affected return db.delete("person", selection, selectionArgs); default: throw new IllegalArgumentException("No Match Uri " + uri); } } @Override public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs) { switch (matcher.match(uri)) { case PERSON_ID: long id = ContentUris.parseId(uri); selection = selection == null ? "id = " + id : "id = " + id + " AND " + selection; case PERSON: SQLiteDatabase db = helper.getWritableDatabase(); // @return the number of rows affected return db.update("person", values, selection, selectionArgs); default: throw new IllegalArgumentException("No Match Uri " + uri); } } @Override public String getType(Uri uri) { switch (matcher.match(uri)) { case PERSON_ID: return "vnd.android.cursor.item/person"; case PERSON: return "vnd.android.cursor.dir/person"; default: throw new IllegalArgumentException("No Match Uri " + uri); } }
1.5. 访问内容提供者
Ÿ 通过Context获得ContentResolver对象
Ÿ 调用ContentResolver对象的方法即可访问内容提供者
private static final String TAG = "ProviderTest"; // 查询 id = 36 public void testQuery1() { // 通过 ContentResolver 调用 ContentProvider 提供的方法 ContentResolver resolver = getContext().getContentResolver(); Uri uri = Uri.parse("content://com.demo.sqlite.provider/person"); Cursor c = resolver.query(uri, new String[] { "id", "name", "balance" }, "id = ?", new String[] { "36" }, null); if (c.moveToNext()) { Person person = new Person(c.getInt(0), c.getString(1), c.getInt(2)); Logger.i(TAG, person.toString()); } } // 查询所有 person public void testQuery2() { // 通过 ContentResolver 调用 ContentProvider 提供的方法 ContentResolver resolver = getContext().getContentResolver(); Uri uri = Uri.parse("content://com.demo.sqlite.provider/person"); Cursor c = resolver.query(uri, null, null, null, null); while (c.moveToNext()) { Person person = new Person(c.getInt(0), c.getString(1), c.getInt(2)); Logger.i(TAG, person.toString()); } } // 通过附带 id 查询 person public void testQuery3() { // 通过 ContentResolver 调用 ContentProvider 提供的方法 ContentResolver resolver = getContext().getContentResolver(); // Uri Uri uri = Uri.parse("content://com.demo.sqlite.provider/person/55"); Cursor c = resolver.query(uri, null, "name = ?", new String[] { "Ashia_54" }, null); while (c.moveToNext()) { Person person = new Person(c.getInt(0), c.getString(1), c.getInt(2)); Logger.i(TAG, person.toString()); } } public void testInsert() { ContentResolver resolver = getContext().getContentResolver(); Uri uri = Uri.parse("content://com.demo.sqlite.provider/person"); ContentValues values = new ContentValues(); Person person = new Person("another Person uri insert", 7000); values.put("name", person.getName()); values.put("balance", person.getBalance()); Uri reUri = resolver.insert(uri, values); Cursor c = resolver.query(reUri, null, null, null, null); if (c.moveToNext()) { Person rePerson = new Person(c.getInt(0), c.getString(1), c.getInt(2)); Logger.i(TAG, rePerson.toString()); } } public void testDelete() { // ContentResolver :This class provides applications access to the content model. ContentResolver resolver = getContext().getContentResolver(); Uri url = Uri.parse("content://com.demo.sqlite.provider/person/121"); // 忘记加 = ?, 异常如下 // android.database.sqlite.SQLiteException: bind or column index out of range long id = resolver.delete(url, "name = ?", new String[] {"zhangsan"}); Logger.i(TAG, id + ""); id = resolver.delete(url, null, null); Logger.i(TAG, id + ""); } public void testDeleteAll() { ContentResolver resolver = getContext().getContentResolver(); Uri url = Uri.parse("content://com.demo.sqlite.provider/person"); int deleteNum = resolver.delete(url, null, null); Logger.i(TAG, deleteNum + ""); } public void testUpdate() { ContentResolver resolver = getContext().getContentResolver(); Uri uri = Uri.parse("content://com.demo.sqlite.provider/person/122"); ContentValues values = new ContentValues(); values.put("balance", 8000); int update = resolver.update(uri, values, "balance = ?", new String[] {"9000"}); Logger.i(TAG, update + ""); } public void testUpdateById() { ContentResolver resolver = getContext().getContentResolver(); ContentValues values = new ContentValues(); values.put("name", "new name"); values.put("balance", 7000); values.put("id", 2); Uri uri = Uri.parse("content://com.demo.sqlite.provider/person/123"); int update = resolver.update(uri, values, null, null); Logger.i(TAG, update + ""); } // Uri写错,异常 // java.lang.IllegalArgumentException: Unknown URI // 主键 id 重复,异常 // android.database.sqlite.SQLiteConstraintException public void testGetType() { ContentResolver resolver = getContext().getContentResolver(); Uri uri = Uri.parse("content://com.demo.sqlite.provider/person/1"); Logger.i(TAG, resolver.getType(uri)); uri = Uri.parse("content://com.demo.sqlite.provider/person"); Logger.i(TAG, resolver.getType(uri)); }
1.6. 监听内容提供者数据变化
Ÿ 在内容提供者中可以通知其他程序数据发生变化
通过Context的getContentResolver()方法获取ContentResolver
调用其notifyChange()方法发送数据修改通知
Ÿ 在其他程序中可以通过ContentObserver监听数据变化
通过Context的getContentResolver()方法获取ContentResolver
调用其registerContentObserver()方法指定对某个Uri注册ContentObserver
自定义ContentObserver,重写onChange()方法获取数据
发送数据修改通知
public Uri insert(Uri uri, ContentValues values) { switch (matcher.match(uri)) { case PERSON: SQLiteDatabase db = helper.getWritableDatabase(); long id = db.insert("person", null, values); // Notify registered observers that a row was updated. 注册 observer // @param observer The observer that originated the change, may be null // 产生改变的Observer. 此处为Provider带来的改变,传 null this.getContext().getContentResolver().notifyChange(uri, null); // 发送修改通知 // return Uri.parse("content://com.demo.sqlite.provider/person/" + id); return ContentUris.withAppendedId(uri, id); default: throw new IllegalArgumentException("No Match Uri " + uri); } }
定义一个ContentObserver,监听Uri所对应的内容提供者的变化
protected static final String TAG = "ObserverActivity"; /** Called when the activity is first created. */ @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.main); // 定义一个ContentObserver ContentObserver observer = new ContentObserver(new Handler()) { /** * Returns true if this observer is interested in notifications for * changes made through the cursor the observer is registered with. */ // 是否传递自己的改变 @Override public boolean deliverSelfNotifications() { return super.deliverSelfNotifications(); } /** * This method is called when a change occurs to the cursor that is * being observed. * * @param selfChange * true if the update was caused by a call to commit on * the cursor that is being observed. */ // 当被监听的内容发生了改变时,调用该方法 @Override public void onChange(boolean selfChange) { Logger.i(TAG, "监听到了变化"); // 打印出最后插入的信息 ContentResolver resolver = getContentResolver(); Uri uri = Uri.parse("content://com.demo.sqlite.provider/person"); // SELECT * FROM person ORDER BY id DESC LIMIT 1; Cursor c = resolver.query(uri, null, null, null, "id DESC LIMIT 1"); if(c.moveToNext()) { String result = "id = " + c.getInt(0) + ", name = " + c.getString(1) + ", balance = " + c.getInt(2); Logger.i(TAG, result); Toast.makeText(ObserverActivity.this, result, 1).show(); } // 父类未做任何实现 super.onChange(selfChange); } }; Uri uri = Uri.parse("content://com.demo.sqlite.provider/person"); // 注册一个ContentObserver /* * @param notifyForDescendents * If true changes to URIs beginning with uri will also cause notifications to be sent. * Iffalse only changes to the exact URI specified by uri will cause notifications to be sent. * If true, than any URI values at or below the specified URI will also trigger a match. */ // 是否监听以 "uri" 开头 的其他 uri getContentResolver().registerContentObserver(uri, true, observer); }
通过ContentObserver,监测内容改变,自动更新ListView
bt_insert = (Button) findViewById(R.id.bt_insert); bt_insert.setOnClickListener(new OnClickListener() { public void onClick(View v) { ContentResolver resolver = ListViewSimpleCursorAdapterActivity.this.getContentResolver(); Uri url = Uri.parse("content://com.demo.sqlite.provider/person"); ContentValues values = new ContentValues(); values.put("name", "test name"); values.put("balance", 3000); resolver.insert(url, values); } }); lv_person.setOnItemClickListener(new MyOnItemClickListener()); Uri uri = Uri.parse("content://com.demo.sqlite.provider/person"); getContentResolver().registerContentObserver(uri, false, new MyContentObserver(new Handler())); public class MyContentObserver extends ContentObserver { public MyContentObserver(Handler handler) { super(handler); } @Override public void onChange(boolean selfChange) { // 当所监听的内容发生了改变,则更新lv中的数据 Cursor c = new PersonDAO(ListViewSimpleCursorAdapterActivity.this).queryAllCursor(); SimpleCursorAdapter adapter = new SimpleCursorAdapter(ListViewSimpleCursorAdapterActivity.this, // R.layout.listview_item, // c, // new String[] { "_id", "name", "balance" }, // new int[] { R.id.tv_id, R.id.tv_name, R.id.tv_balance }); lv_person.setAdapter(adapter); lv_person.setOnItemClickListener(new MyOnItemClickListener()); super.onChange(selfChange); Logger.i(TAG, "onChange"); } }
错误
Uri写错,异常 java.lang.IllegalArgumentException: Unknown URI
主键 id 重复,异常 android.database.sqlite.SQLiteConstraintException
测试ListView更新时,界面总是自动关闭
如果自己是内容提供者,也是内容观察者,
在测试用例中更改内容,应用会强制退出