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能夠與數據進行交互。 框架
像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 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能夠表明不一樣的數據集,或是不一樣的表。
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相關的查詢,你必須實現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); } }
像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。
之前提到過,數據庫裏保存文件,一般建議保存的是路徑->一個合適的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); }
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 ... ] } }