星期一, 9月 01, 2014

Creating a Content Provider (二)

Implementing the ContentProvider Class

ContentProvider實例藉由處理其他應用程式的請求,管理對結構化資料的存取。所有存取的格式最終呼叫ContentResolver,然後呼叫具體的ContentProvider的方法去取得存取。


Required methods

ContentProvider抽象類別定義了六個抽象方法,這些方法必須實作作為你的子類別的一部份。這些所有的方法除了onCreate()外都會被客戶端應用程式呼叫,以嘗試存取你的content provider。

query()
從你的content provider擷取資料。利用參數選擇查詢的資料表、回傳的行與列、結果的排序。回傳資料為一個Cursor物件。
insert()
插入新的row到你的content provider。利用參數選擇目標資料表及獲取column值去使用。回傳薪插入的row的content URI。
update()
更新已存在於你的content provider的row。利用參數選擇資料表與rows來更新以及取得被更新的column值。回傳被更新的row的數量。
delete()
從你的content provider刪除rows。利用參數選擇資料表與rows來刪除。回傳被刪除rows的數量。
getType()
回傳對應到一個content URI的MIME型態。詳細說明在Implementing Content Provider MIME Types。
onCreate()
初始化你的content provider。Android系統建立你的content provider之後,立即呼叫此方法。注意到你的content provider不會被建立,直到ContentResolver嘗試存取它。
注意到這些方法都有一個相同的特徵,與ContentResolver的方法同名。

這些方法的實作應考慮以下內容:

  • 這些所有方法除了onCreate()以外都可以同時被多個執行續呼叫,所以它們必須是thread safe。詳細說明在Processes and Threads。
  • 避免在onCreate()方法中執行冗長的操作。延遲初始化任務直到它們真的被需要,詳細於Implementing the onCreate() method討論。
  • 雖然你必須值做這些方法,你的程式碼不必任何事都做,除了回傳預期的資料型態。例如,你想要預防其他應用程式在某些資料表上插入資料。達成方式可以忽略對insert()的呼叫並回傳0。


Implementing the query() method

ContentProvider.query()方法必須回傳Cursor物件,或是如果失敗,丟出一個Exception。如果你是使用SQLite資料庫做為資料的儲存,你可以簡單地回傳Cursor,此Cursor是SQLiteDatabase類別其中一個query()方法的回傳。如果查詢無符合條件的row,你應該回傳一個Cursor實例,它的getCount()方法回傳值為0。只在如果查詢過程中內部發生錯誤時,回傳null。

如果你不是使用SQLite資料庫做為資料儲存,使用具體的Cursor子類的其中之一。例如,MatrixCursor類別實作一個Cursor,在Cursor中每一row是一個Object陣列。用此類別,使用addRow()方法新增新的row。

記住了,Android系統必須能夠與Exception通信跨過處理程序的邊界。Android可以執行以下exceptions,它們對於處理查詢錯誤有幫助。
  • IllegalArgumentException (如果你的provider接收到一個無效的content URI時,你可能選擇丟出此例外)
  • NullPointerException

Implementing the insert() method

insert()方法用來插入新的row到適當的資料表,利用在ContentValues參數中的值。如果column名稱沒有在ContentValues參數中,你可想要在你的provider程式碼中或你的資料庫綱目中,為它提供預設值。

該方法應該回傳新的row的content URI。建構content URI,使用withAppendedId()方法,將新的row的_ID(或其他主鍵)值附加到資料表的content URI上。



Implementing the delete() method

delete()方法不必實際刪除來自資料儲存中的row,如果與你的provider正使用sync adapter,你應該考慮在被刪除的row標記"delete"標籤,而不是移除整個row。sync adapter可以確認被刪除的row並從provider刪除它們之前將它們從伺服器中移除。


Implementing the update() method

update()方法採用與insert()方法相同的ContentValues參數,相同於使用在delete()方法和ContentProvider.query()方法的selection和selection Args參數。如此可能允許你在這些方法中重複使用程式碼。


Implementing hte onCreate() method

當Android系統啟動provider,Android系統呼叫onCreate()。在此方法中,你應該只執行快速運行的初始化任務,並延遲資料庫的建立和資料的加載,直到provider實際接收到對於資料的請求。如果你在onCreate()方法中執行冗長任務,將會減緩provider的啟動,換言之,將會減緩provider對於其他應用程式的反應。

例如,如果你使用SQLite資料庫,可以在ContentProvider.onCreate()建立一個新的SQLiteOpenHelper物件,然後在你第一次打開資料庫時建立SQLite資料表。促使該事,第一時間你可以呼叫getWritableDatabase()方法,它會自動地呼叫SQLiteOpenHelper.onCreate()方法。

以下兩片段示範ContentProvider.onCreate()與SQLiteOpenHelper.onCreate()之間的互動。第一片段ContentProvider.onCreate()方法的實作。

public class ExampleProvider extends ContentProvider

    /*
     * Defines a handle to the database helper object. The MainDatabaseHelper class is defined
     * in a following snippet.
     */
    private MainDatabaseHelper mOpenHelper;

    // Defines the database name
    private static final String DBNAME = "mydb";

    // Holds the database object
    private SQLiteDatabase db;

    public boolean onCreate() {

        /*
         * Creates a new helper object. This method always returns quickly.
         * Notice that the database itself isn't created or opened
         * until SQLiteOpenHelper.getWritableDatabase is called
         */
        mOpenHelper = new MainDatabaseHelper(
            getContext(),        // the application context
            DBNAME,              // the name of the database)
            null,                // uses the default SQLite cursor
            1                    // the version number
        );

        return true;
    }

    ...

    // Implements the provider's insert method
    public Cursor insert(Uri uri, ContentValues values) {
        // Insert code here to determine which table to open, handle error-checking, and so forth

        ...

        /*
         * Gets a writeable database. This will trigger its creation if it doesn't already exist.
         *
         */
        db = mOpenHelper.getWritableDatabase();
    }
}
下一片段是SQLiteOpenHelper.onCreate()實作,包含一個Helper類別。

..
// A string that defines the SQL statement for creating a table
private static final String SQL_CREATE_MAIN = "CREATE TABLE " +
    "main " +                       // Table's name
    "(" +                           // The columns in the table
    " _ID INTEGER PRIMARY KEY, " +
    " WORD TEXT"
    " FREQUENCY INTEGER " +
    " LOCALE TEXT )";
...
/**
 * Helper class that actually creates and manages the provider's underlying data repository.
 */
protected static final class MainDatabaseHelper extends SQLiteOpenHelper {

    /*
     * Instantiates an open helper for the provider's SQLite data repository
     * Do not do database creation and upgrade here.
     */
    MainDatabaseHelper(Context context) {
        super(context, DBNAME, null, 1);
    }

    /*
     * Creates the data repository. This is called when the provider attempts to open the
     * repository and SQLite reports that it doesn't exist.
     */
    public void onCreate(SQLiteDatabase db) {

        // Creates the main table
        db.execSQL(SQL_CREATE_MAIN);
    }
}