阅读 171

Android 通过ContentProvider数据库更新UI

这篇用到的知识点有几个: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, StringorderBy, String limit)

返回查询到的结果集

4.public int delete (String table, String whereClause, String[] whereArgs)

返回删除的哪行

而查询可以 用异步查询 AsyncQueryHandler ,其实这个类也可以执行增删查该,分别 override 每个方法来做出响应处理

(二)ContentProvider

这是 android 四大组件之一,不需要手动实例化,只要在 Androidmanifest.xml 里面把 ContentProvider 注册上去就行。

 <provider android:/>

复制代码

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:version >
 
    <uses-sdk android:minSdkVersion="8" />
 
    <application
        android:icon="@drawable/ic_launcher"
        android:label="@string/app_name" >
        <activity
            android:
            android:label="@string/app_name" >
            <intent-filter>
                <action android: />
 
                <category android: />
            </intent-filter>
        </activity>
        
        <provider android:/>
    </application>
 
</manifest>
复制代码
文章分类
Android
文章标签