AndroidのContentProvider之数据库更新UI
---恢复内容开始---
快过年了,明年还要找工作。。。。。
之前写过很多android各种知识点的demo,但是过了很久没用到就会忘掉。
现在复习一下。
这篇用到的知识点有几个:SQLiteOpenHelper,Cursor,CursorAdapter,ContentProvider,ContentObserver
对于数据库的操控一般都是用SQLiteOpenHelper,创建该类实例,可以得到一个SQLiteDatabase,而实际上操作数据库用的还是这个。
(一)我们先看下SQLiteOpenHelper这个类的实例
package com.bvin.study.observer; import android.content.Context; import android.database.sqlite.SQLiteDatabase; import android.database.sqlite.SQLiteOpenHelper; public class DBHelper extends SQLiteOpenHelper{ public DBHelper(Context context){ super(context,"cache",null,Config.DATA_VERSION); } @Override public void onCreate(SQLiteDatabase db) { // TODO Auto-generated method stub db.execSQL("create table person(_id integer primary key autoincrement,name string,sex string,age integer)"); } @Override public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) { // TODO Auto-generated method stub } }
这个类主要负责数据库的创建和版本的更新管理。
一般情况下,是可以过去这个类的实例也就是SQLiteDatabase实例去做增删查改的工作。SQLiteDatabase已经封装了对数据库的各种操作方法,而且还提供了一个db.execSQL(sql, bindArgs)方法来执行原生sql语句,有人说这种方式比它封装的方法更高效。但是execSQL()是不返回值的,下面看下那些个方法都返回些什么值。
SQLiteDatabase
1.public long insert (String table, String nullColumnHack, ContentValues values)
这里是向一个表插入数据,values有把要插入的数据和表中的字段对应起来,返回的是插入到表中该行所在得行id。
2.public int update (String table, ContentValues values, String whereClause, String[] whereArgs)
这个方法后面的是按照什么样的条件去更新符合条件的记录,返回更新的记录所在行。
3.public Cursor query (String table, String[] columns, String selection, String[] selectionArgs, String groupBy, String having, String orderBy, String limit)
返回查询到的结果集
4.public int delete (String table, String whereClause, String[] whereArgs)
返回删除的哪行
而查询可以 用异步查询AsyncQueryHandler ,其实这个类也可以执行增删查该,分别override每个方法来做出响应处理
(二)ContentProvider
这是android四大组件之一,不需要手动实例化,只要在Androidmanifest.xml里面把ContentProvider注册上去就行。
<provider android:name=".MyProvide" android:authorities="com.bvin.study.observer.MyProvide"/>
android:name即指定类名,android:authorities即绑定唯一的标识符,而这个一定要和URI的一样。
ContentProvider的uri形式跟域名很像:content://authorities//path,这个uri是非常重要的东西。
path可以表示某张表再后面可以指定某一行的某个字段,像/person/18/name就表示person表中第18个人的name
ui叫统一资源定位符,这样就唯一标识了这个资源,别的应用程序就可以用你的数据了,android系统本身就提供了大量的provider如短信,联系人等
好,uri讲完了就轮到ContentProvider了。
ContentProvider一般不主动构造,它跟Activity一样提供了一个入口方法。光有ContentProvider不行啊,所谓巧妇难为无米之炊啊。所以米在哪里呢,米就用我们做好的DBHelper。DBHelper获取数据库SQLiteDatabase,这个才是真正的数据库。再ContentProvider里覆盖操作数据库的各种方法,然后用SQLiteDatabase去执行各个操作。
看看ContentProvider内部的实现
package com.bvin.study.observer; import android.content.ContentProvider; import android.content.ContentValues; import android.database.Cursor; import android.net.Uri; public class MyProvide extends ContentProvider{ DBHelper dbHelper; @Override public int delete(Uri uri, String selection, String[] selectionArgs) { // TODO Auto-generated method stub return 0; } @Override public String getType(Uri uri) { // TODO Auto-generated method stub return null; } @Override public Uri insert(Uri uri, ContentValues values) { // TODO Auto-generated method stub long rowId = -1; rowId =dbHelper.getWritableDatabase().insert(Config.TABLE_NAME, null, values); Uri cururi = Uri.withAppendedPath(Config.CONTENT_URI, rowId+""); this.getContext().getContentResolver().notifyChange(uri,null); return cururi; } @Override public boolean onCreate() { // TODO Auto-generated method stub dbHelper = new DBHelper(getContext()); return true; } @Override public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder) { // TODO Auto-generated method stub return null; } @Override public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs) { // TODO Auto-generated method stub return 0; } }
这里仅用了插入这个方法,但外部是怎么调用的呢?用到是个叫ContentResolver,这个类可以通过Context上下文获取,ContentResolver也有insert(),update(),query().delete()方法,但ContentResolver的方法是暴露在外面的方法,真正执行的是ContetnProvider。ContentResolver就像一辆车它会前进,后退,转弯,刹车,而ContetnProvider就是司机,外面看到的车在转弯在刹车都是司机在操作,而这些功能司机有吗?没有司机不能代表车转弯刹车,这些核心的功能是汽车方向盘和刹车的功能,所以SQLiteDatabase就是方向盘咯,这个比喻可能不是很恰当。
(三)ContentResolver
前面的都是功能接口服务的提供,接下来我们看如何去调用。这里用了一个ListActivity来调用以上定义的服务和数据可视化,主界面是个Listview,对数据库的操作用菜单来作用,四个菜单分别是增删查改。
@Override public boolean onCreateOptionsMenu(Menu menu) { // TODO Auto-generated method stub menu.add(0, 0, 0, "添加"); menu.add(0, 1, 1, "查询"); menu.add(0, 2, 2, "更改"); menu.add(0, 3, 3, "删除"); return super.onCreateOptionsMenu(menu); } @Override public boolean onOptionsItemSelected(MenuItem item) { // TODO Auto-generated method stub switch(item.getItemId()){ case 0: ContentValues cv = new ContentValues(); cv.put("name", "楚凌"); getContentResolver().insert(Config.CONTENT_URI, cv); break; case 1: break; case 2: dbHelper.getWritableDatabase().execSQL("update "+Config.TABLE_NAME+" set name = 'chuling' where _id = 2"); adapter.notifyDataSetChanged(); break; case 3: dbHelper.getWritableDatabase().delete(Config.TABLE_NAME, null, null); break; } return super.onOptionsItemSelected(item); }
如果按以上代码,执行插入操作listview是不能及时先出来的,重启程序之后就可以看到新加的项。那要怎样才能插入一个就显示一个也就是更新数据及时刷新UI界面,因为ListView绑定的是CursorAdapter而这个cursor是充数据库查询出来的,所以要做的事情有两个。一个是数据源更新然后就是适配器去通知ui数据已改变,cursor就要重新查询一遍,或者adapter更换游标,然后再调用adapter.notifyDataSetChanged();但是cursor.requery()执行会影响效率,而光靠adapter.notifyDataSetChanged()界面是不会更新的,本来想用ContentObserver来实现,最后还是感觉不适合,有没有数据库变动及时更新Listview的良策???
DBHelper dbHelper = null; SimpleCursorAdapter adapter; Cursor cursor; Handler handler; MyObserver observer; @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); //setContentView(R.layout.main); dbHelper = new DBHelper(this); handler = new Handler(){ @Override public void handleMessage(Message msg) { // TODO Auto-generated method stub super.handleMessage(msg); if(msg.what==2013){ if(cursor!=null&&!cursor.isClosed()&&adapter!=null){ adapter.notifyDataSetChanged(); } } } }; cursor = dbHelper.getReadableDatabase().rawQuery("select * from "+Config.TABLE_NAME, null); adapter = new SimpleCursorAdapter(this,android.R.layout.simple_list_item_1,cursor, new String[]{"name"}, new int[]{android.R.id.text1}); setListAdapter(adapter); observer = new MyObserver(this,handler,cursor); getContentResolver().registerContentObserver(Config.CONTENT_URI, false, observer); }
其实这里看到Config.CONTENT_URI,其实这个就是指向的数据库的路径,就是牵着风筝的一条线,牵着线的手是ContentResolver,风筝当然就是ContetnProvider咯!
这里没看cursor.requery()哈,其实这个我已经放在ContentObserver里了。上面还有个handler,这个handler是用来注册ContentObserver的,这里将会处理观察者发出的消息。
(四) ContentObserver
这个类的构造方法必须有个Handler的参数来进行通信,还有个必须的就是必须覆盖onChanged()方法,用handler去发送message跟Activity去通信。
package com.bvin.study.observer; import android.content.AsyncQueryHandler; import android.content.ContentResolver; import android.content.Context; import android.database.ContentObserver; import android.database.Cursor; import android.net.Uri; import android.os.AsyncTask; import android.os.Handler; import android.os.Looper; import android.os.Message; public class MyObserver extends ContentObserver{ private Handler handler; private Cursor cursor; private Context context; public MyObserver(Context context,Handler handler,Cursor cursor){ super(handler); this.context = context; this.handler = handler; this.cursor = cursor; } @Override public void onChange(boolean selfChange) { // TODO Auto-generated method stub super.onChange(selfChange); cursor.requery(); handler.sendEmptyMessage(2013); } }
我这里把cursor传进去,目的就是注册的URI指向的数据库更新时,就在onChanged()方法里再查询一边,然后Activity里的Handler处理消息的方法里用Adapter通知一下,界面就会及时更新了。
但是用这个去更新Listview也不太现实,handler里处理一下其他的ui元素还是可以的,比如插入数据,就蹦出个对话框“数据插入完毕”。。。
下面还是发下全部的代码,貌似还不可以传附件。。
主界面:MainActivity.java
package com.bvin.study.observer; import android.app.ListActivity; import android.content.AsyncQueryHandler; import android.content.ContentResolver; import android.content.ContentValues; import android.database.Cursor; import android.net.Uri; import android.os.Bundle; import android.os.Handler; import android.os.Looper; import android.os.Message; import android.view.Menu; import android.view.MenuItem; import android.widget.SimpleCursorAdapter; public class MainActivity extends ListActivity { /** Called when the activity is first created. */ DBHelper dbHelper = null; SimpleCursorAdapter adapter; Cursor cursor; Handler handler; MyObserver observer; @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); //setContentView(R.layout.main); dbHelper = new DBHelper(this); handler = new Handler(){ @Override public void handleMessage(Message msg) { // TODO Auto-generated method stub super.handleMessage(msg); if(msg.what==2013){ if(cursor!=null&&!cursor.isClosed()&&adapter!=null){ adapter.notifyDataSetChanged(); } } } }; cursor = dbHelper.getReadableDatabase().rawQuery("select * from "+Config.TABLE_NAME, null); adapter = new SimpleCursorAdapter(this,android.R.layout.simple_list_item_1,cursor, new String[]{"name"}, new int[]{android.R.id.text1}); setListAdapter(adapter); observer = new MyObserver(this,handler,cursor); getContentResolver().registerContentObserver(Config.CONTENT_URI, false, observer); } @Override public boolean onCreateOptionsMenu(Menu menu) { // TODO Auto-generated method stub menu.add(0, 0, 0, "添加"); menu.add(0, 1, 1, "查询"); menu.add(0, 2, 2, "更改"); menu.add(0, 3, 3, "删除"); return super.onCreateOptionsMenu(menu); } @Override public boolean onOptionsItemSelected(MenuItem item) { // TODO Auto-generated method stub switch(item.getItemId()){ case 0: ContentValues cv = new ContentValues(); cv.put("name", "楚凌"); getContentResolver().insert(Config.CONTENT_URI, cv); break; case 1: break; case 2: dbHelper.getWritableDatabase().execSQL("update "+Config.TABLE_NAME+" set name = 'chuling' where _id = 2"); adapter.notifyDataSetChanged(); break; case 3: dbHelper.getWritableDatabase().delete(Config.TABLE_NAME, null, null); break; } return super.onOptionsItemSelected(item); } @Override protected void onDestroy() { // TODO Auto-generated method stub super.onDestroy(); if(dbHelper!=null){ dbHelper.close(); } getContentResolver().unregisterContentObserver(observer); } }
MyProvider.java
package com.bvin.study.observer; import android.content.ContentProvider; import android.content.ContentValues; import android.database.Cursor; import android.net.Uri; public class MyProvide extends ContentProvider{ DBHelper dbHelper; @Override public int delete(Uri uri, String selection, String[] selectionArgs) { // TODO Auto-generated method stub return 0; } @Override public String getType(Uri uri) { // TODO Auto-generated method stub return null; } @Override public Uri insert(Uri uri, ContentValues values) { // TODO Auto-generated method stub long rowId = -1; rowId =dbHelper.getWritableDatabase().insert(Config.TABLE_NAME, null, values); Uri cururi = Uri.withAppendedPath(Config.CONTENT_URI, rowId+""); this.getContext().getContentResolver().notifyChange(uri,null); return cururi; } @Override public boolean onCreate() { // TODO Auto-generated method stub dbHelper = new DBHelper(getContext()); return true; } @Override public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder) { // TODO Auto-generated method stub return null; } @Override public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs) { // TODO Auto-generated method stub return 0; } }
DBHelper.java
package com.bvin.study.observer; import android.content.Context; import android.database.sqlite.SQLiteDatabase; import android.database.sqlite.SQLiteOpenHelper; public class DBHelper extends SQLiteOpenHelper{ public DBHelper(Context context){ super(context,"cache",null,Config.DATA_VERSION); } @Override public void onCreate(SQLiteDatabase db) { // TODO Auto-generated method stub db.execSQL("create table person(_id integer primary key autoincrement,name string,sex string,age integer)"); } @Override public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) { // TODO Auto-generated method stub } }
MyObserver.java
package com.bvin.study.observer; import android.content.AsyncQueryHandler; import android.content.ContentResolver; import android.content.Context; import android.database.ContentObserver; import android.database.Cursor; import android.net.Uri; import android.os.AsyncTask; import android.os.Handler; import android.os.Looper; import android.os.Message; public class MyObserver extends ContentObserver{ private Handler handler; private Cursor cursor; private Context context; public MyObserver(Context context,Handler handler,Cursor cursor){ super(handler); this.context = context; this.handler = handler; this.cursor = cursor; } @Override public void onChange(boolean selfChange) { // TODO Auto-generated method stub super.onChange(selfChange); cursor.requery(); handler.sendEmptyMessage(2013); } }
Config.java
package com.bvin.study.observer; import android.net.Uri; public class Config { public static final String AOTHORITY = "com.bvin.study.observer.MyProvide"; public static final String TABLE_NAME = "person"; public static final Uri CONTENT_URI = Uri.parse("content://"+AOTHORITY+"/"+TABLE_NAME); public static final int DATA_VERSION =1; }
AndroidManifest.xml
<?xml version="1.0" encoding="utf-8"?> <manifest xmlns:android="http://schemas.android.com/apk/res/android" package="com.bvin.study.observer" android:versionCode="1" android:versionName="1.0" > <uses-sdk android:minSdkVersion="8" /> <application android:icon="@drawable/ic_launcher" android:label="@string/app_name" > <activity android:name=".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=".MyProvide" android:authorities="com.bvin.study.observer.MyProvide"/> </application> </manifest>
---恢复内容结束---