自定义ContentProvider以及ContentObserver的使用完整详细示范

自定义ContentProvider以及ContentObserver的使用完整详细示例

示例说明:

该示例中一共包含两个工程。其中一个工程完成了自定义ContentProvider,另外一个工程用于测试该自定义ContentProvider且在该工程中使用了ContentObserver监听自定义ContentProvider的数据变化



以下代码为工程TestContentProvider


ContentProviderTest如下:

package cn.testcontentprovider;
import android.content.ContentProvider;
import android.content.ContentUris;
import android.content.ContentValues;
import android.content.UriMatcher;
import android.database.Cursor;
import android.database.sqlite.SQLiteDatabase;
import android.net.Uri;
/**
 * Demo描述:
 * 自定义ContentProvider的实现
 * ContentProvider主要用于在不同的应用程序之间共享数据,这也是官方推荐的方式.
 * 
 * 注意事项:
 * 1 在AndroidManifest.xml中注册ContentProvider时的属性
 *   android:exported="true"表示允许其他应用访问.
 * 2 注意*和#这两个符号在Uri中的作用
 *   其中*表示匹配任意长度的字符
 *   其中#表示匹配任意长度的数据
 *   所以:
 *   一个能匹配所有表的Uri可以写成:
 *   content://cn.bs.testcontentprovider/*
 *   一个能匹配person表中任意一行的Uri可以写成:
 *   content://cn.bs.testcontentprovider/person/#
 *   
 */
public class ContentProviderTest extends ContentProvider {
	private SQLiteDatabaseOpenHelper mSQLiteDatabaseOpenHelper;
	private final static String  AUTHORITY="cn.bs.testcontentprovider";
	private  static UriMatcher mUriMatcher;
	private static final int PERSON_DIR = 0;
	private static final int PERSON = 1;
	
	/**
	 * 利用静态代码块初始化UriMatcher
	 * 在UriMatcher中包含了多个Uri,每个Uri代表一种操作
	 * 当调用UriMatcher.match(Uri uri)方法时就会返回该uri对应的code;
	 * 比如此处的PERSONS和PERSON
	 */
	static {
		mUriMatcher = new UriMatcher(UriMatcher.NO_MATCH);
		// 该URI表示返回所有的person,其中PERSONS为该特定Uri的标识码
		mUriMatcher.addURI(AUTHORITY, "person", PERSON_DIR);
		// 该URI表示返回某一个person,其中PERSON为该特定Uri的标识码
		mUriMatcher.addURI(AUTHORITY, "person/#", PERSON);
	}
	
	
	/**
	 * 在自定义ContentProvider中必须覆写getType(Uri uri)方法.
	 * 该方法用于获取Uri对象所对应的MIME类型.
	 * 
	 * 一个Uri对应的MIME字符串遵守以下三点:
	 * 1  必须以vnd开头
	 * 2  如果该Uri对应的数据可能包含多条记录,那么返回字符串应该以"vnd.android.cursor.dir/"开头
	 * 3  如果该Uri对应的数据只包含一条记录,那么返回字符串应该以"vnd.android.cursor.item/"开头
	 */
	@Override
	public String getType(Uri uri) {
		switch (mUriMatcher.match(uri)) {
		case PERSON_DIR:
			return "vnd.android.cursor.dir/"+AUTHORITY+".persons";
		case PERSON:
			return "vnd.android.cursor.item/"+AUTHORITY+".person";
		default:
			throw new IllegalArgumentException("unknown uri"+uri.toString());
		}
	}	

	
	@Override
	public boolean onCreate() {
		mSQLiteDatabaseOpenHelper=new SQLiteDatabaseOpenHelper(getContext());
		return true;
	}
	

	/**
	 * 插入操作:
	 * 插入操作只有一种可能:向一张表中插入
	 * 返回结果为新增记录对应的Uri
	 * 方法db.insert()返回结果为新增记录对应的主键值
	 */
	@Override
	public Uri insert(Uri uri, ContentValues values) {
		SQLiteDatabase db = mSQLiteDatabaseOpenHelper.getWritableDatabase();
		switch (mUriMatcher.match(uri)) {
		case PERSON_DIR:
			long newId = db.insert("person", "name,phone,salary", values);
			//向外界通知该ContentProvider里的数据发生了变化 ,以便ContentObserver作出相应 
	        getContext().getContentResolver().notifyChange(uri, null);  
			return ContentUris.withAppendedId(uri, newId);
		default:
			throw new IllegalArgumentException("unknown uri" + uri.toString());
		}
	}
	
	/**
	 * 更新操作:
	 * 更新操作有两种可能:更新一张表或者更新某条数据
	 * 在更新某条数据时原理类似于查询某条数据,见下.
	 */
	@Override
	public int update(Uri uri, ContentValues values, String selection,String[] selectionArgs) {
		SQLiteDatabase db = mSQLiteDatabaseOpenHelper.getWritableDatabase();
		int updatedNum = 0;
		switch (mUriMatcher.match(uri)) {
		// 更新表
		case PERSON_DIR:
			updatedNum = db.update("person", values, selection, selectionArgs);
			break;
		// 按照id更新某条数据
		case PERSON:
			long id = ContentUris.parseId(uri);
			String where = "personid=" + id;
			if (selection != null && !"".equals(selection.trim())) {
				where = selection + " and " + where;
			}
			updatedNum = db.update("person", values, where, selectionArgs);
			break;
		default:
			throw new IllegalArgumentException("unknown uri" + uri.toString());
		}
		//向外界通知该ContentProvider里的数据发生了变化 ,以便ContentObserver作出相应 
        getContext().getContentResolver().notifyChange(uri, null);  
		return updatedNum;
	}
	
	/**
	 * 删除操作:
	 * 删除操作有两种可能:删除一张表或者删除某条数据
	 * 在删除某条数据时原理类似于查询某条数据,见下.
	 */
	@Override
	public int delete(Uri uri, String selection, String[] selectionArgs) {
		SQLiteDatabase db = mSQLiteDatabaseOpenHelper.getWritableDatabase();
		int deletedNum = 0;
		switch (mUriMatcher.match(uri)) {
		// 删除表
		case PERSON_DIR:
			deletedNum = db.delete("person", selection, selectionArgs);
			break;
		// 按照id删除某条数据
		case PERSON:
			long id = ContentUris.parseId(uri);
			String where = "personid=" + id;
			if (selection != null && !"".equals(selection.trim())) {
				where = selection + " and " + where;
			}
			deletedNum = db.delete("person", where, selectionArgs);
			break;
		default:
			throw new IllegalArgumentException("unknown uri" + uri.toString());
		}
		//向外界通知该ContentProvider里的数据发生了变化 ,以便ContentObserver作出相应 
        getContext().getContentResolver().notifyChange(uri, null);  
		return deletedNum;
	}

	/**
	 * 查询操作:
	 * 查询操作有两种可能:查询一张表或者查询某条数据
	 * 
	 * 注意事项:
	 * 在查询某条数据时要注意--因为此处是按照personid来查询
	 * 某条数据,但是同时可能还有其他限制.例如:
	 * 要求personid为2且name为xiaoming1
	 * 所以在查询时分为两步:
	 * 第一步:
	 * 解析出personid放入where查询条件
	 * 第二步:
	 * 判断是否有其他限制(如name),若有则将其组拼到where查询条件.
	 * 
	 * 详细代码见下.
	 */
	@Override
	public Cursor query(Uri uri, String[] projection, String selection,String[] selectionArgs, String sortOrder) {
		SQLiteDatabase db = mSQLiteDatabaseOpenHelper.getWritableDatabase();
		Cursor cursor =null;
		switch (mUriMatcher.match(uri)) {
		// 查询表
		case PERSON_DIR:
			cursor = db.query("person", projection, selection, selectionArgs,null, null, sortOrder);
			break;
		// 按照id查询某条数据
		case PERSON:
			// 第一步:
			long id = ContentUris.parseId(uri);
			String where = "personid=" + id;
			// 第二步:
			if (selection != null && !"".equals(selection.trim())) {
				where = selection + " and " + where;
			}
			cursor = db.query("person", projection, where, selectionArgs, null, null, sortOrder);
			break;
		default:
			throw new IllegalArgumentException("unknown uri" + uri.toString());
		}
		return cursor;
	}
	

}

SQLiteDatabaseOpenHelper如下:
package cn.testcontentprovider;
import android.content.Context;
import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteOpenHelper;
public class SQLiteDatabaseOpenHelper extends SQLiteOpenHelper {
	public SQLiteDatabaseOpenHelper(Context context) {
		super(context, "contentprovidertest.db", null, 1);
	}

	@Override
	public void onCreate(SQLiteDatabase db) {
		db.execSQL("create table person(personid integer primary key autoincrement,name varchar(20),phone varchar(12),salary  Integer(12))");		
	}

	//当数据库版本号发生变化时调用该方法
	@Override
	public void onUpgrade(SQLiteDatabase db, int arg1, int arg2) {
		//db.execSQL("ALTER TABLE person ADD phone varchar(12) NULL");
		//db.execSQL("ALTER TABLE person ADD salary  Integer NULL");
	}

}

MainActivity如下:
package cn.testcontentprovider;
import android.app.Activity;
import android.os.Bundle;
public class MainActivity extends Activity {
	@Override
	protected void onCreate(Bundle savedInstanceState) {
		super.onCreate(savedInstanceState);
		setContentView(R.layout.main);
	}

}

AndroidManifest.xml如下:
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="cn.testcontentprovider"
    android:versionCode="1"
    android:versionName="1.0" >

    <uses-sdk
        android:minSdkVersion="8"
        android:targetSdkVersion="8" />

    <uses-permission android:name="android.permission.INTERNET" />
    <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
    <uses-permission android:name="android.permission.MOUNT_UNMOUNT_FILESYSTEMS" />
    
    <application
        android:allowBackup="true"
        android:icon="@drawable/ic_launcher"
        android:label="@string/app_name"
        android:theme="@style/AppTheme" >
        <activity
            android:name="cn.testcontentprovider.MainActivity"
            android:label="@string/app_name" >
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />

                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>
        
         <provider 
            android:name="cn.testcontentprovider.ContentProviderTest"
            android:authorities="cn.bs.testcontentprovider"
            android:exported="true"
         />
    </application>

</manifest>



main.xml如下:
<RelativeLayout 
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    >

     <Button
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="该应用包含一个自定义的ContentProvider"
        android:textSize="15sp"
     android:layout_centerInParent="true"
    />

</RelativeLayout>




以下代码为工程TestBaidu


MainActivity如下:

package cn.testbaidu;
import android.net.Uri;
import android.os.Bundle;
import android.os.Handler;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.Button;
import android.app.Activity;
import android.content.ContentResolver;
import android.content.ContentValues;
import android.database.ContentObserver;
import android.database.Cursor;
/**
 * Demo描述:
 * 应用A(TestBaidu)调用另外一个应用(TestContentProvider)中的自定义ContentProvider,即:
 * 1 自定义ContentProvider的使用
 * 2 其它应用调用该ContentProvider
 * 3 ContentObserver的使用
 * 
 * 备注说明:
 * 1 该例子在以前版本的基础上整理了代码
 * 2 该例子在以前版本的基础上融合了ContentObserver的使用
 *   利用ContentObserver随时监听ContentProvider的数据变化.
 *   为实现该功能需要在自定义的ContentProvider的insert(),update(),delete()
 *   方法中调用getContext().getContentResolver().notifyChange(uri, null); 
 *   向外界通知该ContentProvider里的数据发生了变化 ,以便ContentObserver作出相应  
 * 
 * 测试方法:
 * 1 依次测试ContentProvider的增查删改(注意该顺序)!!
 * 2 其它应用查询该ContentProvider的数据
 *
 */
public class MainActivity extends Activity {
	private Button mAddButton;
	private Button mDeleteButton;
	private Button mUpdateButton;
	private Button mQueryButton;
	private Button mTypeButton;
	private long lastTime=0;
    private ContentResolver mContentResolver;
    private ContentObserverSubClass mContentObserverSubClass;
	@Override
	protected void onCreate(Bundle savedInstanceState) {
		super.onCreate(savedInstanceState);
		setContentView(R.layout.main);
		init();
		initContentObserver();
	}

	private void init() {
		mContentResolver=this.getContentResolver();
		
		mAddButton=(Button) findViewById(R.id.addButton);
		mAddButton.setOnClickListener(new ClickListenerImpl());
		
		mDeleteButton=(Button) findViewById(R.id.deleteButton);
		mDeleteButton.setOnClickListener(new ClickListenerImpl());
		
		mUpdateButton=(Button) findViewById(R.id.updateButton);
		mUpdateButton.setOnClickListener(new ClickListenerImpl());
		
		mQueryButton=(Button) findViewById(R.id.queryButton);
		mQueryButton.setOnClickListener(new ClickListenerImpl());
		
		mTypeButton=(Button) findViewById(R.id.typeButton);
		mTypeButton.setOnClickListener(new ClickListenerImpl());
		
	}
	
	

	// 注册一个针对ContentProvider的ContentObserver用来观察内容提供者的数据变化
	private void initContentObserver() {
		Uri uri = Uri.parse("content://cn.bs.testcontentprovider/person");
		mContentObserverSubClass=new ContentObserverSubClass(new Handler());
		this.getContentResolver().registerContentObserver(uri, true,mContentObserverSubClass);
	}
	
	@Override
	protected void onDestroy() {
		super.onDestroy();
		if (mContentObserverSubClass!=null) {
			this.getContentResolver().unregisterContentObserver(mContentObserverSubClass);
		}
	}

	
	
	
	// 自定义一个内容观察者ContentObserver
	private class ContentObserverSubClass extends ContentObserver {

		public ContentObserverSubClass(Handler handler) {
			super(handler);
		}

		//采用时间戳避免多次调用onChange( )
		@Override
		public void onChange(boolean selfChange) {
			super.onChange(selfChange);
			System.out.println("ContentObserver onChange() selfChange="+ selfChange);
			if (System.currentTimeMillis()-lastTime>2000) {
				ContentResolver resolver = getContentResolver();
				Uri uri = Uri.parse("content://cn.bs.testcontentprovider/person");
				// 获取最新的一条数据
				Cursor cursor = resolver.query(uri, null, null, null,"personid desc limit 1");
				while (cursor.moveToNext()) {
					int personid = cursor.getInt(cursor.getColumnIndex("personid"));
					System.out.println("内容提供者中的数据发生变化,现数据中第一条数据的personid="+ personid);
				}
				cursor.close();
				lastTime=System.currentTimeMillis();
			}else{
				System.out.println("时间间隔过短,忽略此次更新");
			}
			
			
		}
		
		@Override
		public boolean deliverSelfNotifications() {
		    return true;
		}
		
	}
	
	
	
	

	private class ClickListenerImpl implements OnClickListener {
		@Override
		public void onClick(View v) {
			switch (v.getId()) {
			case R.id.addButton:
				Person person = null;
				for (int i = 0; i < 5; i++) {
					person = new Person("xiaoming" + i, "9527" + i, (8888 + i));
					testInsert(person);
				}
				break;
			case R.id.deleteButton:
				testDelete(1);
				break;
			case R.id.updateButton:
				testUpdate(3);
				break;
			case R.id.queryButton:
				// 查询表
				// queryFromContentProvider(-1);

				// 查询personid=2的数据
				testQuery(2);
				break;
			case R.id.typeButton:
				testType();
				break;
			default:
				break;
			}

		}

	}
	private void testInsert(Person person) {
        ContentValues contentValues=new ContentValues();
        contentValues.put("name", person.getName());
        contentValues.put("phone", person.getPhone());
        contentValues.put("salary",person.getSalary());
        Uri insertUri=Uri.parse("content://cn.bs.testcontentprovider/person");
        Uri returnUri=mContentResolver.insert(insertUri, contentValues);
        System.out.println("新增数据:returnUri="+returnUri);
	}
	
	private void testDelete(int index){
		Uri uri=Uri.parse("content://cn.bs.testcontentprovider/person/"+String.valueOf(index));
		mContentResolver.delete(uri, null, null);
	}
	
	private void testUpdate(int index){
		Uri uri=Uri.parse("content://cn.bs.testcontentprovider/person/"+String.valueOf(index));
		ContentValues values=new ContentValues();
		values.put("name", "hanmeimei");
		values.put("phone", "1234");
		values.put("salary", 333);
		mContentResolver.update(uri, values, null, null);
	}

	private void testQuery(int index) {
		Uri uri=null;
		if (index<=0) {
			//查询表
			uri=Uri.parse("content://cn.bs.testcontentprovider/person");
		} else {
			 //按照id查询某条数据
			uri=Uri.parse("content://cn.bs.testcontentprovider/person/"+String.valueOf(index));
		}
		
		//对应上面的:查询表
		//Cursor cursor= mContentResolver.query(uri, null, null, null, null);
		
		//对应上面的:查询personid=2的数据
		//注意:因为name是varchar字段的,所以应该写作"name='xiaoming1'"
		//     若写成"name=xiaoming1"查询时会报错
		Cursor cursor= mContentResolver.query(uri, null, "name='xiaoming1'", null, null);
		
		while(cursor.moveToNext()){
			int personid=cursor.getInt(cursor.getColumnIndex("personid"));
			String name=cursor.getString(cursor.getColumnIndex("name"));
			String phone=cursor.getString(cursor.getColumnIndex("phone"));
			int salary=cursor.getInt(cursor.getColumnIndex("salary"));
			System.out.println("查询得到:personid=" + personid+",name="+name+",phone="+phone+",salary="+salary);
		}
		cursor.close();
	}
	
	private void testType(){
		Uri dirUri=Uri.parse("content://cn.bs.testcontentprovider/person");
	    String dirType=mContentResolver.getType(dirUri);
	    System.out.println("dirType:"+dirType);
	    
	    Uri itemUri=Uri.parse("content://cn.bs.testcontentprovider/person/3");
	    String itemType=mContentResolver.getType(itemUri);
	    System.out.println("itemType:"+itemType);
	}

}


Person如下:

package cn.testbaidu;

public class Person {
	private Integer id;
	private String name;
	private String phone;
	private Integer salary;
	public Person(String name, String phone,Integer salary) {
		this.name = name;
		this.phone = phone;
		this.salary=salary;
	}
	public Person(Integer id, String name, String phone,Integer salary) {
		this.id = id;
		this.name = name;
		this.phone = phone;
		this.salary=salary;
	}
	public Integer getId() {
		return id;
	}
	public void setId(Integer id) {
		this.id = id;
	}
	public String getName() {
		return name;
	}
	public void setName(String name) {
		this.name = name;
	}
	public String getPhone() {
		return phone;
	}
	public void setPhone(String phone) {
		this.phone = phone;
	}
	public Integer getSalary() {
		return salary;
	}
	public void setSalary(Integer salary) {
		this.salary = salary;
	}
	@Override
	public String toString() {
		return "Person [id=" + id + ", name=" + name + ", phone=" + phone+ ", salary=" + salary + "]";
	}
	
	
	
}

main.xml如下:
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent" >

    <Button
        android:id="@+id/addButton"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_centerHorizontal="true"
        android:layout_marginTop="30dip"
        android:text="增加"
        android:textSize="20sp" />
    
      <Button
        android:id="@+id/queryButton"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_centerHorizontal="true"
        android:layout_marginTop="30dip"
         android:layout_below="@id/addButton"
        android:text="查询"
        android:textSize="20sp" />
    

    <Button
        android:id="@+id/deleteButton"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_centerHorizontal="true"
        android:layout_marginTop="30dip"
        android:layout_below="@id/queryButton"
        android:text="删除"
        android:textSize="20sp" />

    <Button
        android:id="@+id/updateButton"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_centerHorizontal="true"
        android:layout_marginTop="30dip"
         android:layout_below="@id/deleteButton"
        android:text="修改"
        android:textSize="20sp" />

  
      <Button
        android:id="@+id/typeButton"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_centerHorizontal="true"
        android:layout_marginTop="30dip"
         android:layout_below="@id/updateButton"
        android:text="类型"
        android:textSize="20sp" />

</RelativeLayout>