Android ContentProvider UnderStanding

ContentProvider是Android裏的四大組件之一。

顧名思義,其作爲內容提供者,爲不同應用之間提供了一個數據訪問通道。

對於其的使用,無非也就是兩方面:

  • 在我們自己的應用裏,通過ContentProvider訪問外部應用的數據。
  • 我們在自己的應用裏編寫ContentProvider,提供給其它想要訪問我們應用某些數據的應用使用。

那麼,首先我們來看一下,如何最基本的通過ContentProvider訪問其它應用裏的數據。

從代碼編寫上來說,其實十分簡單。例如,最常見的例子,我們希望通過ContentProvider在自己的應用裏訪問系統通訊錄裏的聯繫人數據。
那麼,代碼可能是這樣的:

private void readContactsByContentProvider() {
        Cursor cursor = null;

        try {
            cursor = getContentResolver().query(ContactsContract.CommonDataKinds.Phone.CONTENT_URI, null, null, null,
                    null);

            while (cursor.moveToNext()) {
                String contactName = cursor
                        .getString(cursor.getColumnIndex(ContactsContract.CommonDataKinds.Phone.DISPLAY_NAME));
                String phoneNunber = cursor
                        .getString(cursor.getColumnIndex(ContactsContract.CommonDataKinds.Phone.NUMBER));

                contacts.add(contactName + "\n" + phoneNunber);
            }
        } catch (Exception e) {
            // TODO: handle exception
        } finally {
            if (cursor != null) {
                cursor.close();
            }
        }

    }

把上面的代碼分解一下,我們發現通過ContentProvider來訪問外部數據的過程其實很簡單:

  • 首先,我們會使用到一個Uri。
  • 接着,我們需要獲取到一個ContentResolver,直譯也就可以理解爲內容解釋器。
  • 結合上面二者,就可以通過Resolver調用增刪改查方法,來操作數據了。

我們注意到兩個關鍵的東西,Uri和ContentResolver。實際上,通過ContentProvider訪問數據的原理,也就是基於他們來實現的。

Uri我們都知道意思是,統一資源標識符。
在我們這裏談到的話題裏,該Uri實際上就是代表的內容提供者爲我們提供的用以訪問某對應資源的資源路徑。
再看到關鍵字,“統一”。既然統一,則代表其具有命名規範,而Android裏ContentProvider使用到的Uri命名規範,通常爲:

  • content://權限名/路徑名

這樣的命名規範實際上十分清晰:

  • content:// 我們可以看做是一種協議聲明,代表通過ContentProvider訪問數據。就好像我們在通過Http協議訪問資源時,Uri的格式是:http://*一樣。
  • 權限名:其格式通常爲“應用包名/provider”。同樣的,通過其命名規範,我們就可以想得到,它是爲了區分訪問不同應用間的內容而設定的。因爲基本來說,每個應用的包名永遠是保持唯一的。這個就如同網頁的域名一樣,我們通過http://www.baidu.com是爲了標明,我們想要訪問的目標資源是百度的網站上的。
  • 路徑名:路徑名是爲了區分不同數據庫表而存在的。這個依然很好理解,因爲一個應用裏往往存在多張不同的表,所以權限名用於區分應用,而路徑名則可以保證我們區分同一個應用的不同的表。正如同通過”http://www.baidu.com/A“和”http://www.baidu.com/B“區分我們訪問的是同樣存在於百度網站上面的,不同的資源。

到這裏,我們已經清楚的知道了ContentPorvider裏的Uri的使用原理和規則。
既然有了資源標識符,那麼,我們自然需要一個解釋器來解析該標識符,並做出正確操作。
以我們前面使用到的例子中的代碼而言:

getContentResolver().query(ContactsContract.CommonDataKinds.Phone.CONTENT_URI, null, null, null,
                    null);

簡單的還原一下,我們可以得到等價的如下代碼:

ContentResolver().query(URI, null, null, null,
                    null);

這樣,我們更能得到重點,我們來解析一下:
1.首先我們已經得到了訪問目標資源的URI。
2.然後我們通過getContentResolver得到了解釋器。
3.通過解釋器,解析URI,我們將得到對應的目標數據路徑。
4.現在我們已經有了一條操作數據的通道,那麼接下來的就是操作數據,如上面的例子中,我們是通過query方法進行操作,也就是執行了查詢操作。

到這裏,對於通過ContentProvider訪問外部應用數據的操作,我們就告一段落了。
現在,來到了更有意思的話題,假如我們想提供數據給外部應用訪問,我們應當怎麼去編寫Provider呢?

我們先最基本的解析其過程:

  • 首先創建一個自己的provider類,繼承自ContentProvider。
  • 接着,重寫其中的6個方法,分別是:
    //主要在這裏面創建或更新數據庫
    1.onCreate()
    //沒什麼好說的,在這裏面實際上就是在操作我們自己應用的數據庫,根據需求邏輯實現即
    2.insert();update();delete();query()。
    // 方法故名思議,返回一個類型。這個類型是什麼呢,實際上就是代表的Uri對象所對應的MIME類型。Android裏規定,其格式應當爲:
    Uri若以路徑結尾:vnd. + android.cursor.dir + / vnd. + 包名(權限名) + . + 路徑
    Uri若以id結尾:vnd. + android.cursor.item + / vnd. + 包名(權限名) + . + 路徑
    3.getType()
  • 最後,當我們完成我們自己的provider類的編寫工作後,將其註冊到項目配置文件裏面。就搞定了,例如:
    <provider
    android:name="com.tsr.contentprovideruse.providers.MyContentProvider"
    android:authorities="com.tsr.contentprovideruse.provider"
    android:exported="true" />

瞭解了實現過程之後,我們來進行一下實戰。假設現在有如下需求:
在我們的應用裏,有表”Student“存放有學生信息。
現在有另一個應用希望訪問該表裏的學生信息情況。

於是,我們完成了ContentProvider的編寫,得到如下代碼:

public class MyContentProvider extends ContentProvider {

    private MyDBOpenHelper mDBOpenHelper;

    private static final String URI_AUTHORITY = "com.tsr.contentprovideruse.provider";
    private static UriMatcher mURIMatcher;

    private static final int STUDENT_DIR = 0;
    private static final int STUDENT_ITEM = 1;

    static {
        mURIMatcher = new UriMatcher(UriMatcher.NO_MATCH);
        mURIMatcher.addURI(URI_AUTHORITY, "student", BOOK_DIR);
        mURIMatcher.addURI(URI_AUTHORITY, "student/#", BOOK_ITEM);
    }

    @Override
    public boolean onCreate() {
        mDBOpenHelper = new MyDBOpenHelper(getContext(), "testDB.db", null, 1);
        return true;
    }

    @Override
    public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder) {
        SQLiteDatabase db = mDBOpenHelper.getReadableDatabase();
        Cursor cursor = null;

        switch (mURIMatcher.match(uri)) {
        case STUDENT_DIR: {
            cursor = db.query("student", projection, selection, selectionArgs, null, null, sortOrder);
        }
            break;
        case STUDENT_ITEM: {
            String id = uri.getPathSegments().get(1);
            cursor = db.query("student", projection, "id = ?", new String[] { id }, null, null, sortOrder);
        }
            break;
        default:
            break;
        }
        return cursor;
    }

    @Override
    public Uri insert(Uri uri, ContentValues values) {
        SQLiteDatabase db = mDBOpenHelper.getReadableDatabase();
        Uri returnUri = null;

        switch (mURIMatcher.match(uri)) {
        case STUDENT_DIR:
        case STUDENT_ITEM:

            long newStudentID = db.insert("student", null, values);
            returnUri = Uri.parse("content://" + URI_AUTHORITY + "student/" + newStudentID);
            break;
        default:
            break;
        }

        return returnUri;
    }

    @Override
    public int delete(Uri uri, String selection, String[] selectionArgs) {
        SQLiteDatabase db = mDBOpenHelper.getReadableDatabase();
        int deleteRows = 0;

        switch (mURIMatcher.match(uri)) {
        case STUDENT_DIR: {
            deleteRows = db.delete("student", selection, selectionArgs);
        }
            break;
        case STUDENT_ITEM: {
            String studentID = uri.getPathSegments().get(1);
            deleteRows = db.delete("student", "id = ?", new String[] { studentID });
        }
            break;

        default:
            break;
        }

        return deleteRows;
    }

    @Override
    public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs) {
        SQLiteDatabase db = mDBOpenHelper.getReadableDatabase();
        int updateRows = 0;

        switch (mURIMatcher.match(uri)) {
        case STUDENT_DIR: {
            updateRows = db.update("student", values, selection, selectionArgs);
        }
            break;
        case STUDENT_ITEM: {
            String studentID = uri.getPathSegments().get(1);
            updateRows = db.update("student", values, "id = ?", new String[] { studentID });
        }
            break;

        default:
            break;
        }

        return updateRows;
    }

    @Override
    public String getType(Uri uri) {
        switch (mURIMatcher.match(uri)) {
        case BOOK_DIR:
            return "vnd.android.cursor.dir/vnd." + URI_AUTHORITY + ".student";
        case BOOK_ITEM:
            return "vnd.android.cursor.item/vnd." + URI_AUTHORITY + ".student";

        default:
            break;
        }
        return null;
    }

}

經過之前的解釋,實際上provider裏的各個方法的重寫,我們都已經清楚了其原理。
所以,這裏我們主要來看一下UriMatcher的使用:
前面,我們已經說到,關於ContentProvider的實際原理,實際上就是通過解釋器解析Uri,得到資源路徑,然後使用對應的方法進行數據操作。

那麼,究竟是怎麼樣對Uri進行解析的,UriMatcher就是一個關鍵角色。
在我們上面完成的代碼當中,我們注意到:

  • 1.首先,我們在靜態代碼塊中初始化了一個UriMatcher對象。
  • 2.然後,通過調用其實例方法addURI將其添加到其中。
  • 3.我們注意到,該方法接受了三個參數,分別是Uri當中的權限名、路徑名,以及一個唯一標識符。
  • 4.所謂的唯一標識符,可以看做是由傳入的”權限名+路徑名”組成的Uri對象的身份證,他們保持唯一對應。
  • 5.最後,在增刪改查的方法中,就可以通過該”身份證”,進行解析了。我們注意到通過UriMatcher的match()方法,實際上就是在驗證”身份證”信息,match方法會去驗證傳入的uri參數,如果驗證成功,就會返回該唯一身份標識。
  • 6.於是,通過該標識,我們就可以解析得到,該次訪問操作想要訪問的資源目的地是哪裏。從而進行對應的數據庫操作。

可能這樣的解析過程,還是顯得較爲抽象。那麼,我們再次回頭看到哪行熟悉的代碼:

getContentResolver().query(ContactsContract.CommonDataKinds.Phone.CONTENT_URI, null, null, null,
                    null);

現在,我們已經完全能夠明白,這行代碼到底是在做怎麼樣的工作了,分析一下,:

  • 得到解釋器對象,通過Uri中的權限名,知道了訪問的將是哪個應用的內容,於是找到了對應的ContentProvider。
  • 執行的是query方法,於是我們定義的MyContentProvider類的query()方法得到執行。
  • 於是通過switch語句,判斷通過mURIMatcher.match(uri)解析得到的唯一標識,我們知道了訪問的路徑。
  • 訪問的路徑,也知道了,剩下的就是根據其餘的參數與條件,向對應的數據庫表做數據操作而已了。
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章