1. 程式人生 > >【我的Android進階之旅】自定義ContentProvider

【我的Android進階之旅】自定義ContentProvider

引言

我們知道Android有四大元件,ContentProvider是其中之一,顧名思義:內容提供者。什麼是內容提供者呢?一個抽象類,可以暴露應用的資料給其他應用。應用裡的資料通常說的是資料庫,事實上普通的檔案,甚至是記憶體中的物件,也可以作為內容提供者暴露的資料形式。為什麼要使用內容提供者呢?從上面定義就知道,內容提供者可以實現應用間的資料訪問,一般是暴露表格形式的資料庫中的資料。內容提供者的實現機制是什麼呢?由於是實現應用間的資料通訊,自然也是兩個程序間的通訊,其內部實現機制是Binder機制。那麼,內容提供者也是實現程序間通訊的一種方式。

事實上在開發中,很少需要自己寫一個ContentProvider,一般都是去訪問其他應用的ContentProvider。本篇文章之所以去研究如何自己寫一個ContentProvider,也是為了更好的在開發中理解:如何訪問其他應用的內容提供者

如何自定義一個ContentProvider

接下來介紹如何自己去實現一個內容提供者,大致分三步進行:
  1. 繼承抽象類ContentProvider,重寫onCreate,CUDR,getType六個方法:onCreate()方法中,獲取SQLiteDatabase物件;CUDR方法通過對uri進行判斷,做相應的增刪改查資料的操作;getType方法是返回uri對應的MIME型別。
  2. 註冊可以訪問內容提供者的uri:建立靜態程式碼塊,static{…code},在類載入的時候註冊可以訪問內容提供者的uri,使用類UriMatcher的addURI(…)完成。
  3. 清單檔案中配置provider:註冊內容提供者,加入authorities屬性,對外暴露該應用的內容提供者。

以下是程式碼部分

package com.wzy.myprovider;

import android.content.ContentProvider;
import android.content.ContentUris;
import android.content.ContentValues;
import android.content.Context;
import android.content.UriMatcher;
import android.database.Cursor;
import android.database.SQLException;
import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteOpenHelper;
import android.net.Uri;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;

public class BookProvider extends ContentProvider {
    static final String DATABASE_NAME = "provider.db";
    static final String DATABASE_TABLE = "book";
    static final int DATABASE_VERSION = 1;
    static final String DATABASE_CREATE = "create table " +
            DATABASE_TABLE + " (_id integer primary key autoincrement, " + "name text not null, author text not null);";
    static final String PROVIDER_NAME = "com.wzy.provider";
    static final int BOOKS = 1;
    static final int BOOK_ID = 2;
    static UriMatcher uriMatcher;
    SQLiteDatabase db;

    static {
        uriMatcher = new UriMatcher(UriMatcher.NO_MATCH);
        uriMatcher.addURI(PROVIDER_NAME, "books", BOOKS);
        uriMatcher.addURI(PROVIDER_NAME, "books/#", BOOK_ID);
    }

    private static class DatabaseHelper extends SQLiteOpenHelper {
        DatabaseHelper(Context context) {
            super(context, DATABASE_NAME, null, DATABASE_VERSION);
        }

        @Override
        public void onCreate(SQLiteDatabase db) {
            db.execSQL(DATABASE_CREATE);
        }

        @Override
        public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
            db.execSQL("DROP TABLE IF EXISTS BookInfo");
            onCreate(db);
        }
    }


    @Override
    public boolean onCreate() {
        DatabaseHelper helper = new DatabaseHelper(getContext());
        db = helper.getWritableDatabase();
        return false;
    }


    @Nullable
    @Override
    public String getType(@NonNull Uri uri) {
        switch (uriMatcher.match(uri)) {
            case BOOKS:
                return "vnd.android.cursor.dir/vnd.com.wzy.provider.books";
            case BOOK_ID:
                return "vnd.android.cursor.item/vnd.com.wzy.provider.books";
            default:
                throw new IllegalArgumentException("Unsupported URI: " + uri);
        }
    }

    @Nullable
    @Override
    public Cursor query(@NonNull Uri uri, @Nullable String[] projection, @Nullable String selection, @Nullable String[] selectionArgs, @Nullable String sortOrder) {
        Cursor cursor = null;
        switch (uriMatcher.match(uri)) {
            case BOOK_ID:
                cursor = db.query(DATABASE_TABLE, projection, "_id = ", new String[]{uri.getPathSegments().get(1)}, null, null, sortOrder);
                break;
            case BOOKS:
                cursor = db.query(DATABASE_TABLE, projection, selection, selectionArgs, null, null, sortOrder);
                break;
            default:
                throw new IllegalArgumentException("Unknown URI " + uri);
        }
        return cursor;
    }

    @Nullable
    @Override
    public Uri insert(@NonNull Uri uri, @Nullable ContentValues values) {
        long rowId = db.insert(DATABASE_TABLE, null, values);
        if (rowId > 0) {
            Uri bookUri = ContentUris.withAppendedId(uri, rowId);
            return bookUri;
        }
        throw new SQLException("Failed to insert row into " + uri);
    }

    @Override
    public int delete(@NonNull Uri uri, @Nullable String selection, @Nullable String[] selectionArgs) {
        int rowIDs;
        switch (uriMatcher.match(uri)) {
            case BOOK_ID:
                String bookID = uri.getPathSegments().get(1);
                rowIDs = db.delete(DATABASE_TABLE, "_id = ", new String[]{bookID});
                break;
            case BOOKS:
                rowIDs = db.delete(DATABASE_TABLE, selection, selectionArgs);
                break;
            default:
                throw new IllegalArgumentException("Unknown URI " + uri);
        }
        return rowIDs;
    }

    @Override
    public int update(@NonNull Uri uri, @Nullable ContentValues values, @Nullable String selection, @Nullable String[] selectionArgs) {
        int rowIDs;
        switch (uriMatcher.match(uri)) {
            case BOOK_ID:
                String bookID = uri.getPathSegments().get(1);
                rowIDs = db.update(DATABASE_TABLE, values, "_id = ?", new String[]{bookID});
                break;
            case BOOKS:
                rowIDs = db.update(DATABASE_TABLE, values, selection, selectionArgs);
                break;
            default:
                throw new IllegalArgumentException("Unknown URI " + uri);
        }
        return rowIDs;
    }

}

初次觀察程式碼,我們可以發現,內容提供者就是基於sql實現的一層封裝。我們再觀察靜態程式碼塊,在這裡,值得一提的是靜態程式碼塊總是執行在類的構造方法執行之前。

static {
    uriMatcher = new UriMatcher(UriMatcher.NO_MATCH);
    uriMatcher.addURI(PROVIDER_NAME, "books", BOOKS);
    uriMatcher.addURI(PROVIDER_NAME, "books/#", BOOK_ID);
}

我們進入UriMatcher的構造方法去檢視下

public UriMatcher(int code)
{
    mCode = code;
    mWhich = -1;
    mChildren = new ArrayList<UriMatcher>();
    mText = null;
}

在這個裡面維護了一個ArrayList,可以預知的是,這個類裡面的addURI和match方法就是基於對這個列表新增和遍歷實現的。在這裡就不多做贅述了,有興趣的童鞋可以自行研究。

我們繼續往下觀察,我們建立了一個抽象幫助類SQLiteOpenHelper的子類並在onCreate中去例項化了一個物件,然後通過getWritableDatabase()獲取SQLiteDatabase例項。其實這裡應該的做法是單獨建立一個類繼承自SQLiteOpenHelper,並使用單例模式來獲取其例項。

我們來了解下uri。uri是如何構成的呢? uri 有三個構成元素scheme + authorities + path。先看這樣一個uri,uri = “content://com.wzy.provider/books”,在這裡,三個構成元素分別是:

scheme:“content://”; 一般是固定的 authorities :“com.wzy.provider”;authorities就是在清單檔案中配置的authorities屬性的值,唯一標識該應用的內容提供者。 path:"/books";path裡面常常放的是一些表名,欄位資訊,確定訪問該資料庫中哪個表的哪些資料.

繼續往下,我們接下里分析CUDR操作,我們重寫了這樣四個方法:query,insert,delete,update,這個四個方法的引數都是想訪問該應用的其他使用者傳遞過來的.我們呼叫uriMatcher.match(uri)來匹配uri進行對應的CRUD操作.如果返回的code沒有在uriMatcher中註冊過.就跑出IllegalArgumentException("Unsupported URI: " + uri)異常,代表uri不合法。如果合法就呼叫SQLiteDatabase的query,insert,delete,update四個方法進行增刪改查資料。

值得一提的是,在進行insert操作的時候返回的是一個uri,我們往資料庫插入一條記錄併成功返回該記錄的ID的時候,會將該ID以路徑的方式拼接到原始uri上面。舉個栗子:比如說我們通過"content://com.wzy.provider/books"往資料庫表book成功插入一條資料,並返回id為99,那麼我們得到的uri就是"content://com.wzy.provider/books/99".

這樣,一個完整的ContentProvider就定義完成了。不要忘記在清單檔案中進行申明:

<provider
    android:name=".BookProvider"
    android:authorities="com.wzy.provider"
    android:exported="true">

</provider>

要注意,設定exported="true"是為了其他應用能訪問到我們的BookProvider。

關於呼叫

現在內容提供者也定義好了,我們該派他上場了。之前說過,內容提供者是基於Binder實現的,可以進行跨程序訪問。現在我們重新建立一個測試應用,寫個簡單的測試程式碼並列印log可以看下效果
private void test() {
    Uri uri = Uri.parse("content://com.wzy.provider/books");
    ContentValues values = new ContentValues();
    values.put("name","西遊記");
    values.put("author","吳承恩");
    Uri bookUri = getContentResolver().insert(uri,values);
    values.put("name","紅樓夢");
    values.put("author","曹雪芹");
    getContentResolver().update(bookUri,values,null,null);
}

寫在最後

繼承ContentProvider還有一個重要的getType()方法需要重寫,這個留到下一章吧,now,I need to fill my stomach;

在這裡插入圖片描述