Android四大組件之ContentProvider總結

ContentProvider

ContentProvider,顧名思義,內容提供者,爲存儲和獲取數據提供統一的接口,可以在不同的應用程序之間共享數據

ContentProvider爲應用間的數據交互提供了一個安全的環境。它准許你把自己的應用數據根據需求開放給其他應用進行增、刪、改、查,而不用擔心直接開放數據庫權限而帶來的安全問題。

ContentProvider提供了對底層數據存儲方式的抽象,如下圖中:

這裏寫圖片描述

底層使用了SQLite數據庫,在用了ContentProvider封裝後,即使你把數據庫換成MongoDB,也不會對上層數據使用層代碼產生影響。

ContentResolver

ContentProvider對數據層進行了封裝,那該如何使用ContentProvider進行增,刪,改,查的操作呢?

在一個應用中直接調用另一個應用的ContentProvider,顯然是不可能的。Android中跨進程,一定需要通過Binder機制

其實系統就提供給了我們一個叫ContentResolver的東西。ContentResolver,顧名思義,內容解析者

我們可以在Context的實現類ContextImpl中獲得ApplicationContentResolver的實例,ApplicationContentResolver繼承自ContentResolver(ContentResolver是個抽象類):

    private final ApplicationContentResolver mContentResolver;

    @Override
    public ContentResolver getContentResolver() {
        return mContentResolver;
    }

    private static final class ApplicationContentResolver extends ContentResolver {
        ……
    }

之後ContentResolver通過層層調用,與系統服務進行交互,最後成功調用ContentProvider。情況可以分爲三種:

  1. Provider進程不存在:當Provider進程不存在時,先創建進程併發布對應的Provider。

  2. Provider未發佈:請求Provider時,Provider進程存在但Provider的記錄對象爲空,則要去發佈對應的Provider。

  3. Provider已發佈:直接可以調用了。

關於源碼的解析,請移步大神Gityuan寫的理解ContentProvider原理

ContentResolver幫助我們統一管理與不同ContentProvider間的操作,如圖:
這裏寫圖片描述

ContentProvider中的URI

那ContentResolver是如何找到自己需要的ContentProvider呢?我們通過URI(Uniform Resource Identifier),即一個標識、定位任何資源的字符串

ContentProvider中的URI有固定格式,如下圖:

這裏寫圖片描述

  • Authority:授權信息,用以區別不同的ContentProvider
  • Path:表名,用以區分ContentProvider中不同的數據表
  • Id:Id號,用以區別表中的不同數據

URI組裝代碼示例:

public class TestContract {

    protected static final String CONTENT_AUTHORITY = "me.pengtao.contentprovidertest";
    protected static final Uri BASE_CONTENT_URI = Uri.parse("content://" + CONTENT_AUTHORITY);

    protected static final String PATH_TEST = "test";
    public static final class TestEntry implements BaseColumns {

        public static final Uri CONTENT_URI = BASE_CONTENT_URI.buildUpon().appendPath(PATH_TEST).build();
        protected static Uri buildUri(long id) {
            return ContentUris.withAppendedId(CONTENT_URI, id);
        }

        protected static final String TABLE_NAME = "test";

        public static final String COLUMN_NAME = "name";
    }
}

從上面代碼我們可以看到,我們創建了一個content://me.pengtao.contentprovidertest/test的uri,並且開了一個靜態方法,用以在有新數據產生時根據id生成新的uri。

創建ContentProvider

必須方法

抽象類ContentProvider定義了六個抽象方法,您必須將這些方法作爲自己具體子類的一部分加以實現。 所有這些方法(onCreate() 除外)都由一個嘗試訪問您的內容提供程序的客戶端應用調用:

query()
從您的提供程序檢索數據。使用參數選擇要查詢的表、要返回的行和列以及結果的排序順序。 將數據作爲 Cursor 對象返回。

insert()
在您的提供程序中插入一個新行。使用參數選擇目標表並獲取要使用的列值。 返回新插入行的內容 URI。

update()
更新您提供程序中的現有行。使用參數選擇要更新的表和行,並獲取更新後的列值。 返回已更新的行數。

delete()
從您的提供程序中刪除行。使用參數選擇要刪除的表和行。 返回已刪除的行數。

getType()
返回內容 URI 對應的 MIME 類型。實現內容提供程序 MIME 類型部分對此方法做了更詳盡的描述。

onCreate()
初始化您的提供程序。Android 系統會在創建您的提供程序後立即調用此方法。 請注意,ContentResolver 對象嘗試訪問您的提供程序時,系統纔會創建它。

注意:

  • 所有這些方法(onCreate() 除外)都可由多個線程同時調用,因此它們必須是線程安全方法。

  • 避免在 onCreate() 中執行長時間操作。將初始化任務推遲到實際需要時進行。

  • 儘管您必須實現這些方法,但您的代碼只需返回要求的數據類型,無需執行任何其他操作。 例如,您可能想防止其他應用向某些表插入數據。 要實現此目的,您可以忽略 insert() 調用並返回 0。

UriMatcher

爲幫助您選擇對傳入的內容 URI 執行的操作,提供程序 API 加入了實用類 UriMatcher,它會將內容 URI“模式”映射到整型值。您可以在一個 switch 語句中使用這些整型值,爲匹配特定模式的一個或多個內容 URI 選擇所需操作。

例1:

content://com.example.app.provider/table1

一個名爲 table1 的表

例2:

content://com.example.app.provider/table3/1 

對應由 table3 中 1 標識的行的內容 URI

例3:

以下代碼段演示了 UriMatcher 中方法的工作方式。 此代碼採用不同方式處理整個表的 URI 與單個行的 URI,它爲表使用的內容 URI 模式是 content://< authority >/< path >,爲單個行使用的內容 URI 模式則是 content://< authority >/< path >/< id >

方法addURI()會將授權和路徑映射到一個整型值,方法match()會返回URI的整型值。switch語句會在查詢整個表與查詢單個記錄之間進行選擇:

public class ExampleProvider extends ContentProvider {
...
    // Creates a UriMatcher object.
    private static final UriMatcher sUriMatcher = new UriMatcher(UriMatcher.NO_MATCH);

    static {
        /*
         * The calls to addURI() go here, for all of the content URI patterns that the provider
         * should recognize. For this snippet, only the calls for table 3 are shown.
         */

        /*
         * Sets the integer value for multiple rows in table 3 to 1. Notice that no wildcard is used
         * in the path
         */
        sUriMatcher.addURI("com.example.app.provider", "table3", 1);

        /*
         * Sets the code for a single row to 2. In this case, the "#" wildcard is
         * used. "content://com.example.app.provider/table3/3" matches, but
         * "content://com.example.app.provider/table3 doesn't.
         */
        sUriMatcher.addURI("com.example.app.provider", "table3/#", 2);
    }
...
    // Implements ContentProvider.query()
    public Cursor query(
        Uri uri,
        String[] projection,
        String selection,
        String[] selectionArgs,
        String sortOrder) {
...
        /*
         * Choose the table to query and a sort order based on the code returned for the incoming
         * URI. Here, too, only the statements for table 3 are shown.
         */
        switch (sUriMatcher.match(uri)) {


            // If the incoming URI was for all of table3
            case 1:

                if (TextUtils.isEmpty(sortOrder)) sortOrder = "_ID ASC";
                break;

            // If the incoming URI was for a single row
            case 2:

                /*
                 * Because this URI was for a single row, the _ID value part is
                 * present. Get the last path segment from the URI; this is the _ID value.
                 * Then, append the value to the WHERE clause for the query
                 */
                selection = selection + "_ID = " uri.getLastPathSegment();
                break;

            default:
            ...
                // If the URI is not recognized, you should do some error handling here.
        }
        // call the code to actually do the query
    }

程序權限

即使底層數據爲私有數據,所有應用仍可從您的提供程序讀取數據或向其寫入數據,因爲在默認情況下,您的提供程序未設置權限。 要想改變這種情況,請使用屬性或 < provider > 元素的子元素在您的清單文件中爲您的提供程序設置權限。 您可以設置適用於整個提供程序、特定表甚至特定記錄的權限,或者設置同時適用於這三者的權限

您可以通過清單文件中的一個或多個 < permission > 元素爲您的提供程序定義權限。要使權限對您的提供程序具有唯一性,請爲 android:name 屬性使用 Java 風格作用域。 例如,將讀取權限命名爲 com.example.app.provider.permission.READ_PROVIDER

更多權限設置,請見谷歌官方文檔——創建內容提供程序

參考:
1.ContentProvider從入門到精通
2.谷歌官方文檔——創建內容提供程序
3.理解ContentProvider原理

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章