Android学习之自定义ContentProvider类的使用.

前天学习ContentProvider时做了一个查询手机通讯录的Demo:http://blog.csdn.net/u010979495/article/details/40394977当时还不怎么理解,所以昨天和今天开始研究自定义ContentProvider类,通过自定义ContentProvider对SQLite的操作来加深理解。

对ContentProvider练习中的一些总结:

1.创建MyProvider类(继承自ContentProvider类),然后重写里面的各种方法(例如onCreate(),onInsert()...),该类的作用是接收ContentResolver传来的Uri,进行判断,对不同的Uri做不同的处理。

2.需要在Manifest里面配置对应的Provider节点(采用authorities进行唯一标识)。然后在启动该程序时,会自动调用Provider的onCreate()方法。

3.自定义一个数据库管理类MyDBManager(继承自SQLiteOpenHelper),该类的作用是创建数据库(MyProvider_dlc.db)以及更新。

4.在Provider的onCreate()方法里面调用MyDBManager里面的onCreate()注意:在调getReadableDatabase或getWritableDatabase时,会判断指定的数据库是否存在,不存在则调SQLiteDatabase.create创建, onCreate只在数据库第一次创建时才执行

个人在这遇到的问题:一直提示表格错误,原因是第一次创建了一个错误的表格,而没有删除对应的数据库,导致之后就误以为数据库已经存在对应的表格,所以不会继续创建,导致了报错。

解决方法:在应用程序里面将改程序的数据删除,重新部署。这样下一次就会生成一个正确的数据库了。

5.创建一个常量管理类MyContentProviderMetaData(里面管理各种常量,如自定义ContentProvider的URI,数据库的名字,表名等等)

6.创建一个测试类MyContentProviderActivity(通过ContentResolver对象给自定义的ContentProvider发送Uri和操作请求(如删除,查询等),之后ContentProvider对象在进行判断,对不同的Uri和操作做不同的处理)

总结ContentProvider与ContentResolver:

1.外界程序通过ContentResolver访问ContentProvider提供的数据

2.ContentResolver和ContentProvider的关系大概是类似于观察者模式里面的,ContentProvider是watched,ContentResolver是watcher。。。ContentResolver里面提供有notifyChange()接口,当数据改变时发通知其他的ContentObserver(非原创)

Uri的格式:content:://+域名(authorities)+/路径(比如需要操作testTable表,则路径为/testTable)

总结下在这过程中遇到的错误以及解决方法:

1.在使用构造SQL查询语句的辅助类QLiteQueryBuilder qb时,忘记创建对象出来(犯了这个错,log了一大堆东西才找出来)

2.以前有一个老版本的数据库,里面的表创建错了。所以改了代码以后不会重新创建(OnCreate()只会创建一次,如果存在数据库就不会在创建了)
解决方法:把该程序的数据清空(可以直接在手机上操作,也可以通过代码)

3.创建SQL语句时每个变量之间忘了加空格(sql基础)。。。

学习总结:

1.实践检验真知。。。刚开始对这一块十分模糊,慢慢的通过敲代码编程练习,解决各种问题,最终理解就会越来越深刻

2.养成良好的编程习惯很重要。。。代码多了后要在管理代码上做点功夫,比如,常用常量最好放在类里面做静态数据,这样更改起来方便

3.继续努力吧,现在懂得还是太少了!

附录(关键代码):

1.数据库管理类:MyDBManager

/**创建一个数据库帮助类,用来操作数据库*/
public class MyDBManager extends SQLiteOpenHelper{
	private static final String TAG="MyDBManager";
	//重写构造函数
	public MyDBManager(Context context) {
		//context,数据库名,CursorFactory,版本号
		super(context, MyContentProviderMetaData.DATABASE_NAME,null,
MyContentProviderMetaData.DATABASE_VERSION);
	}
	//第一次运行时执行,只要有数据库就不会在运行
	@Override
	public void onCreate(SQLiteDatabase db) {
		//静态内部类也是直接可以访问静态成员的
		//创建表的sql语句
		//注意里面的空格,语法不能出错
		//拼接sql语句的时候引用变量的两端要加上空格
		String sql="CREATE TABLE "
				+TestTableMetaData.TABLE_NAME
				+" ("
				+MyContentProviderMetaData.TestTableMetaData._ID+" INTEGER PRIMARY KEY, "
				+TestTableMetaData.PERSON_NAME+" TEXT, "
				+TestTableMetaData.PERSON_NUMBER+" TEXT"
				+");";
		Log.i(TAG,"创建表:"+db.getPath());
		db.execSQL(sql);
	}

	@Override
	public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
		Log.i(TAG,"更新数据库,从"+oldVersion+"到"
				+newVersion+",以前的数据会被销毁");
		//销毁的sql语句
		String sql="DROP TABLE IF EXISTS"+TestTableMetaData.TABLE_NAME;
		//执行
		db.execSQL(sql);
		//重新创建db
		onCreate(db);
	}
}
2.自定义ContentProvider类MyProvider(仿照Android程序设计基础书本上的代码)

/**创建一个MyProvider类,该类继承ContentProvider,实现其抽象方法来操作数据库,
 * 该类引用MyDBManager类来获得数据库实例*/
public class MyProvider extends ContentProvider{
	private static final String TAG="MyProvider";
	/**创建表列名映射
	 * 类似于创建默认的projection
	 * */
	private static HashMap sTestTableProjectionMap=null;
	static
	{
		//添加一些默认的表
		sTestTableProjectionMap=new HashMap();
		sTestTableProjectionMap.put(TestTableMetaData._ID, TestTableMetaData._ID);
		sTestTableProjectionMap.put
		(TestTableMetaData.PERSON_NAME, TestTableMetaData.PERSON_NAME);
		sTestTableProjectionMap.put
		(TestTableMetaData.PERSON_NUMBER, TestTableMetaData.PERSON_NUMBER);

	}
	/**创建URI最佳匹配器*/
	private static UriMatcher sUriMatcher=null;
	/**注册URI请求类型*/
	//删除数据类型,目前只有这么一种
	private static final int INCOMMING_TESTTABLE_COLLECTION_URI_INDICTOR=1;
	//静态代码块,执行的优先级最高,因为需要在这里面给静态变量初始化
	static{
		//常量UriMatcher.NO_MATCH表示不匹配任何路径的返回码
		sUriMatcher =new UriMatcher(UriMatcher.NO_MATCH);
		//添加需要匹配uri,如果匹配就会返回匹配码
		//如果match()方法匹配
		//MyContentProviderMetaData.AUTHORITY/TestTableMetaData.TABLE_NAME路径,
		//返回匹配码为INCOMMING_TESTTABLE_COLLECTION_URI_INDICTOR(1)
		sUriMatcher.addURI(MyContentProviderMetaData.AUTHORITY, 
				TestTableMetaData.TABLE_NAME,
				INCOMMING_TESTTABLE_COLLECTION_URI_INDICTOR);	
	}
	//数据库帮助类的对象
	public MyDBManager dbHelper=null;
	@Override
	public boolean onCreate() {
		//生成新的数据库帮助类的对象
		Log.i(TAG,TAG+"创建时");
		if(dbHelper==null)
		{
			Log.i(TAG,"生成一个新表");
		dbHelper=new MyDBManager(getContext());
		Log.i(TAG,"db对象:"+dbHelper.toString());
		}
		return true;
	}

	@Override
	public Cursor query(Uri uri, String[] projection, String selection,
			String[] selectionArgs, String sortOrder) {
		//查找功能
		Log.i(TAG,"查询!");
		Cursor cursor=null;
		//构造SQL查询语句的辅助类
		//根据返回值判断是查询全部数据还是某个ID的数据,然后执行不同的方法
		SQLiteQueryBuilder qb=null;
		//记得生成qb---要不然null错误
		qb=new SQLiteQueryBuilder();
		//选择URI
		switch(sUriMatcher.match(uri))
		{
		case INCOMMING_TESTTABLE_COLLECTION_URI_INDICTOR:
			//设置表名字
			qb.setTables(MyContentProviderMetaData.MYPROVIDER_TABLE_NAME);
			//设置projectionMap---必须是HashMap,这是查询全部数据时的设置
			qb.setProjectionMap(sTestTableProjectionMap);
			break;
			//如果是单个数据,设置方法还必须不同
		default:
			throw new IllegalArgumentException("未知URI:"+uri);
		}	
		//排序方法
		String orderBy="";
		if(TextUtils.isEmpty(sortOrder))
		{//默认排序
			orderBy=TestTableMetaData.DEFAULT_SORT_ORDER;
		}
		else
		{
			orderBy=sortOrder;
		}
		//SQLite对象
		//得到一个可读的SQLiteDatabase 实例。
		//这个查询是由SQLiteQueryBuilder来发起的而不是SQLiteDatabase直接发起
				SQLiteDatabase db=dbHelper.getReadableDatabase();
				try {
					Log.i(TAG,"db开始进入表中查询!:");
					cursor=db.query(TestTableMetaData.TABLE_NAME
							,projection,selection,selectionArgs, null, null, orderBy);
			int tempi=cursor.getCount();
			Log.i(TAG,"cursor的数目为:"+tempi);
				} catch (Exception e) {
					Log.i(TAG,"db查询时出错:"+e.getMessage());
				}
		ContentResolver cr=this.getContext().getContentResolver();
		cursor.setNotificationUri(cr, uri);
		return cursor;
	}
	@Override
	public String getType(Uri uri) {
		switch(sUriMatcher.match(uri)){
		case INCOMMING_TESTTABLE_COLLECTION_URI_INDICTOR:
			//还回对应的字符串
			return TestTableMetaData.CONTENT_TYPE;
		default:
			//抛出异常,参数格式错误
			throw new IllegalArgumentException("未知的URI:"+uri);
		}
	}
	@Override
	public Uri insert(Uri uri, ContentValues values) {
		//生成新的数据库帮助类的对象
				if(dbHelper==null)
				{
					Log.i(TAG,"生成一个新表");
				dbHelper=new MyDBManager(getContext());
				Log.i(TAG,dbHelper.toString());
				}
				else
				{
					Log.i(TAG,dbHelper.toString());
				}
		Log.i(TAG,"插入!");
		//判断,如果URI不对,则抛出错误
		if(sUriMatcher.match(uri)!=INCOMMING_TESTTABLE_COLLECTION_URI_INDICTOR)
		{
			throw new IllegalArgumentException("未知URI:"+uri);
		}
		//如果以前没有该数据,其实这个在外部已经做了相应的处理了
		if(values.containsKey(TestTableMetaData.PERSON_NAME)==false)
		{
			throw new SQLException("插入失败,因为人的姓名是必须的!"+uri);
		}
		if(values.containsKey(TestTableMetaData.PERSON_NUMBER)==false)
		{
			//插入一个默认数字为0
			values.put(TestTableMetaData.PERSON_NUMBER, "0");
		}
		//SQLite对象
		Log.i(TAG,"开始插入!");
		SQLiteDatabase db=dbHelper.getWritableDatabase();
		//插入语句估计是找到某一个值,然后就将所有的数据都插入这一行了
		//下面的第二个参数代表数据库里面必须存在的值(不能为空的一个值)
		Log.i(TAG,"db准备完毕!");
		long rowID=db.insert(TestTableMetaData.TABLE_NAME
				,null, values);
		if(rowID>0)
		{
			Uri insertTestTableUri=ContentUris.withAppendedId
					(TestTableMetaData.CONTENT_URI, rowID);
			getContext().getContentResolver().notifyChange
			(insertTestTableUri,null);
			return insertTestTableUri;
		}
		//如果这时候没有返回代表插入出错
		throw new SQLException("插入数据出错:"+uri);
	}
	@Override
	public int delete(Uri uri, String selection, String[] selectionArgs) {
		//生成新的数据库帮助类的对象
		Log.i(TAG,"删除!"+TestTableMetaData.TABLE_NAME);
		//SQLite对象
		SQLiteDatabase db=dbHelper.getWritableDatabase();
		int count=0;
		switch(sUriMatcher.match(uri)){
		case INCOMMING_TESTTABLE_COLLECTION_URI_INDICTOR:
			count=db.delete(TestTableMetaData.TABLE_NAME,
					selection, selectionArgs);
			break;
		default:
			//抛出异常,参数格式错误
			throw new IllegalArgumentException("未知的URI:"+uri);
		}
		this.getContext().getContentResolver().notifyChange(uri, null);
		return count;
	}

	@Override
	public int update(Uri uri, ContentValues values, String selection,
			String[] selectionArgs) {
		Log.i(TAG,"更新!");
		//SQLite对象
		SQLiteDatabase db=dbHelper.getWritableDatabase();
		int count=0;
		switch(sUriMatcher.match(uri)){
		case INCOMMING_TESTTABLE_COLLECTION_URI_INDICTOR:
			count=db.update(TestTableMetaData.TABLE_NAME, values,
					selection, selectionArgs);
			break;
		default:
			//抛出异常,参数格式错误
			throw new IllegalArgumentException("未知的URI:"+uri);
		}
		this.getContext().getContentResolver().notifyChange(uri, null);
		return count;
	}

}
3.放置常量的类MyContentProviderMetaData

/**这个类封装MyProvider里面的常用信息
 * 数据库名,表名,版本号,以及ContenProvider相关的URI常量
 * */
public class MyContentProviderMetaData {
	//数据库名
	public static final String DATABASE_NAME="MyProvider_dlc.db";
	//版本号
	public static final int DATABASE_VERSION=1;
	//授权名称-和manifest里的一样
	//程序通过设置这个Uri才能对自定义里面的数据表进行操作
	public static final String AUTHORITY=
			"dlc.test04.contentprovider.customcontentprovider.myprovider";
	//表名
	public static final String MYPROVIDER_TABLE_NAME="testTable";
	/**内部类,封装关于表的字段信息,实现BaseColumns接口
	 * 开发人员不必在定义字段_ID和_Count了(因为实现了BaseColumns)
	 * static声明--只属于类而不是属于对象
	 * */
	public static final class TestTableMetaData implements BaseColumns{
		//表的属性-表明  属性名
		public static final String TABLE_NAME="testTable";
		public static final String PERSON_NAME="name";
		public static final String PERSON_NUMBER="number";
		//URI,目前只有这么一种类型
		//注意,里面是表名
		public static final Uri CONTENT_URI=Uri.parse
				("content://"+AUTHORITY+"/"+TABLE_NAME);
		//多记录,这是用来URI得到Type(类型为删除数据型时),仿照系统的返回值
		/***此方法返回一个所给Uri的指定数据的MIME类型。它的返回值如果以vnem开头,
		 * 那么就代表这个Uri指定的是单条数据。如果是以vnd.Android.cursor.dir开头的话
		 * ,那么说明这个Uri指定的是全部数据。/
		 */
		public static final String CONTENT_TYPE=
		"vnd.Android.cursor.dir/"
		+".MyProvider_dlc.db.testprovider";
		//DESC(降序),ASC(升序),这句话的意思为按ID升序
		public static final String DEFAULT_SORT_ORDER=_ID+" ASC";
	}
}

4.测试Activity---MyContentProviderActivity

/**这个Activity的功能是用自定义的Provider进行数据读写操作*/
public class MyContentProviderActivity extends testActivity implements OnClickListener{
	private static final String TAG="MyContentProviderActivity";
	//定义按钮,以及其它控件
	//文本域
	private EditText edt_my_name;
	private EditText edt_my_number;
	//按钮
	private Button btn_my_insert;
	private Button btn_my_delete;
	private Button btn_my_query;
	private ListView my_contentView;
	@Override
	protected void onCreate(Bundle savedInstanceState) {
		Log.i(TAG,"创建"+TAG);
		super.onCreate(savedInstanceState);
		setContentView(R.layout.mycontentprovider_view);
		bindButtons();
		setListenerButtons();
		//添加进入全局Activity列表
		Attributes.allActivitys.add( MyContentProviderActivity.this); 
	}
	@Override
	protected void onDestroy() {
		Log.i(TAG,"销毁"+TAG);
		super.onDestroy();
		Attributes.allActivitys.remove(MyContentProviderActivity.this);
	}
	@Override
	public void showMessage() {
		Log.i(TAG,"我是"+TAG+"我被调用来显示消息了!");
	}
	/**功能函数*/
	//绑定按钮
	private void bindButtons()
	{
		Log.i(TAG,"绑定按钮控件!");
		edt_my_name=(EditText) findViewById(R.id.edt_my_name);
		edt_my_number=(EditText) findViewById(R.id.edt_my_number);
		btn_my_insert=(Button) findViewById(R.id.btn_my_insert);
		btn_my_delete=(Button) findViewById(R.id.btn_my_delete);
		btn_my_query=(Button) findViewById(R.id.btn_my_query);
		my_contentView=(ListView) findViewById(R.id.my_content);
	}
	//设置监听
	private void setListenerButtons()
	{
		Log.i(TAG,"设置监听事件!");
		btn_my_insert.setOnClickListener(this);
		btn_my_delete.setOnClickListener(this);
		btn_my_query.setOnClickListener(this);
	}
	//刷新,在这里进行查询
	private void refreshView()
	{
		Log.i(TAG,"刷新查询数据!");
		 List> items = fillMaps(); //查询添加到列表
		 ListAdapter listAdapter = new SimpleAdapter(
                  this,items,R.layout.my_list2,
                  new String[]{"name","key"},
                  new int[]{R.id.list_my_name,R.id.list_my_number});
		 my_contentView.setAdapter(listAdapter);
	}
	/**插入,删除*/
	private void onInsert(Person p)
	{
		try {
			Log.i(TAG,"给数据库插入初始数据!");
			//如果名字是空的,插入无效
			if("".equals(p.getName()))
			{
				Log.w(TAG,"名字为空,插入无效");
				return;
			}
			ContentResolver cr=this.getContentResolver();
			ContentValues cv=new ContentValues();
			cv.put(TestTableMetaData.PERSON_NAME, p.getName());
			cv.put(TestTableMetaData.PERSON_NUMBER, p.getNumber());
			cr.insert(TestTableMetaData.CONTENT_URI, cv);
			Log.i(TAG,"给数据库插入数据成功!");
		} catch (Exception e) {
			Log.e(TAG,"插入数据出错!"+e.getMessage());
		}
	}
	private void onDelete(String name)
	{
		try {
			Log.i(TAG,"删除数据");
			/**使用一下方法删除时,信息删除了,但是联系人列表中会保留一个空值*/
			// 获得ContentResolver对象
					ContentResolver contentResolver =this.getBaseContext().getContentResolver();
					Uri url = TestTableMetaData.CONTENT_URI;
					// 设置删除条件
					String where = TestTableMetaData.PERSON_NAME + "=?";
					String[] selectionArgs = new String[]{ name };
					contentResolver.delete(url, where, selectionArgs);
		} catch (Exception e) {
			Log.i(TAG,"删除数据数据出错:"+e.getMessage());
		}
		
	}
	/**功能函数,填充自己的listMap*/
	 private List> fillMaps()
	 {
		 Log.i(TAG,"开始匹配HashMap");
		 List> items = 
				 new ArrayList>();
		 //自定以Provider的URI
		 Uri contactUri=TestTableMetaData.CONTENT_URI;
		 ContentResolver contentResolver = this.getBaseContext().getContentResolver();
		 Cursor cursor=null;
		  try {
			   cursor = contentResolver.query(
					  contactUri, null, null, null, null);
			   if(cursor==null){ Log.i(TAG,"查询意外,得到null指针!");}
			   Log.i(TAG,"得到Cursor");
			  if(cursor.moveToFirst())
			  {
				  int idColumn = cursor.getColumnIndex(
                         TestTableMetaData._ID);
				  int displayNameColumn = cursor.getColumnIndex(
						  TestTableMetaData.PERSON_NAME);
				  int displayNumberColumn = cursor.getColumnIndex(
						  TestTableMetaData.PERSON_NUMBER);
				  //迭代查询所有用户
				  do
				  {
					  //得到值
					  String contactId = cursor.getString(idColumn);
                     String displayName = cursor.getString(displayNameColumn);
                     String displayNumber=cursor.getString(displayNumberColumn);
                   	  Log.i(TAG,"查询ID为:"+contactId+"!");  
                   	  Log.i(TAG,"该人人"+displayName+"!");
                   	  Log.i(TAG,"号码为:"+displayNumber);
                     //查询所有的电话号码完毕
           		  //添加对应信息     
   				  HashMap i = new HashMap();
                     i.put("name", displayName);
                     i.put("key", displayNumber);
                     items.add(i);
				  }while(cursor.moveToNext());//查询联系人完毕
		
			  }
             else//没有联系人
             {
           	  HashMap i = new HashMap();
                 i.put("name", "Your Custome DB");
                 i.put("key", "Have No DATA!");
                 items.add(i);
             }
			  Log.i(TAG,"查询成功完毕!");
		} catch (Exception e) {
			Log.e(TAG,"查询错误!"+e.getMessage());
		}
		  //记得关闭
		  finally{
			  if(cursor!=null)
			  {
				  cursor.close();
			  }
		  }
		return items;	 
	 }
	@Override
	public void onClick(View v) {
		//获得文本框中的信息
				String name=edt_my_name.getText().toString();
				String number=edt_my_number.getText().toString();
				Person p = new Person(name, number);
				switch(v.getId())
				{
				case R.id.btn_my_insert:
					onInsert(p);
					refreshView();
					break;
				case R.id.btn_my_delete:
					onDelete(name);
					refreshView();
					break;
				case R.id.btn_my_query:
					//想自定义DB读取数据库
					refreshView();
					break;
				default:
					break;
				}
	}
}

5.Manifest里面声明


        
        
            
                
                
                
                
                
            
        
            
            
             "
                	  "
   
   
   
    


 
  

其它的就不上来了!

完整的工程地址:http://download.csdn.net/detail/u010979495/8081117

你可能感兴趣的:(Android学习,ContentProvider)