Android 4 学习(16):Database and Content Providers

参考《Professional Android 4 Development

Database and Content Providers

Android Database简介

Android使用SQLite数据库和ContentProvider来实现结构化数据的读写。在Android中,SQLite是以lib的形式存在的,每个应用程序含有自己的SQLite lib,减少了数据库层的耦合,并且提升了安全性。SQLite的数据文件默认存储在这个目录:

/data/data/<package_name>/databases

Content Provider使用URI的方式提供了一个统一的数据访问接口,Schemacontent://,与SQLite不同,Content Provider可以跨应用程序使用,可以通过发布自己的Content Provider供他人使用。

 

Content Values和Cursors

Content Values用于插入数据,Cursor则是返回的查询结果。Content Values对应数据中的一行数据。Cursor类提供了移动游标的方法,常用的有这些:

 

  • moveToFirst
  • moveToNext
  • moveToPrevious
  • getCount
  • getColumnIndexOrThrow
  • getColumnName
  • getColumnNames
  • moveToPosition
  • getPosition

SQLiteOpenHelper

SQLiteOpenHelper是一个抽象类,使用它可以更方便地进行操作数据库。SQLiteOpenHelper会缓存数据库实例,提升访问效率,也正是因为这种缓存策略,用户只有在不再访问数据库的时候才需要关闭数据库连接。下面是一个SQLiteOpenHelper的实现类例子:

 

private static class HoardDBOpenHelper extends SQLiteOpenHelper {
  private static final String DATABASE_NAME = “myDatabase.db";
  private static final String DATABASE_TABLE = “GoldHoards";
  private static final int DATABASE_VERSION = 1;
  // SQL Statement to create a new database.
  private static final String DATABASE_CREATE = “create table “ + DATABASE_TABLE + “ (“ + KEY_ID +
      “ integer primary key autoincrement, “ +
      KEY_GOLD_HOARD_NAME_COLUMN + “ text not null, “ +
      KEY_GOLD_HOARDED_COLUMN + “ float, “ +
      KEY_GOLD_HOARD_ACCESSIBLE_COLUMN + “ integer);";
  public HoardDBOpenHelper(Context context, String name, CursorFactory factory, int version) {
    super(context, name, factory, version);
  }
  // Called when no database exists in disk and the helper class needs to create a new one.
  @Override
  public void onCreate(SQLiteDatabase db) {
    db.execSQL(DATABASE_CREATE);
  }
  // Called when there is a database version mismatch meaning that the version of the database on disk needs to be upgraded to
  // the current version.
  @Override
  public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
    // Log the version upgrade.
    Log.w(“TaskDBAdapter", “Upgrading from version “ + oldVersion + “ to “ + newVersion + “, which will destroy all old data");
    // Upgrade the existing database to conform to the new version. Multiple previous versions can be handled by comparing oldVersion and newVersion values.
    // The simplest case is to drop the old table and create a new one.
    db.execSQL(“DROP TABLE IF IT EXISTS “ + DATABASE_TABLE);
    // Create a new one.
    onCreate(db);
  }
}

 

调用SQLiteOpenHelpergetWritableDatabase或者getReadableDatabase方法可以获得一个数据库实例,若不存在数据库实例,SQLiteOpenHelper的内部实现是使用onCreate()方法来创建一个数据库实例并返回。

 

使用其他方式创建或打开SQLite数据库

SQLiteDatabase db = context.openOrCreateDatabase(DATABASE_NAME, Context.MODE_PRIVATE, null);

使用上面的代码可以获得数据库,onCreateonUpdate方法中的逻辑需要手动来完成。

 

Android数据库设计需要注意的事情

1. 文件不要直接存在数据库中。更好的方法是将文件路径放到数据库中。

2. 表中加入auto-incrementid字段。

 

数据库查询

使用query方法,传入下面这些参数,即可实现数据库查询:

  1. Boolean类型的参数,用于指示是否包含重复数据。
  2. 要查询的数据库表名。
  3. Projection,指定要返回的列。
  4. Where,可以用?做占位符。
  5. Where参数。
  6. Group by。
  7. Having。
  8. 字符串,指定返回行的顺序。
  9. 字符串,用于指定返回的最大行数
// Specify the result column projection. Return the minimum set of columns required to satisfy your requirements.
String[] result_columns = new String[] {KEY_ID, KEY_GOLD_HOARD_ACCESSIBLE_COLUMN, KEY_GOLD_HOARDED_COLUMN };
// Specify the where clause that will limit our results.
String where = KEY_GOLD_HOARD_ACCESSIBLE_COLUMN + “=" + 1;
// Replace these with valid SQL statements as necessary.
String whereArgs[] = null;
String groupBy = null;
String having = null;
String order = null;
SQLiteDatabase db = hoardDBOpenHelper.getWritableDatabase();
Cursor cursor = db.query(HoardDBOpenHelper.DATABASE_TABLE, result_columns, where, whereArgs, groupBy, having, order);

Cursor中提取数据

Cursor中提取数据需要两步。首先使用前面介绍的moveTo<loction>等方法首先移动到某行,然后使用get<type>方法获取某列的值。使用getColumnIndexOrThrow或getColumnIndex方法可以通过列名获得该列在Cursor中的index。若找不到该列,getColumnIndex方法将返回-1,而getColumnIndexOrThrow将抛出异常。

int columnIndex = cursor.getColumnIndex(KEY_COLUMN_1_NAME);
if (columnIndex > -1) {
  String columnValue = cursor.getString(columnIndex);
  // Do something with the column value.
}
else {
  // Do something else if the column doesn’t exist.
}

下面是一个从Cursor中读取数据的完整示例:

 

float totalHoard = 0f;
float averageHoard = 0f;
// Find the index to the column(s) being used.
int GOLD_HOARDED_COLUMN_INDEX = cursor.getColumnIndexOrThrow(KEY_GOLD_HOARDED_COLUMN);
// Iterate over the cursors rows.
// The Cursor is initialized at before first, so we can check only if there is a “next" row available. If the
// result Cursor is empty this will return false.
while (cursor.moveToNext()) {
  float hoard = cursor.getFloat(GOLD_HOARDED_COLUMN_INDEX);
  totalHoard += hoard;
}
// Calculate an average -- checking for divide by zero errors.
float cursorCount = cursor.getCount();
averageHoard = cursorCount > 0 ? (totalHoard / cursorCount) : Float.NaN;
// Close the Cursor when you’ve finished with it.
cursor.close();

 

增、改、删

插入数据:

// Create a new row of values to insert.
ContentValues newValues = new ContentValues();
// Assign values for each row.
newValues.put(KEY_GOLD_HOARD_NAME_COLUMN, hoardName);
newValues.put(KEY_GOLD_HOARDED_COLUMN, hoardValue);
newValues.put(KEY_GOLD_HOARD_ACCESSIBLE_COLUMN, hoardAccessible);
// [ ... Repeat for each column / value pair ... ]
// Insert the row into your table
SQLiteDatabase db = hoardDBOpenHelper.getWritableDatabase();
db.insert(HoardDBOpenHelper.DATABASE_TABLE, null, newValues);

更新数据:

// Create the updated row Content Values.
ContentValues updatedValues = new ContentValues();
// Assign values for each row.
updatedValues.put(KEY_GOLD_HOARDED_COLUMN, newHoardValue);
// [ ... Repeat for each column to update ... ]
// Specify a where clause the defines which rows should be updated. Specify where arguments as necessary.
String where = KEY_ID + “=" + hoardId;
String whereArgs[] = null;
// Update the row with the specified index with the new values.
SQLiteDatabase db = hoardDBOpenHelper.getWritableDatabase();
db.update(HoardDBOpenHelper.DATABASE_TABLE, updatedValues, where, whereArgs);

删除数据:

// Specify a where clause that determines which row(s) to delete.
// Specify where arguments as necessary.
String where = KEY_GOLD_HOARDED_COLUMN + “=" + 0;
String whereArgs[] = null;
// Delete the rows that match the where clause.
SQLiteDatabase db = hoardDBOpenHelper.getWritableDatabase();
db.delete(HoardDBOpenHelper.DATABASE_TABLE, where, whereArgs);

创建Content Providers

Content Provider是一个接口,Content Resolver可以通过这个接口读取数据。通过继承ContentProvider类,即可创建一个Content Provider

 

public class MyContentProvider extends ContentProvider

 

注册Content Provider

Content Provider加到Manifest后,Content Resolver才能找到并使用。每个Content Provider标签中都有authorities属性,这个属性标识Content Provider

 

<provider android:name=".MyContentProvider" android:authorities="com.paad.skeletondatabaseprovider" />

 

发布Content Provider

每个ContentProvider都应该还有一个静态字符串变量CONTENT_URI,这个变量由该ContentProviderauthoritiesdata path组成,如:

 

public static final Uri CONTENT_URI = Uri.parse(“content://com.paad.skeletondatabaseprovider/elements”);

 

在CONTENT_URI后面加上行号,即可获得某一行数据的URI,如:

 

content://com.paad.skeletondatabaseprovider/elements/5

在实际使用中,最好将这两种URI都发布出来,并配合UriMatcher使用:

 

// Create the constants used to differentiate between the different URI requests.
private static final int ALLROWS = 1;
private static final int SINGLE_ROW = 2;
private static final UriMatcher uriMatcher;
// Populate the UriMatcher object, where a URI ending in ‘elements’ will correspond to a request for all items,
// and ‘elements/[rowID]’ represents a single row.
static {
  uriMatcher = new UriMatcher(UriMatcher.NO_MATCH);
  uriMatcher.addURI(“com.paad.skeletondatabaseprovider”, “elements”, ALLROWS);
  uriMatcher.addURI(“com.paad.skeletondatabaseprovider”, “elements/#”, SINGLE_ROW);
}

 

UriMatcher经常和SQLiteQueryBuilder配合使用:

SQLiteQueryBuilder queryBuilder = new SQLiteQueryBuilder();
// If this is a row query, limit the result set to the passed in row.
switch (uriMatcher.match(uri)) {
  case SINGLE_ROW :
    String rowID = uri.getPathSegments().get(1);
    queryBuilder.appendWhere(KEY_ID + “=” + rowID);
  default: break;
}

创建Content Provider的数据库

使用SQLiteOpenHelper实现ContentProvideronCreate方法:

 

private MySQLiteOpenHelper myOpenHelper;
@Override
public boolean onCreate() {
  // Construct the underlying database.
  // Defer opening the database until you need to perform a query or transaction.
  myOpenHelper = new MySQLiteOpenHelper(getContext(), MySQLiteOpenHelper.DATABASE_NAME, null,  MySQLiteOpenHelper.DATABASE_VERSION);
  return true;
}

 

实现Content Provider查询

@Override
public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder) {
  // Open the database.
  SQLiteDatabase db;
  try {
    db = myOpenHelper.getWritableDatabase();
  } catch (SQLiteException ex) {
    db = myOpenHelper.getReadableDatabase();
  }
  // Replace these with valid SQL statements if necessary.
  String groupBy = null;
  String having = null;
  // Use an SQLite Query Builder to simplify constructing the database query.
  SQLiteQueryBuilder queryBuilder = new SQLiteQueryBuilder();
  // If this is a row query, limit the result set to the passed in row.
  switch (uriMatcher.match(uri)) {
    case SINGLE_ROW :
      String rowID = uri.getPathSegments().get(1);
      queryBuilder.appendWhere(KEY_ID + “=” + rowID);
    default: 
      break;   }   // Specify the table on which to perform the query. This can be a specific table or a join as required.   queryBuilder.setTables(MySQLiteOpenHelper.DATABASE_TABLE);   // Execute the query.   Cursor cursor = queryBuilder.query(db, projection, selection, selectionArgs, groupBy, having, sortOrder);   // Return the result Cursor.   return cursor; }

实现query方法之后,还需要提供返回数据的MIME信息。因此要重写getType方法并返回一个字符串来描述你的数据类型。数据类型有两种,一种是单条数据,另一种是集合数据:

 

  • 单条数据:vnd.android.cursor.item/vnd.<companyname>.<contenttype>

 

  • 所有数据:vnd.android.cursor.dir/vnd.<companyname>.<contenttype>
@Override
public String getType(Uri uri) {
  // Return a string that identifies the MIME type for a Content Provider URI
  switch (uriMatcher.match(uri)) {
    case ALLROWS:
      return “vnd.android.cursor.dir/vnd.paad.elemental”;
    case SINGLE_ROW:
      return “vnd.android.cursor.item/vnd.paad.elemental”;
    default:
      throw new IllegalArgumentException(“Unsupported URI: “ + uri);
  }
}

Content Provider Transactions

在修改完数据后,需要调用Content ResolvernotifyChange方法,这样Content Observer即可获得数据修改通知。下面是一段比较全面的示例代码:

 

@Override
public int delete(Uri uri, String selection, String[] selectionArgs) {
  // Open a read / write database to support the transaction.
  SQLiteDatabase db = myOpenHelper.getWritableDatabase();
  // If this is a row URI, limit the deletion to the specified row.
  switch (uriMatcher.match(uri)) {
    case SINGLE_ROW :
      String rowID = uri.getPathSegments().get(1);
      selection = KEY_ID + “=” + rowID + (!TextUtils.isEmpty(selection) ?“ AND (“ + selection + ‘)’ : “”);
  default: break;
}
// To return the number of deleted items you must specify a where clause. To delete all rows and return a value pass in “1”.
if (selection == null)
  selection = “1”;
  // Perform the deletion.
  int deleteCount = db.delete(MySQLiteOpenHelper.DATABASE_TABLE, selection, selectionArgs);
  // Notify any observers of the change in the data set.
  getContext().getContentResolver().notifyChange(uri, null);
  // Return the number of deleted items.
  return deleteCount;
}
@Override
public Uri insert(Uri uri, ContentValues values) {
  // Open a read / write database to support the transaction.
  SQLiteDatabase db = myOpenHelper.getWritableDatabase();
  // To add empty rows to your database by passing in an empty
  // Content Values object you must use the null column hack parameter to specify the name of the column that can be set to null.
  String nullColumnHack = null;
  // Insert the values into the table
  long id = db.insert(MySQLiteOpenHelper.DATABASE_TABLE, nullColumnHack, values);
  // Construct and return the URI of the newly inserted row.
  if (id > -1) {
    // Construct and return the URI of the newly inserted row.
    Uri insertedId = ContentUris.withAppendedId(CONTENT_URI, id);
    // Notify any observers of the change in the data set.
    getContext().getContentResolver().notifyChange(insertedId, null);
    return insertedId;
  }else
    return null;
}
@Override
public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs) {
  // Open a read / write database to support the transaction.
  SQLiteDatabase db = myOpenHelper.getWritableDatabase();
  // If this is a row URI, limit the deletion to the specified row.
  switch (uriMatcher.match(uri)) {
    case SINGLE_ROW :
      String rowID = uri.getPathSegments().get(1);
      selection = KEY_ID + “=” + rowID + (!TextUtils.isEmpty(selection) ?“ AND (“ + selection + ‘)’ : “”);
    default: break;
  }
  // Perform the update.
  int updateCount = db.update(MySQLiteOpenHelper.DATABASE_TABLE, values, selection, selectionArgs);
  // Notify any observers of the change in the data set.
  getContext().getContentResolver().notifyChange(uri, null);
  return updateCount;
}

 

Content Provider中存储文件

文件存储的最佳设计模式是将文件路径存储在数据库中,使用Content Provider的接口方法读写。文件路径应该存放在数据库表中的_data列中,并重写Content ProvideropenFile方法,返回ParcelFileDescriptor:

@Override
public ParcelFileDescriptor openFile(Uri uri, String mode) throws FileNotFoundException {
  // Find the row ID and use it as a filename.
  String rowID = uri.getPathSegments().get(1);
  // Create a file object in the application’s external files directory.
  String picsDir = Environment.DIRECTORY_PICTURES;
  File file = new File(getContext().getExternalFilesDir(picsDir), rowID);
  // If the file doesn’t exist, create it now.
  if (!file.exists()) {
    try {
      file.createNewFile();
    } catch (IOException e) {
      Log.d(TAG, “File creation failed: “ + e.getMessage());
    }
  }
  // Translate the mode parameter to the corresponding Parcel File Descriptor open mode.
  int fileMode = 0;
  if (mode.contains(“w”))
    fileMode |= ParcelFileDescriptor.MODE_WRITE_ONLY;
  if (mode.contains(“r”))
    fileMode |= ParcelFileDescriptor.MODE_READ_ONLY;
  if (mode.contains(“+”))
    fileMode |= ParcelFileDescriptor.MODE_APPEND;
  // Return a Parcel File Descriptor that represents the file.
  return ParcelFileDescriptor.open(file, fileMode);
}

Content ProviderSkeleton示例

import android.content.ContentProvider;
import android.content.ContentUris;
import android.content.ContentValues;
import android.content.Context;
import android.content.UriMatcher;
import android.database.Cursor;
import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteDatabase.CursorFactory;
import android.database.sqlite.SQLiteOpenHelper;
import android.database.sqlite.SQLiteQueryBuilder;
import android.net.Uri;
import android.text.TextUtils;
import android.util.Log;
public class MyContentProvider extends ContentProvider {
  public static final Uri CONTENT_URI = Uri.parse(“content://com.paad.skeletondatabaseprovider/elements”);
  // Create the constants used to differentiate between the different URI requests.
  private static final int ALLROWS = 1;
  private static final int SINGLE_ROW = 2;
  private static final UriMatcher uriMatcher;
  // Populate the UriMatcher object, where a URI ending in ‘elements’ will correspond to a request for all
  // items, and ‘elements/[rowID]’ represents a single row.
  static {
    uriMatcher = new UriMatcher(UriMatcher.NO_MATCH);
    uriMatcher.addURI(“com.paad.skeletondatabaseprovider”, “elements”, ALLROWS);
    uriMatcher.addURI(“com.paad.skeletondatabaseprovider”, “elements/#”, SINGLE_ROW);
  }
  // The index (key) column name for use in where clauses.
  public static final String KEY_ID = “_id”;
  // The name and column index of each column in your database.
  // These should be descriptive.
  public static final String KEY_COLUMN_1_NAME = “KEY_COLUMN_1_NAME”;
  // TODO: Create public field for each column in your table.
  // SQLite Open Helper variable
  private MySQLiteOpenHelper myOpenHelper;
  @Override
  public boolean onCreate() {
    // Construct the underlying database.
    // Defer opening the database until you need to perform a query or transaction.
    myOpenHelper = new MySQLiteOpenHelper(getContext(), MySQLiteOpenHelper.DATABASE_NAME, null, MySQLiteOpenHelper.DATABASE_VERSION);
    return true;
  }
  @Override
  public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder) {
  // Open the database.
  SQLiteDatabase db = myOpenHelper.getWritableDatabase();
  // Replace these with valid SQL statements if necessary.
  String groupBy = null;
  String having = null;
  SQLiteQueryBuilder queryBuilder = new SQLiteQueryBuilder();
  queryBuilder.setTables(MySQLiteOpenHelper.DATABASE_TABLE);
  // If this is a row query, limit the result set to the passed in row.
  switch (uriMatcher.match(uri)) {
    case SINGLE_ROW :
      String rowID = uri.getPathSegments().get(1);
      queryBuilder.appendWhere(KEY_ID + “=” + rowID);
    default: break;
  }
  Cursor cursor = queryBuilder.query(db, projection, selection, selectionArgs, groupBy, having, sortOrder);
  return cursor;
}
@Override
public int delete(Uri uri, String selection, String[] selectionArgs){
  // Open a read / write database to support the transaction.
  SQLiteDatabase db = myOpenHelper.getWritableDatabase();
  // If this is a row URI, limit the deletion to the specified row.
  switch (uriMatcher.match(uri)) {
    case SINGLE_ROW :
      String rowID = uri.getPathSegments().get(1);
      selection = KEY_ID + “=” + rowID + (!TextUtils.isEmpty(selection) ?“ AND (“ + selection + ‘)’ : “”);
    default: break;
  }
  // To return the number of deleted items, you must specify a where clause. To delete all rows and return a value, pass in “1”.
  if (selection == null)  selection = “1”;
  // Execute the deletion.
  int deleteCount = db.delete(MySQLiteOpenHelper.DATABASE_TABLE, selection, selectionArgs);
  // Notify any observers of the change in the data set.
  getContext().getContentResolver().notifyChange(uri, null);
  return deleteCount;
}
@Override
public Uri insert(Uri uri, ContentValues values) {
  // Open a read / write database to support the transaction.
  SQLiteDatabase db = myOpenHelper.getWritableDatabase();
  // To add empty rows to your database by passing in an empty
  // Content Values object, you must use the null column hack parameter to specify the name of the column that can be set to null.
  String nullColumnHack = null;
  // Insert the values into the table
  long id = db.insert(MySQLiteOpenHelper.DATABASE_TABLE, nullColumnHack, values);
  if (id > -1) {
  // Construct and return the URI of the newly inserted row.
  Uri insertedId = ContentUris.withAppendedId(CONTENT_URI, id);
  // Notify any observers of the change in the data set.
  getContext().getContentResolver().notifyChange(insertedId, null);
  return insertedId;
}else
  return null;
}
@Override
public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs) {
  // Open a read / write database to support the transaction.
  SQLiteDatabase db = myOpenHelper.getWritableDatabase();
  // If this is a row URI, limit the deletion to the specified row.
  switch (uriMatcher.match(uri)) {
    case SINGLE_ROW :
      String rowID = uri.getPathSegments().get(1);
      selection = KEY_ID + “=” + rowID + (!TextUtils.isEmpty(selection) ?“ AND (“ + selection + ‘)’ : “”);
    default: break;
  }
  // Perform the update.
  int updateCount = db.update(MySQLiteOpenHelper.DATABASE_TABLE, values, selection, selectionArgs);
  // Notify any observers of the change in the data set.
  getContext().getContentResolver().notifyChange(uri, null);
  return updateCount;
}
@Override
public String getType(Uri uri) {
  // Return a string that identifies the MIME type for a Content Provider URI
  switch (uriMatcher.match(uri)) {
    case ALLROWS:
      return “vnd.android.cursor.dir/vnd.paad.elemental”;
    case SINGLE_ROW:
      return “vnd.android.cursor.item/vnd.paad.elemental”;
    default:
      throw new IllegalArgumentException(“Unsupported URI: “ + uri);
  }
}
private static class MySQLiteOpenHelper extends SQLiteOpenHelper {
  // [ ... SQLite Open Helper Implementation ... ]
}
}

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

原文地址:https://www.cnblogs.com/jubincn/p/3520735.html