安卓高效开发:数据库基本
2013-11-28 12:11
351 查看
应用开发最怕的就是重复造轮子,即使这个轮子是自己造的,很多人批评码农工作就是Ctrl+C,可最近发现自己连这个组合键都懒得用了,复制过来的代码在新项目中还需要调整,费时费力,干脆把写过的东西整理下,形成类似框架的东西,以后开发岂不是so easy,先从数据库下手,这个用的最多,经测试基本可靠且易扩展。略喜。
数据库基本操作包括增删改查及更新,本文中使用ContentProvider(CP)对其进行了封装,不得不说CP是个好东西,提供统一接口及URI检查,还可以进行异常处理和自定义过程,虽然归根揭底还需要数据库句柄的操作,但其提供的统一标识还是有利与进程间数据共享的,使用CP后的数据库包结构如下图:
![](http://img.blog.csdn.net/20131128120352500)
数据库包中含有三个文件
Contract.java 契约文件,类似于C语言里的头文件,客户只需要拿到这个文件,就知道怎么做了,我们需要把那些对外暴露的内容加到该文件中,契约这名字是我根据NotePad里注释直译的,感觉作用相像。
CustomDataBaseHelper.java, 帮助类,进行过数据库开发的朋友对这个帮助类多很熟悉,负责数据库建立、打开、更新等操作。实际的建表过程就是在数据库建立时进行的。
CustomProvider,这就是CP的核心文件了,包括该CP的具体增删改查过程,URI的判断及异常处理都在这里,废话不多说了,上代码,看注释。
1 Contract.java
2 CustomDataBaseHelper.java
3 CustomProvider.java
在CP的增删改过程中,我们能看到notifyChange这个函数被调用,这个对UI设计比较关键,很多时候纠结于怎样让UI和数据库同步,也就是在适配器中Cursor的内容改变时即时调整UI,notifyChange可以让那些对数据库感兴趣的观察者么做一些实用的操作,类似于适配器的notifyDataSetChanged操作。
为了验证有效性,需要一个测试文件,测试文件对该数据库包的基本操作进行测试,测试结果真实有效,没有进行异常及压力测试,有兴趣的朋友可自行验证修改,测试文件代码如下。
TestActivity.java
测试界面及数据库内容如下:
![](http://img.blog.csdn.net/20131128120412062)
![](http://img.blog.csdn.net/20131128120714125)
不要忘了在AndroidManifest.xml中注册provider:
1) Contract.java 仿照EmployeeTable增加关于新表的内部类;添加新的MIME类型及对应识别码。
2) CustomDataBaseHelper.java 仿照CMD_CREATE_TABLE新添建立新表的sql语句,并在onCreate时使用。
3) CustomProvider.java在检测MIME类型的switch-case语句中加入新的类型标识,具体过程不变。
多快好省,一个个新表诞生了~
小结
本文主要是整理了以前用在项目中使用的数据库基本结构,通过过程细分及重构,方便以后扩展及二次开发,由于时间及水平问题,该结构可能还存在问题,以后会逐步优化,有需要的同学在使用时也可自行debug,友情分享,无技术支持~
数据库基本操作包括增删改查及更新,本文中使用ContentProvider(CP)对其进行了封装,不得不说CP是个好东西,提供统一接口及URI检查,还可以进行异常处理和自定义过程,虽然归根揭底还需要数据库句柄的操作,但其提供的统一标识还是有利与进程间数据共享的,使用CP后的数据库包结构如下图:
数据库包中含有三个文件
Contract.java 契约文件,类似于C语言里的头文件,客户只需要拿到这个文件,就知道怎么做了,我们需要把那些对外暴露的内容加到该文件中,契约这名字是我根据NotePad里注释直译的,感觉作用相像。
CustomDataBaseHelper.java, 帮助类,进行过数据库开发的朋友对这个帮助类多很熟悉,负责数据库建立、打开、更新等操作。实际的建表过程就是在数据库建立时进行的。
CustomProvider,这就是CP的核心文件了,包括该CP的具体增删改查过程,URI的判断及异常处理都在这里,废话不多说了,上代码,看注释。
1 Contract.java
package com.klpchan.provider; import android.net.Uri; import android.provider.BaseColumns; /** * 契约文件,用于向客户展现内容提供器(CP)的具体项,类似于C语言中的h文件。 * 好的契约文件能够向客户展示该CP的URIs、数据库名、表名、列名等,具体的数据 * 库操作不在该文件中。 * 一般情况,客户代码只需使用这个文件的内容就能够达到目的。 */ public final class Contract { /** * 每个CP在向系统注册,都有个身份ID,这里用AUTHORITY表示。 * 在AndroidManifest.xml中注册CP时需使用该常量。本例设置为包名。 */ public static String AUTHORITY = "com.klpchan.androidklpdatabase"; /** * 协议类型,类似于网络中的"http://"格式, * Android文件系统定位某个文件位置类似于: content://contacts/people/1 * 网络上定义某个文件位置也采用类似格式 : http://www.baidu.com/mp3/xxx.mp3 * 都是使用协议类型+域名+文件位置的方式。 */ private static final String SCHEME = "content://"; /** * 默认排序,以join_time项升序排列, * 该默认排序表示的客户搜索的游标内容排序方式,实际在 * 数据库表中的数据序列不变,仍是按照创建先后顺序排列。 */ public static final String DEFAULT_SORT_ORDER = "join_time ASC"; /** * 自定义数据库的表及单项的MIME类型 * 通过该MIME类型可以判断需要CP操作的是对象是表整体还是单项, * 同时向系统表明该CP可以提供的数据MIME类型。 * 这个结合Intent-filter使用较为常见。 */ public static final String RAW_CONTACTS_TYPE = "vnd.android.cursor.dir/vnd.klp.db.raw_contacts"; public static final String RAW_CONTACTS_ITEM_TYPE = "vnd.android.cursor.item/vnd.klp.db.raw_contacts"; /** * 上述MIME类型的匹配参数,用于判断需要CP处理的数据MIME类型,多结合Intent-filter使用。 */ //需处理的URI是 1:表URI 2:具体数据项 public static final int RAW_CONTACTS_DB = 1; public static final int RAW_CONTACTS_DB_ID = 2; /** * 数据库中包含很多表,将一个表的所有信息以一个内部类表示出来 * 包括表名、URI、列名及列相对位置。 * 列相对位置代码中通过cursor可以找到,考虑易读性,可在该内部类中定义。 * 每个内部类表示一张表。 */ public static final class EmployeeTable implements BaseColumns { //表名 public static final String TABLE_NAME = "employeeInfo"; //表URI public static final Uri CONTENT_URI = Uri.parse(SCHEME + AUTHORITY + "/" + TABLE_NAME); //表中列相对位置,从0开始 public static final int POS_COL_ID = 0; public static final int POS_COL_NAME = 1; public static final int POS_COL_JOB_TITLE = 2; public static final int POS_COL_PRE_COMPANY = 3; public static final int POS_COL_CREATED_DATE = 4; public static final int POS_COL_WAGE_YEAR = 5; //列名 public static final String KEY_NAME = "name"; public static final String KEY_JOB_TITLE = "job_title"; public static final String KEY_PRE_COMPANY = "pre_company"; public static final String KEY_CREATED_DATE = "join_time"; public static final String KEY_WAGE_YEAR = "wage_year"; } }
2 CustomDataBaseHelper.java
package com.klpchan.provider; import android.content.Context; import android.database.sqlite.SQLiteDatabase; import android.database.sqlite.SQLiteOpenHelper; import android.util.Log; import com.klpchan.provider.Contract.EmployeeTable; /** * 数据库操作的帮助类,负责某一具体数据库的创建、打开、升级等操作。 */ public class CustomDataBaseHelper extends SQLiteOpenHelper { protected static final String TAG = "DB::CustomDataBaseHelper"; //帮助类负责的数据库名称 public static final String DATABASE_NAME = "HR.db"; //数据库当前版本 protected static final int DATABASE_VERSION = 1; //单例静态帮助对象 static CustomDataBaseHelper mInstance = null; //建表命令,在数据库被创建时执行 private static final String CMD_CREATE_TABLE = "CREATE TABLE " + EmployeeTable.TABLE_NAME + " (" + EmployeeTable._ID + " INTEGER PRIMARY KEY AUTOINCREMENT , " + EmployeeTable.KEY_NAME + " TEXT , " + EmployeeTable.KEY_JOB_TITLE + " TEXT , " + EmployeeTable.KEY_PRE_COMPANY + " TEXT , " + EmployeeTable.KEY_CREATED_DATE + " TEXT , " + EmployeeTable.KEY_WAGE_YEAR + " TEXT " + " ) "; //某个帮助类负责某个特定的数据库 private CustomDataBaseHelper(Context context) { super(context, DATABASE_NAME, null, DATABASE_VERSION); } public static CustomDataBaseHelper getInstance(Context context) { if (mInstance == null) mInstance = new CustomDataBaseHelper(context); return mInstance; } /** * 当帮助类执行getWritableDatabase时 * 如果数据库不存在,则调用该方法创建数据库。 * 如存在,则调用onOpen方法打开数据库。 */ @Override public void onCreate(SQLiteDatabase db) { Log.i(TAG, "create db"); db.execSQL(CMD_CREATE_TABLE); } @Override public void onOpen(SQLiteDatabase db) { super.onOpen(db); Log.i(TAG, "open"); db.rawQuery("PRAGMA synchronous = 1", null); db.rawQuery("PRAGMA journal_mode = WAL", null); } @Override public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) { Log.i(TAG, "ContactsDataBaseHelper onUpgrade - oldVersion : " + oldVersion + ", newVersion : " + newVersion); //不删除表的内容,仅仅添加一列内容 /* db.execSQL("ALTER TABLE " + EmployeeTable.TABLE_NAME + " ADD COLUMN " + EmployeeTable.KEY_ADDED_COLOUM + " INTEGER");*/ //删除旧表重建 db.execSQL("DROP TABLE IF EXISTS " + EmployeeTable.TABLE_NAME); onCreate(db); } }
3 CustomProvider.java
package com.klpchan.provider; import java.util.ArrayList; import android.content.ContentProvider; import android.content.ContentProviderOperation; import android.content.ContentProviderResult; import android.content.ContentUris; import android.content.ContentValues; import android.content.OperationApplicationException; import android.content.UriMatcher; import android.database.Cursor; import android.database.sqlite.SQLiteDatabase; import android.database.sqlite.SQLiteQueryBuilder; import android.net.Uri; import android.provider.BaseColumns; import android.text.TextUtils; import android.util.Log; import com.klpchan.provider.Contract.EmployeeTable; public class CustomProvider extends ContentProvider { protected static final String TAG = "DB::CustomProvider"; //数据库帮助类及执行具体增删改查的数据库句柄 private static CustomDataBaseHelper mDBOpenHelper; private SQLiteDatabase mDataBase; /** * 一个URI的匹配类,引入了两种URI模式,如果需CP处理的URI如下: * content://com.klpchan.androidklpdatabase/employeeInfo : 对应表操作 * content://com.klpchan.androidklpdatabase/employeeInfo/# : 对于具体数据项操作 * 其余情况抛出异常,不予操作。 */ private static final UriMatcher uriMatcher; static { uriMatcher = new UriMatcher(UriMatcher.NO_MATCH); uriMatcher.addURI(Contract.AUTHORITY, EmployeeTable.TABLE_NAME, Contract.RAW_CONTACTS_DB); uriMatcher.addURI(Contract.AUTHORITY, EmployeeTable.TABLE_NAME + "/#", Contract.RAW_CONTACTS_DB_ID); } public ContentProviderResult[] applyBatch(ArrayList<ContentProviderOperation> operations) throws OperationApplicationException { Log.d(TAG, "applyBatch start"); SQLiteDatabase db = mDBOpenHelper.getWritableDatabase(); db.beginTransaction(); try { ContentProviderResult[] results = super.applyBatch(operations); db.setTransactionSuccessful(); return results; } finally { db.endTransaction(); } } public static void closeDataBase() { if (mDBOpenHelper != null) mDBOpenHelper.close(); } /** * 由于在Android.Manifest中已注册provider,程序在第一次执行时通过installProvider * 调用该方法,provider安装成功后一直存在直到程序卸载。 */ @Override public boolean onCreate() { Log.i(TAG, "Create provider"); mDBOpenHelper = CustomDataBaseHelper.getInstance(getContext()); if (mDBOpenHelper != null); mDataBase = mDBOpenHelper.getWritableDatabase(); return true; } /** * 判断需处理URI的MIME类型,多结合Intent-filter使用。 */ @Override public String getType(Uri uri) { int match = uriMatcher.match(uri); switch (match) { case Contract.RAW_CONTACTS_DB: return Contract.RAW_CONTACTS_TYPE; case Contract.RAW_CONTACTS_DB_ID: return Contract.RAW_CONTACTS_ITEM_TYPE; default: throw new IllegalArgumentException("Unknown URI: " + uri); } } //CP的插入操作,会检查URI是否为表格式并加入数据建立时间 @Override public Uri insert(Uri uri, ContentValues values) { if (values == null) return null; if (uriMatcher.match(uri) != Contract.RAW_CONTACTS_DB ) { throw new IllegalArgumentException("Unknown URI " + uri); } String table = (String) uri.getPathSegments().get(0); if (uriMatcher.match(uri) == Contract.RAW_CONTACTS_DB) { if (!values.containsKey(EmployeeTable.KEY_CREATED_DATE)) values.put(EmployeeTable.KEY_CREATED_DATE, Long.valueOf(System.currentTimeMillis())); } //在向数据库插入数据后,通知数据改变可以让依附于该数据库的观察者如Cursor重新查询,即时更新view long rowId = mDataBase.insert(table, null, values); if (rowId > 0) { Uri genuri = ContentUris.withAppendedId(uri, rowId); getContext().getContentResolver().notifyChange(genuri, null); return genuri; } return null; } //CP的删除操作,只删除特定数据项。 @Override public int delete(Uri uri, String selection, String[] selectionArgs) { String table = (String) uri.getPathSegments().get(0); int match = uriMatcher.match(uri); switch (match) { case Contract.RAW_CONTACTS_DB: break; case Contract.RAW_CONTACTS_DB_ID: selection = BaseColumns._ID + "=" + (String) uri.getPathSegments().get(1) + (!TextUtils.isEmpty(selection) ? " AND (" + selection + ')' : ""); break; default: throw new IllegalArgumentException("Unknown URI: " + uri); } int count = mDataBase.delete(table, selection, selectionArgs); getContext().getContentResolver().notifyChange(uri, null); return count; } //CP更新操作。注意在每次数据更新时NotifyChange,以供数据观察者进行处理 @Override public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs) { String table = (String) uri.getPathSegments().get(0); int match = uriMatcher.match(uri); switch (match) { case Contract.RAW_CONTACTS_DB: break; case Contract.RAW_CONTACTS_DB_ID: selection = BaseColumns._ID + "=" + (String) uri.getPathSegments().get(1) + (!TextUtils.isEmpty(selection) ? " AND (" + selection + ')' : ""); break; default: throw new IllegalArgumentException("Unknown URI: " + uri); } int count = mDataBase.update(table, values, selection, selectionArgs); if (count > 0) { getContext().getContentResolver().notifyChange(uri, null); } return count; } //CP查询,默认查询方式为建立时间升序。 @Override public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder) { SQLiteQueryBuilder qb = new SQLiteQueryBuilder(); String table = (String) uri.getPathSegments().get(0); qb.setTables(table); int match = uriMatcher.match(uri); switch (match) { case Contract.RAW_CONTACTS_DB: break; case Contract.RAW_CONTACTS_DB_ID: qb.appendWhere(BaseColumns._ID + "=" + uri.getPathSegments().get(1)); break; default: throw new IllegalArgumentException("Unknown URI: " + uri); } String orderBy = null; if (match == Contract.RAW_CONTACTS_DB) { if (TextUtils.isEmpty(sortOrder) && table.equals(EmployeeTable.TABLE_NAME)) { orderBy = Contract.DEFAULT_SORT_ORDER; } else { orderBy = sortOrder; } } else { orderBy = sortOrder; } Cursor c = qb.query(mDataBase, projection, selection, selectionArgs, null, null, orderBy); c.setNotificationUri(getContext().getContentResolver(), uri); return c; } }
在CP的增删改过程中,我们能看到notifyChange这个函数被调用,这个对UI设计比较关键,很多时候纠结于怎样让UI和数据库同步,也就是在适配器中Cursor的内容改变时即时调整UI,notifyChange可以让那些对数据库感兴趣的观察者么做一些实用的操作,类似于适配器的notifyDataSetChanged操作。
为了验证有效性,需要一个测试文件,测试文件对该数据库包的基本操作进行测试,测试结果真实有效,没有进行异常及压力测试,有兴趣的朋友可自行验证修改,测试文件代码如下。
TestActivity.java
package com.klpchan.androidklpdatabase; import java.util.ArrayList; import android.app.Activity; import android.content.ContentResolver; import android.content.ContentValues; import android.database.ContentObserver; import android.database.Cursor; import android.database.CursorWrapper; import android.os.Bundle; import android.os.Handler; import android.os.Message; import android.util.Log; import android.view.View; import android.view.View.OnClickListener; import android.widget.Button; import android.widget.ListView; import android.widget.SimpleCursorAdapter; import com.klpchan.androidklpdatabase.R; import com.klpchan.provider.Contract.EmployeeTable; public class TestActivity extends Activity { private ContentResolver resolver; private ListView listView; protected static final String TAG = "DB::Test"; private Button Button1; private Button Button2; private Button Button3; private Button Button4; private Button Button5; private Button Button6; @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.main); resolver = getContentResolver(); listView = (ListView) findViewById(R.id.listView); OnClickListener clickListener = new OnClickListener() { public void onClick(View v) { switch (v.getId()) { case R.id.button1: init(); break; case R.id.button2: query(); break; case R.id.button3: insert(); break; case R.id.button4: update(); break; case R.id.button5: delete(); break; case R.id.button6: deleteAll(); break; default: break; } } }; Button1 = (Button) this.findViewById(R.id.button1); Button1.setOnClickListener(clickListener); Button2 = (Button) this.findViewById(R.id.button2); Button2.setOnClickListener(clickListener); Button3 = (Button) this.findViewById(R.id.button3); Button3.setOnClickListener(clickListener); Button4 = (Button) this.findViewById(R.id.button4); Button4.setOnClickListener(clickListener); Button5 = (Button) this.findViewById(R.id.button5); Button5.setOnClickListener(clickListener); Button6 = (Button) this.findViewById(R.id.button6); Button6.setOnClickListener(clickListener); /* getContentResolver().registerContentObserver(EmployeeTable.CONTENT_URI, true, new MemoObserver(handler));*/ } public void init() { ArrayList<Memo> memos = new ArrayList<Memo>(); Memo memo1 = new Memo("Ella", "Engineer", "MS", 22); Memo memo2 = new Memo("Jenny","Engineer","PG",23); Memo memo3 = new Memo("Jessica","Tester","GMC",16); memos.add(memo1); memos.add(memo2); memos.add(memo3); for (Memo memo : memos) { resolver.insert(EmployeeTable.CONTENT_URI, loadCV(memo)); } } @SuppressWarnings("deprecation") public void query() { Cursor c = resolver.query(EmployeeTable.CONTENT_URI, null, null, null, null); printLog(c); CursorWrapper cursorWrapper = new CursorWrapper(c); SimpleCursorAdapter adapter = new SimpleCursorAdapter(this, android.R.layout.simple_list_item_2, cursorWrapper, new String[] { EmployeeTable.KEY_NAME, EmployeeTable.KEY_JOB_TITLE }, new int[] { android.R.id.text1, android.R.id.text2 } ); listView.setAdapter(adapter); startManagingCursor(cursorWrapper); } public void insert() { Memo memo = new Memo("Alina","Engineer","UN",18); resolver.insert(EmployeeTable.CONTENT_URI, loadCV(memo)); } public void update() { Memo memo = new Memo("Alina","Senior Engineer","UN",25); resolver.update(EmployeeTable.CONTENT_URI, loadCV(memo), (EmployeeTable.KEY_NAME + " = ?"), new String[] { memo.name }); } public void delete() { resolver.delete(EmployeeTable.CONTENT_URI, "name = 'Alina'", null); } public void deleteAll() { resolver.delete(EmployeeTable.CONTENT_URI, null, null); } public class Memo { public int _id; public String name; public String jobtitle; public String precompany; public long createdate; public double wage; public Memo(String name,String jobtitle,String precompany,double wage) { this.name = name; this.jobtitle = jobtitle; this.precompany = precompany; this.wage = wage; } } private ContentValues loadCV(Memo memo){ ContentValues cv = new ContentValues(); cv.put(EmployeeTable.KEY_NAME, memo.name); cv.put(EmployeeTable.KEY_JOB_TITLE, memo.jobtitle); cv.put(EmployeeTable.KEY_PRE_COMPANY, memo.precompany); cv.put(EmployeeTable.KEY_WAGE_YEAR, memo.wage); return cv; } public class MemoObserver extends ContentObserver { private Handler handler; public MemoObserver(Handler handler) { super(handler); this.handler = handler; } @Override public void onChange(boolean selfChange) { super.onChange(selfChange); Message msg = new Message(); handler.sendMessage(msg); } } void printLog(Cursor c){ if (null == c || c.getCount() <= 0) { Log.w(TAG, "printLog : query result is empty"); } for (int i = 0; i < c.getCount(); i++) { c.moveToPosition(i); Log.i(TAG, "get cursor item [" + i + "]:" + c.getString(EmployeeTable.POS_COL_NAME) + c.getString(EmployeeTable.POS_COL_JOB_TITLE) + c.getString(EmployeeTable.POS_COL_PRE_COMPANY) + c.getDouble(EmployeeTable.POS_COL_WAGE_YEAR)); } } }
测试界面及数据库内容如下:
不要忘了在AndroidManifest.xml中注册provider:
<provider android:name="com.klpchan.provider.CustomProvider" android:exported="false" android:authorities="com.klpchan.androidklpdatabase" /> </application>如果想要进行扩展,建立其它列内容的表时,只需修改以下部分
1) Contract.java 仿照EmployeeTable增加关于新表的内部类;添加新的MIME类型及对应识别码。
2) CustomDataBaseHelper.java 仿照CMD_CREATE_TABLE新添建立新表的sql语句,并在onCreate时使用。
3) CustomProvider.java在检测MIME类型的switch-case语句中加入新的类型标识,具体过程不变。
多快好省,一个个新表诞生了~
小结
本文主要是整理了以前用在项目中使用的数据库基本结构,通过过程细分及重构,方便以后扩展及二次开发,由于时间及水平问题,该结构可能还存在问题,以后会逐步优化,有需要的同学在使用时也可自行debug,友情分享,无技术支持~
相关文章推荐
- SQL Server Profiler使用方法
- MyBatis的动态SQL详解
- sqlserver中不同服务器的数据库数据同步存储过程
- MySQL中的空间扩展
- oracle 学习笔记之循环
- oracle常用经典SQL查询
- System.Data.OracleClient需要Oracle客户端软件8.1.7或更高版本 的解决办法 vs2010 链接oracle数据库
- mysql提示Fatal error: Can't open and lock privilege tables: Table 'mysql.host' doesn't exist解决方法
- 日期SQL 脚本
- mysql中使用正则表达式时的注意事项
- MariaDB 和 MySQL 比较
- Oracle数据类型
- Oracle中ROWNUM的使用技巧
- 统计类SQL(按时间)
- oracle的job定期执行写法
- mysql参数优化
- PostgreSQL数据库导出命令pg_dump详解
- 操作系统中数据文件与ORACLE数据库中查询的bytes大小不同的解析
- oracle BBED的安装
- mongodb 安装 启动