前天学习ContentProvider时做了一个查询手机通讯录的Demo:http://blog.csdn.net/u010979495/article/details/40394977当时还不怎么理解,所以昨天和今天开始研究自定义ContentProvider类,通过自定义ContentProvider对SQLite的操作来加深理解。
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";
}
}
/**这个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;
}
}
}
"
"
完整的工程地址:http://download.csdn.net/detail/u010979495/8081117