zoukankan      html  css  js  c++  java
  • 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 ... ]
    }
    }

     

     

     

     

     

     

     

     

     

     

     

     

     

     

     

  • 相关阅读:
    Docker 设置阿里云镜像
    Linux 安装Navicat Premium 15
    Ubuntu常用工具安装
    Docker安装MongoDB、MySQL、Jenkins、Gitlab、Nginx
    Ubuntu18.04修改apt-get源
    Spring定时任务
    Quartz学习总结
    cron表达式
    将VirtualBox里安装的虚拟机在后台运行方法(在状态栏隐藏窗口)
    npm小结
  • 原文地址:https://www.cnblogs.com/jubincn/p/3520735.html
Copyright © 2011-2022 走看看