android之數據庫和Content Provider(二)

建立Content Provider

Content Provider爲公佈數據提供了一個接口,別的APP使用Content Resolver來使用該接口所提供的數據。 java

做爲4大組件之一,建立一個新的Content Provider須要繼承一個抽象類ContentProvider: android

public class MyContentProvider extends ContentProvider 數據庫

 

就像以前描述的數據庫的Contract/Helper類,也一樣最好是去包含靜態數據庫常量---尤爲是列名等。 app

你須要重寫onCreate方法去初始化底層數據源,一樣還有重寫query,update,delete,insert和getType這些方法,使Content Resolver能夠與數據進行交互。 框架

 

註冊Content Provider

像Activity,Service,Content Provider,必須在Mainfest中註冊。使用一個provider標籤:包含一個name和authorities屬性。 ide

authorities屬性 去定義Provider的authority的基礎URI。Content Provider的authority表明一個數據庫。 ui

每一個Content Provider的authority必須是惟一的,因此定義基礎URI一般以包名做爲路徑。如:
com.<CompanyName>.provider.<ApplicationName>
this

例子: 
.net

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

公佈你的Content ProviderURI地址

每一個Content Provider應該使用公有靜態常量 CONTENT_URI ,使其更容易被訪問。 線程

如:

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

 

相似與上述這例子表示請求表中全部的記錄,而:

content://com.paad.skeletondatabaseprovider/elements/5
表示某行記錄。(接數字)

全部這些形式都支持你去訪問你的Provider。這麼作最簡單方式是去使用UriMatcher,一個很是有用得類,用來解析URI和決定它的格式。

// 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); 
}

 

你可能使用相似的技術去暴露某個Content Provider更多的URI,這些URI能夠表明不一樣的數據集,或是不一樣的表。

 

建立Content Provider的數據庫

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的onCreate是在主線程中執行的,數據庫一旦打開後,若程序還在運行,不必立刻又把它關掉,這出於效率問題。你可能擔憂資源問題,其實事實是系統若是真須要額外資源,你的APP會被殺死,而後相關的數據庫也會被關。

 

實現Content Provider查詢

爲了支持Content Provider相關的查詢,你必須實現query和getType方法。Content Resolver使用這些方法去獲取低沉的數據而無需關心它的細節和實現。

注意:UriMatcher對象一般用來完善事務和查詢的請求,SQLite Query Builder更方便做爲執行基於行查詢的幫手。

例子:實現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; 
}

 

實現查詢後,你必須還要指定一個MIME類型去鑑定返回的數據。重寫getType方法,返回一個惟一描述你數據的字符串。

此類型返回必須包含2種形式,其一爲單個條目,另一個爲全部的條目:

Single item:

vnd.android.cursor.item/vnd.<companyname>.<contenttype>

All items:

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事務

像query方法,固然還有delete,insert,update方法,實現後,由Content Resolver調用,從而其它APP就可使用。

當執行修改數據集的事務,比較好的方式是去調用Content Resolver的notifyChange方法。這個方法會通知全部內容觀察者。

怎麼用? 直接看一些框架代碼:

@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;  
}

 

Tip:ContentUris類包含一個withAppendedId方法,此方法很方便得幫助構建一個附加指定行ID的Uri。

 

Content Provider 存儲文件

之前提到過,數據庫裏保存文件,一般建議保存的是路徑->一個合適的Uri.

一般表中文件類型的數據列名取名_data形式。

重寫openFile方法

@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 Provider 實現框架代碼:

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 ... ] 
  }

}
相關文章
相關標籤/搜索