3html
與設備上的其餘應用分享寵物應用數據不在此課程的範圍以內。可是,咱們想展現來自 Humane Society Silicon Valley 的 Jessica 的一個小片斷,其中包含分享他們的寵物收容所數據的使用案例。在此課程中,咱們將在寵物應用中建立一個內容提供程序 ,它會爲未來進行此類型的數據分享奠基基礎。java
5react
PetProvider.java 文件的代碼片斷android
6github
內容提供程序文檔數據庫
7數組
提示:對於內容主機名,可在 這裏 中查看咱們的 provider manifest 清單。markdown
9
咱們剛纔看到,設計和使用正確的 URI 對咱們從表中獲取所需的信息很是重要。
如今,咱們來看看如何向 PetContract.java
代碼添加 URI。
還記得 URI 的 3 個部分嗎——scheme、content 主機名和數據類型。
因爲其中某些成分是可重複使用的,不會發生變化,咱們能夠將它們做爲常數。
那麼如今的問題是存儲這些常數的最佳地方是哪裏。記得咱們之間將與數據相關的全部常數存儲在了 Contract 類中,因此這也是存儲 URI 常數信息的一個理想選擇。
首先咱們來看用於識別咱們以前在 AndroidManifest 標籤中設置的 內容提供程序 的內容主機名(Content Authority):
<provider android:name=」.data.PetProvider」 android:authorities=」com.example.android.pets」 android:exported=」false」 />
在 PetContract.java
中,咱們將它設置爲一個字符串常數,它的值和 AndroidManifest 中的同樣:
public static final String CONTENT_AUTHORITY = "com.example.android.pets";
接下來,咱們將 CONTENT_AUTHORITY 常數與 scheme 「content://」鏈接起來,咱們將建立 BASE_CONTENT_URI,它將由與 PetsProvider 關聯的每個 URI 共用:
"content://" + CONTENT_AUTHORITY
要使這個 URI 有用,咱們將使用 parse 方法,它將 URI 字符串做爲輸入,而後返回一個 URI。
public static final Uri BASE_CONTENT_URI = Uri.parse("content://" + CONTENT_AUTHORITY);
此常數存儲位置將附加到基本內容 URI 的每一個表的路徑。
public static final String PATH_PETS = "pets";
最後,在 contract 中的每一個 Entry 類中,咱們爲類建立一個完整的 URI 做爲常數,稱爲 CONTENT_URI。
Uri.withAppendedPath() 方法將 BASE_CONTENT_URI(包含 scheme 和內容主機名)附加到路徑段。
public static final Uri CONTENT_URI = Uri.withAppendedPath(BASE_CONTENT_URI, PATH_PETS);
在添加了這些常數後,PetContract.java 類看起來將是 這樣的。
複製 PetEntry 外的前 3 個常數,更新你的 PetContract.java 類,以匹配咱們的代碼。
而後,複製 PetEntry 內的 CONTENT_URI 常數。
11
內容提供程序中的 URI matcher 示例 (向下滾動至 ExampleProvider 代碼片斷)
13
將 PetProvider 類中的 query() 方法替換爲 此代碼片斷。
如需瞭解更多,這裏有關於 ContentUris.parseId() 方法的詳細信息。
14
在這裏查看 ContentResolver 查詢示例。
16
將 PetProvider 類中的 insert() 方法替換爲此代碼片斷。
提示 #1:可去掉 CatalogActivity 中的 PetDbHelper
提示 #2:將 toast 消息中的文本移至 strings.xml 資源文件中,使它們不會硬編碼至 Java 文件中。
在這裏查看 ContentResolver 插入示例。
答案中完整 CatalogActivity.java 文件,連接在這裏
18
你能夠看到,肯定用戶輸入的寵物數據有效是很是重要的。在咱們的上下文中,對數據進行完整性檢查(也稱爲數據驗證或輸入驗證)意味着進行一個快速測試,以在將數據插入數據庫前,確保數據在你的合理指望內。
一旦無效數據進入你的數據庫,要整理良好和不良數據可就麻煩了。它會讓你的數據分析變得困難,由於你觀察到的趨勢會不可靠。並且,UI 代碼會變得極其複雜,由於它必須處理全部這些異常值,而沒法對數據作出特定假設。
在咱們的應用中,進行完整性檢查的最佳位置是在 PetProvider 中,在對數據庫進行任何更改前執行。特別是,PetProvider 暴露 query()、insert()、update() 和 delete() 方法,對吧? 可是查詢數據不須要對數據庫進行任何更改,因此無需在此添加任何檢查。刪除數據也不會添加新數據。可是,插入和更新數據就須要在數據庫中插入新數據,因此咱們須要對這些 Provider 方法進行完整性檢查。 一個有趣的類比就是將 內容提供程序 視爲警察,它負責容許或拒絕進入數據庫的數據。
就像在這節課開頭提到的,內容提供程序 還有另一個優點。若是沒有它,咱們就得在插入或更新寵物的 UI 代碼中的全部地方複製/粘貼一樣的數據驗證邏輯。 當複製粘貼操做較多時,不免會引入錯誤。並且未來的開發人員有可能會調整一個地方的數據驗證代碼,但意外地忘記了調整其餘地方的代碼。可是如今,全部的邏輯均可以集中在 PetProvider 文件中,若是須要修改,咱們只在一個地方修改便可。
咱們將對 insert() 和 update() 方法中傳入的 ContentValues 對象的每一個值進行完整性檢查。因爲咱們僅實現了 PetProvider insert() 方法,咱們主要在此方法中進行數據驗證。以後,當你實現 update() 方法時,確保也進行數據驗證。
此練習的第一步是寫下 ContentValues 捆綁包中每一個值的要求:名稱、品種、性別和體重。例如,咱們不想讓空名稱進入數據庫。
第二步是獲取每項要求,在 PetProvider insert() 方法中測試它們。我用名稱屬性向你展現一個示例,咱們不但願名稱爲 null。
咱們能夠根據鍵名從 ContentValues 對象中提取一項屬性。咱們可使用 ContentValues.getAsString(PetEntry.COLUMN_PET_NAME) 提取爲名稱存儲的字符串值,好比它能夠是 Tommy。
假設「values」是一個 ContentValues 對象:
String name = values.getAsString(PetEntry.COLUMN_PET_NAME);
你能夠根據你感興趣的屬性的數據類型,使用其餘 ContentValues 方法,如:getAsInteger() 或 getAsBoolean()。可用的 ContentValues 方法參見 文檔頁面。
而後咱們能夠檢查 ContentValues 對象的名稱是否爲空。若是爲空,咱們應使用錯誤消息拋出一個新的 IllegalArgumentException,說「須要爲寵物添加名稱」(Pet requires a name),而非繼續建立新寵物。 這樣,調用此 Provider 方法的任何開發人員將知道他們須要更改代碼,覺得寵物提供一個名稱。
(注:如「Android 基礎知識:網絡」中所介紹,能夠拋出異常並中止應用運轉,以向開發人員發出信號,說明發生了錯誤,若是接受錯誤數據並容納它會使結果更糟。 理想狀況下,調用此方法的 UI 代碼會足夠智能,向最終用戶顯示錯誤來告訴他們在到達應用崩潰點以前提供一個寵物名稱。)
在 PetProvider.java 中:
private Uri insertPet(Uri uri, ContentValues values) { // Check that the name is not null String name = values.getAsString(PetEntry.COLUMN_PET_NAME); if (name == null) { throw new IllegalArgumentException("Pet requires a name"); } …
名稱能夠爲空的緣由有 2 個。要麼 Contentvalues 對象明確添加了代碼:values.put(PetEntry.COLUMN_PET_NAME, null)。或從一開始鍵/值對就未添加到 ContentValues 對象。記住,不保證寵物應用中的 ContentValues 對象裏有所有 4 個寵物屬性。在 UI 代碼的某個地方,咱們可能不當心忘記了添加某個屬性,如名稱,而僅向 ContentValues 捆綁包添加了品種、性別和體重。
無論是怎樣發生的,PetProvider 僅關心數據不包含空名稱,不然就會拋出一個錯誤。在這個練習中,你須要填寫 TODO,向 insertPet() 方法添加更多代碼以確保 ContentValues 對象你在第 1 步列出的要求。
查看此連接複習 Java 運算符,包括等式運算符,關係運算符,&& 運算符 和 || 運算符。
提示:你可能須要在 PetContract 中編寫一個方法,使用 PetContract 中聲明的性別常數,肯定性別值是否有效。
在 PetProvider.java 中:
private Uri insertPet(Uri uri, ContentValues values) { // Check that the name is not null String name = values.getAsString(PetEntry.COLUMN_PET_NAME); if (name == null) { throw new IllegalArgumentException("Pet requires a name"); } // TODO: Finish sanity checking the rest of the attributes in ContentValues // Get writeable database SQLiteDatabase database = mDbHelper.getWritableDatabase(); // Insert the new pet with the given values long id = database.insert(PetEntry.TABLE_NAME, null, values); // If the ID is -1, then the insertion failed. Log an error and return null. if (id == -1) { Log.e(LOG_TAG, "Failed to insert row for " + uri); return null; } // Return the new URI with the ID (of the newly inserted row) appended at the end return ContentUris.withAppendedId(uri, id); }
對於品種,此字段能夠爲空,無需在 Provider 中檢查該值。對於性別,該字段必須爲非空,且必須等於如下有效性別常數中的一個:GENDER_MALE、GENDER_FEMALE 或 GENDER_UNKNOWN。 最後,體重(weight)屬性略有點麻煩。嚴格來講,從咱們定義 pets 表的方式來看,重量能夠爲空。咱們添加了一個數據庫約束,在沒有提供重量的狀況下可使用默認值 0。因此咱們容許空重量值。可是若是提供了重量值,咱們必須確保它大於或等於 0。但不容許負重量。
咱們能夠跳過品種,對性別進行完整性檢查。因爲性別存儲爲一個整數,咱們使用 ContentValues getAsInt() 方法並傳入性別列鍵。
在 PetProvider insertPet() 方法中:
Integer gender = values.getAsInteger(PetEntry.COLUMN_PET_GENDER);
若是性別爲空或其並不是有效性別值中的一個,那麼咱們就拋出一個 IllegalArgumentException,顯示錯誤消息「請爲寵物提供有效的性別」(Pet requires valid gender)。 注意,在 PetEntry.isValidGender(gender) 前加上「!」符號表示該值的相反值。若是 isValidGender() 返回 true,那麼在它簽名加上「!」符號,返回的值將爲 false。 若是 isValidGender()返回的值爲 false,那麼在它前面添加「!」符號將使返回的值爲 true。 我還使用了「||」運算符,由於若是性別爲空或無效,那麼「if」檢查將爲 true,咱們應拋出一個異常。這個邏輯有點複雜,你能夠嘗試孤立「if」檢查的每一個部分,一個一個來,以確保你所有搞清楚。
if (gender == null || !PetEntry.isValidGender(gender)) { throw new IllegalArgumentException("Pet requires valid gender"); }
我在 PetContract 的定義了性別常數的 PetEntry 類中定義了 isValidGender() 方法。此方法將整數做爲輸入,根據整數是否有有效性別(等於 GENDER_MALE、GENDER_FEMALE 或 GENDER_UNKNOWN)返回 true 或 false。 我打算將這個輔助方法放在 PetContract 中,由於我認爲它在應用的多個地方能夠用到。要了解此方法添加在 PetContract 文件的何處,請查看此連接。
在 PetContract.java 文件的 PetEntry 類中:
/** * Returns whether or not the given gender is {@link #GENDER_UNKNOWN}, {@link #GENDER_MALE}, * or {@link #GENDER_FEMALE}. */ public static boolean isValidGender(int gender) { if (gender == GENDER_UNKNOWN || gender == GENDER_MALE || gender == GENDER_FEMALE) { return true; } return false; }
好的,這樣咱們就能夠確保性別值知足咱們的要求。
要從 ContentValues 對象中提取重量值,咱們使用 ContentValues getAsInteger() 方法,並傳入重量做爲鍵/值對中的鍵。 在 PetProvider insertPet() 方法中:
In PetProvider insertPet() method:
// If the weight is provided, check that it's greater than or equal to 0 kg Integer weight = values.getAsInteger(PetEntry.COLUMN_PET_WEIGHT);
若是重量爲空,不要緊,咱們能夠繼續進行插入(數據庫會自動插入默認重量 0)。若是重量不爲空,而爲負值,那麼咱們須要拋出一個異常,顯示消息「請爲寵物提供有效的重量」(Pet requires valid weight)。 咱們使用「&&」符號代表」「weight != null」和「weight < 0」都必須爲 true,才能使整個測試條件的結果爲 true,並執行「if」語句中的代碼。
if (weight != null && weight < 0) { throw new IllegalArgumentException("Pet requires valid weight"); }
若是全部完整性檢查都經過了,且值都是合理的,那麼咱們即可以繼續使用代碼向數據庫中插入寵物。這是此編碼任務結尾處的 insertPet() 方法。
在 PetProvider.java 中:
private Uri insertPet(Uri uri, ContentValues values) { // Check that the name is not null String name = values.getAsString(PetEntry.COLUMN_PET_NAME); if (name == null) { throw new IllegalArgumentException("Pet requires a name"); } // Check that the gender is valid Integer gender = values.getAsInteger(PetEntry.COLUMN_PET_GENDER); if (gender == null || !PetEntry.isValidGender(gender)) { throw new IllegalArgumentException("Pet requires valid gender"); } // If the weight is provided, check that it's greater than or equal to 0 kg Integer weight = values.getAsInteger(PetEntry.COLUMN_PET_WEIGHT); if (weight != null && weight < 0) { throw new IllegalArgumentException("Pet requires valid weight"); } // No need to check the breed, any value is valid (including null). // Get writeable database SQLiteDatabase database = mDbHelper.getWritableDatabase(); // Insert the new pet with the given values long id = database.insert(PetEntry.TABLE_NAME, null, values); // If the ID is -1, then the insertion failed. Log an error and return null. if (id == -1) { Log.e(LOG_TAG, "Failed to insert row for " + uri); return null; } // Return the new URI with the ID (of the newly inserted row) appended at the end return ContentUris.withAppendedId(uri, id); }
練習完成先後的差別。
棒極了!相信到此,你已經徹底明白了在 Provider 中添加基本檢查對確保你的數據庫中的數據乾淨的重要性,而且它將在之後爲你省去不少讓人頭疼的工做。
19
好的,如今是你編寫其餘 Provider 方法的時候了。咱們接下來看 update() 方法,由於它和 insert() 比較類似。
請參閱 內容提供程序 update() 的文檔,瞭解此方法的輸入和輸出。輸入參數爲 Uri、ContentValues 對象以及 selection 和 selectionArgs。返回值爲成功更新的行的編號。
這是 update() 方法的端到端流程。
根據來自 UriMatcher 的結果,PETS 和 PET_ID case 均受支持。在 PETS case 中,調用者想要按照 selection 和 selectionArgs 更新 pets 表中的多個行。 在 PET_ID case 中,調用者想要更新特定寵物。要寫入數據庫中的新值包含在傳入方法的 ContentValues 對象中。
咱們能夠看到如何返回表明受影響行的整數。
假設咱們想將虛擬寵物條目 Toto 更新爲不一樣的寵物 Milo,一隻法國鬥牛犬!你能夠在 Instagram 上的 @frenchiebutt 查看此貨的一些超蠢萌圖片。 因爲它們都是公的,咱們無需更新性別。咱們只需在 ContentValues 中包含 3 個屬性便可:名稱、品種和重量。
update() 方法的輸入示例:
URI: content://com.example.android.pets/pets/ ContentValues: name is Milo, breed is French bulldog, weight is 20 Selection: 「name=?」 SelectionArgs: { 「Toto」 }
update() 方法的輸入示例:
SQLite statement: UPDATE pets SET name = ‘Milo’, breed=’French bulldog’, weight=20 WHERE name=‘Toto’
結果:
If we started off with 3 Toto’s in our pet table, then a successful update operation would return the number 3 - for 3 rows being updated to have Milo’s attributes.
假如咱們想將單個寵物(好比 Tommy)更新爲 Milo。此次,咱們不用傳入帶 selection 和 selectionArgs 的 URI,而是 Tommy 的特定內容 URI(好比 content://com.example.android.pets/pets/5)。 因爲它們的性別都爲公,咱們無需更新性別。咱們只需在 ContentValues 中包含 3 個屬性:姓名、品種和重量。
update() 方法的示例輸入:
URI: content://com.example.android.pets/pets/5 ContentValues: name is Milo, breed is French bulldog, weight is 20
在 update() 方法中:
SQLite statement: UPDATE pets SET name = ‘Milo’, breed=’French bulldog’, weight=20 WHERE _id=5
結果:
A successful update operation would return 1, for one row being updated to have Milo’s attributes (specifically row #5).
如今咱們來實現代碼。這是練習開始的代碼片斷。將你的 PetProvider 類中的當前 update() 方法更新爲下面提供的方法。並添加咱們提供的 updatePet() 輔助方法。
你會注意到 PETS 和 PET_ID cases 都調用了updatePet() 方法來執行實際的數據庫操做。惟一的區別是在 PET_ID case 中,咱們多了 2 行代碼,用來手動設置 selection 字符串和 selection arguments 數組根據傳入的寵物 URI 指向單個寵物。 與 PetProvider query() 方法中的邏輯相似,咱們將 selection字符串設爲「id=?」,而 selectionArgs 爲咱們關心的行 ID(經過使用 ContentUris.parseId() 方法從 URI 中抽取 ID)。 這是 update() 方法中發生的主要步驟。
Provider update() 方法會返回受影響的行的編號。若是你嘗試使用空 ContentValues 對象調用 update() 方法, Provider 將返回更新的行數爲 0。
代碼中留了一個 TODO 須要由你來完成。根據 updatePet() 輔助方法上的註釋,你會看到此方法用於執行實際的數據庫更新操做。
對 ContentValues 對象中的數據進行完整性檢查
因爲你在向數據庫中插入新數據,確保名稱、品種、性別和重量值知足咱們在以前練習中列出的要求。
雖然數據要求與 insert() 方法的同樣,可是仍然有一個關鍵差異。對於 insert() 方法,因爲要插入的是全新寵物,全部全部屬性(品種除外)都應提供。但對於 update() 方法,你的 ContentValues 對象中不須要所有四個屬性。你只須要更新一個屬性,例如品種。在此狀況下,你更新的字段無需在 ContentValues 對象中。這些字段(ContentValues 對象中不包含的)將和以前保持同樣。
由於無需提供全部的值,咱們建議你在檢查值是否合理前,使用 ContentValues containsKey()) 方法來檢查鍵/值對是否存在。
完成後,確保你的應用依然可在你的設備上編譯並運行。如今你還無需更新 UI 代碼來調用 provider update() 方法。這將在第 4 課講解。
好的,咱們就先講到這,開始編碼吧,祝你好運!
在 PetProvider update() 方法中,對每一個可能的更新值執行完整性檢查。首先,咱們使用 ContentValues containsKey() 方法來檢查是否存在每一個屬性。若是存在鍵,那咱們就從其中提取值,而後檢查它是否有效。
咱們能夠換種方式思考此代碼變動,即咱們在每一個寵物屬性(來自 insertPet() 方法)的代碼塊四周包裹一個「if」檢查,以先確認屬性是存在的。
在 PetProvider.java 中:
private int updatePet(Uri uri, ContentValues values, String selection, String[] selectionArgs) { // If the {@link PetEntry#COLUMN_PET_NAME} key is present, // check that the name value is not null. if (values.containsKey(PetEntry.COLUMN_PET_NAME)) { String name = values.getAsString(PetEntry.COLUMN_PET_NAME); if (name == null) { throw new IllegalArgumentException("Pet requires a name"); } } // If the {@link PetEntry#COLUMN_PET_GENDER} key is present, // check that the gender value is valid. if (values.containsKey(PetEntry.COLUMN_PET_GENDER)) { Integer gender = values.getAsInteger(PetEntry.COLUMN_PET_GENDER); if (gender == null || !PetEntry.isValidGender(gender)) { throw new IllegalArgumentException("Pet requires valid gender"); } } // If the {@link PetEntry#COLUMN_PET_WEIGHT} key is present, // check that the weight value is valid. if (values.containsKey(PetEntry.COLUMN_PET_WEIGHT)) { // Check that the weight is greater than or equal to 0 kg Integer weight = values.getAsInteger(PetEntry.COLUMN_PET_WEIGHT); if (weight != null && weight < 0) { throw new IllegalArgumentException("Pet requires valid weight"); } } // No need to check the breed, any value is valid (including null). …
這也是對 ContentValues 對象進行快速檢查的好機會。若是它裏面沒有鍵/值對,那麼僅返回 0 行受影響。若是沒有能夠更新的新值,則無需對數據庫執行操做,並且每一個數據庫操做都會佔用設備上的一些內存資源。
// If there are no values to update, then don't try to update the database if (values.size() == 0) { return 0; }
若是咱們實際想要對數據庫執行一些更改,那麼從 PetDbHelper 獲取可寫入的數據庫(由於咱們在對數據源執行編輯)。 一旦咱們有了 SQLiteDatabase 對象,咱們對它調用 update() 並傳入表名、新的 ContentValues、selection 和 selectionArgs。SQLiteDatabase update() 方法的返回值爲受影響的行的編號。因此咱們能夠直接返回它。
在 PetProvider.java 中:
@Override public int update(Uri uri, ContentValues contentValues, String selection, String[] selectionArgs) { final int match = sUriMatcher.match(uri); switch (match) { case PETS: return updatePet(uri, contentValues, selection, selectionArgs); case PET_ID: // For the PET_ID code, extract out the ID from the URI, // so we know which row to update. Selection will be "_id=?" and selection // arguments will be a String array containing the actual ID. selection = PetEntry._ID + "=?"; selectionArgs = new String[] { String.valueOf(ContentUris.parseId(uri)) }; return updatePet(uri, contentValues, selection, selectionArgs); default: throw new IllegalArgumentException("Update is not supported for " + uri); } } /** * Update pets in the database with the given content values. Apply the changes to the rows * specified in the selection and selection arguments (which could be 0 or 1 or more pets). * Return the number of rows that were successfully updated. */ private int updatePet(Uri uri, ContentValues values, String selection, String[] selectionArgs) { // If the {@link PetEntry#COLUMN_PET_NAME} key is present, // check that the name value is not null. if (values.containsKey(PetEntry.COLUMN_PET_NAME)) { String name = values.getAsString(PetEntry.COLUMN_PET_NAME); if (name == null) { throw new IllegalArgumentException("Pet requires a name"); } } // If the {@link PetEntry#COLUMN_PET_GENDER} key is present, // check that the gender value is valid. if (values.containsKey(PetEntry.COLUMN_PET_GENDER)) { Integer gender = values.getAsInteger(PetEntry.COLUMN_PET_GENDER); if (gender == null || !PetEntry.isValidGender(gender)) { throw new IllegalArgumentException("Pet requires valid gender"); } } // If the {@link PetEntry#COLUMN_PET_WEIGHT} key is present, // check that the weight value is valid. if (values.containsKey(PetEntry.COLUMN_PET_WEIGHT)) { // Check that the weight is greater than or equal to 0 kg Integer weight = values.getAsInteger(PetEntry.COLUMN_PET_WEIGHT); if (weight != null && weight < 0) { throw new IllegalArgumentException("Pet requires valid weight"); } } // No need to check the breed, any value is valid (including null). // If there are no values to update, then don't try to update the database if (values.size() == 0) { return 0; } // Otherwise, get writeable database to update the data SQLiteDatabase database = mDbHelper.getWritableDatabase(); // Returns the number of database rows affected by the update statement return database.update(PetEntry.TABLE_NAME, values, selection, selectionArgs); }
咱們運行應用以確保它依然可編譯。咱們將在第 4 節課中鏈接 UI 的更新功能時真正測試此代碼是否正確實現了。
20
好的,最後一個要實現的方法是刪除。到目前爲止你已學習了三種方法的實現示例,我相信你能夠本身實現 delete() 方法。
從 內容提供程序 delete() 方面的文檔中,咱們瞭解到此方法有 3 個輸入:uri、selection 和 selectionArgs。返回值是成功刪除的行的編號。
這裏是 delete() 方法端到端的流程圖。
UriMatcher 幫助肯定執行這兩種 case 中的哪個:PETS 仍是 PET_ID case。在 PETS case 中,調用者想要根據 selection 和 selectionArgs 刪除 pets 表中的多個行。在 PET_ID case 中,調用者想要刪除特定寵物。
上面的圖顯示了返回值爲表明刪除行編號的整數。
S假設收容所開展了一個「收養花斑貓」的活動,其中全部的花斑貓都被收養了。這意味着咱們須要從 pets 表中刪除全部品種爲花斑貓的動物。
delete() 方法示例輸入
URI: content://com.example.android.pets/pets Selection: 「breed=?」 SelectionArgs: { 「Calico」 }
在 delete() 方法中:
SQLite statement: DELETE pets WHERE breed= ‘Calico’
結果:
成功的刪除操做會返回 pets 表中最初花斑貓的數量。例如,若是收容所中最初有 10 只花斑貓,刪除操做後咱們會得到 10 個行的編號。
例如,法國鬥牛犬 Milo(@frenchiebutt on instagram)很是可愛,有一個家庭來收養了它!這意味着咱們須要從帶收養寵物表中將它刪除。
delete() 方法輸入示例:
URI: content://com.example.android.pets/pets/5 Selection: 「name=?」 SelectionArgs: { 「Milo」 }
在 delete() 方法中:
SQLite statement: DELETE pets WHERE _id=5
結果:
成功的刪除操做將返回 1,由於一個行被刪除。## delete() 方法的代碼
刪除當前 delete() 方法的代碼,替換爲這個代碼片斷 中的代碼。並完成TODO。
Delete() 方法和其餘方法看起來很像。我首先抓取數據庫的可寫入版本,而後匹配 URI。我有兩個 case,一個是刪除全部寵物,一個是刪除單個寵物。
若是未給定任何一個的 URI,將拋出異常,就像其餘方法中同樣。到此,個人四個 CRUD 方法就完成了。
@Override
public int delete(Uri uri, String selection, String[] selectionArgs) { // Get writeable database SQLiteDatabase database = mDbHelper.getWritableDatabase(); final int match = sUriMatcher.match(uri); switch (match) { case PETS: // Delete all rows that match the selection and selection args return database.delete(PetEntry.TABLE_NAME, selection, selectionArgs); case PET_ID: // Delete a single row given by the ID in the URI selection = PetEntry._ID + "=?"; selectionArgs = new String[] { String.valueOf(ContentUris.parseId(uri)) }; return database.delete(PetEntry.TABLE_NAME, selection, selectionArgs); default: throw new IllegalArgumentException("Deletion is not supported for " + uri); } }
21
你可能注意到在 PetProvider 中,還有一個咱們須要重載的方法:getType(Uri uri) 方法。此方法的用途是返回描述輸入 URI 中存儲的數據類型的字符串。該字符串爲 MIME 類型,也稱爲內容類型。
此功能比較重要的一個使用案例是當你隨數據字段上的 URI 集發送 intent 時,Android 系統會檢查此 URI 的 MIME 類型,肯定設備上的哪一個應用組件最適合處理你的請求。(若是 URI 恰巧爲內容 URI,那麼系統會檢查相應的 內容提供程序,使用 getType() 方法獲取 MIME 類型。) 在此文章「構建 Intent」(Building an Intent) 部分的「數據」(Data) 標題下了解詳情。
Android 文檔中是這樣描述 內容提供程序 getType() 方法的:
實現此 [方法] 來處理給定 URI 的 MIME 數據類型請求。返回的 MIME 類型對個單個記錄應以「vnd.android.cursor.item」 開頭,多個項應以「vnd.android.cursor.dir/」開頭。
定義一開始看起來可能有點不太清楚,咱們經過一個示例來講明。ContactsProvider 能夠處理許多不一樣類型的數據:聯繫人、照片、電話號碼、郵箱地址等……每種數據的 MIME 類型不一樣。瞭解 getType() 方法如何在 ContactsProvider 中實現。
對於常見的數據類型,如圖像,已經有普遍使用的 MIME 類型字符串慣例:「image/jpeg」或「image/png」(或其餘圖像文件擴展名)。而後,對於特定於應用的數據,你能夠自定義 MIME 類型。
聯繫人應用爲單個聯繫人定義了自定義 MIME 類型, 做爲 ContactsContract 中的常數值。而後,若是 URI 指代單個聯繫人,getType() 方法將返回此 MIME 類型。注意,自定義 MIME 類型以「vnd.android.cursor.item」開頭(如以前的定義所述),由於它爲單個記錄。在此狀況中,「單個記錄」指數據庫表中的單個行。
/** * The MIME type of a {@link #CONTENT_URI} subdirectory of a single * person. */ public static final String CONTENT_ITEM_TYPE = "vnd.android.cursor.item/contact";
若是 URI 指代整個聯繫人列表,那聯繫人應用將使用不一樣的自定義 MIME 類型。注意,此狀況下的自定義 MIME 類型以「vnd.android.cursor.dir」開頭(其中「dir」爲目錄的縮寫),由於 URI 能夠指向多個記錄。此處多個記錄指數據庫中的多個行或多個聯繫人。
/** * The MIME type of {@link #CONTENT_URI} providing a directory of * people. */ public static final String CONTENT_TYPE = "vnd.android.cursor.dir/contact";
接下來,我要你閱讀此 StackOverflow 帖子的回覆,其中很好地解釋了 MIME 類型對於 內容提供程序 的重要性並提供了示例。 (維基百科上的這篇文章也更詳細地解釋了 MIME 類型。)
定義自定義寵物 MIME 類型
在咱們的應用中,基本上有兩種類型的 URI。第一種 URI 爲「content://com.example.android.pets/pets/」,指代整個 pets 表。它表明整個寵物列表。用 MIME 類型來講,這稱爲數據目錄。 第二種 URI 是「content://com.example.android.pets/pets/#」,它表明單個寵物。用 MIME 類型來講,單個數據行即單個數據項。
content://com.example.android.pets/pets → Returns directory MIME type content://com.example.android.pets/pets/# → Returns item MIME type
因爲 MIME 類型要遵循特定格式,如下是咱們應用中數據的 MIME 類型。MIME 類型字符串按約定以「vnd.android.cursor…」開頭,後面跟寵物內容主機名,以及數據路徑。
目錄 MIME 類型: vnd.android.cursor.dir/com.example.android.pet/pets
項 MIME 類型: vnd.android.cursor.item/com.example.android.pet/pets
第 1 步:在 PetContract 中聲明 MIME 類型常數
要在咱們的 PetProvider 中實現這個行爲,首先應該在 PetContract 文件中在 PetEntry 內聲明表明 MIME 類型的常數。一個細微的差異就在於 cursor 後的詞:「dir」或「item」。
PetEntry.CONTENT_LIST_TYPE vnd.android.cursor.dir/com.example.android.pet/pets
PetEntry.CONTENT_ITEM_TYPE vnd.android.cursor.item/com.example.android.pet/pets
將如下代碼添加到你的應用。你能夠在文件中定義了寵物內容 URI 的位置後面插入這些常數。
在 PetContract.java 中:
public static final class PetEntry implements BaseColumns { … /** * The MIME type of the {@link #CONTENT_URI} for a list of pets. */ public static final String CONTENT_LIST_TYPE = ContentResolver.CURSOR_DIR_BASE_TYPE + "/" + CONTENT_AUTHORITY + "/" + PATH_PETS; /** * The MIME type of the {@link #CONTENT_URI} for a single pet. */ public static final String CONTENT_ITEM_TYPE = ContentResolver.CURSOR_ITEM_BASE_TYPE + "/" + CONTENT_AUTHORITY + "/" + PATH_PETS; …
你會注意到,咱們在使用 ContentResolver 類中定義的常數:CURSOR_DIR_BASE_TYPE(它映射到常數「vnd.android.cursor.dir」)和 CURSOR_ITEM_BASE_TYPE(映射到常數「vnd.android.cursor.item」)。 所以,將 ContentResolver 類的此額外 import 語句添加到 PetContract 的頂部(若還沒有自動導入)。
在 PetContract.java 中:
import android.content.ContentResolver;
如需 PetContract.java 文件的完整上下文,請查看此處。
第 2 步:實現 內容提供程序 getType() 方法
接下來是對 getType() 方法的實際實現,在這裏必須對每個 Uri 返回正確的 MIME 類型。將此版本的 getType() 方法替換爲你的 PetProvider 中當前存在的空白方法。
UriMatcher PETS case → Return MIME type PetEntry.CONTENT_LIST_TYPE UriMatcher PET_ID case → Return MIME type PetEntry.CONTENT_ITEM_TYPE
在 PetProvider.java 中:
@Override public String getType(Uri uri) { final int match = sUriMatcher.match(uri); switch (match) { case PETS: return PetEntry.CONTENT_LIST_TYPE; case PET_ID: return PetEntry.CONTENT_ITEM_TYPE; default: throw new IllegalStateException("Unknown URI " + uri + " with match " + match); } }
到此,構建你的首個 內容提供程序 就完成了,祝賀你!拍拍你本身的備或與附近的朋友或任何人擊掌歡呼吧!