天天看點

Android之自定義ContentProvider詳解

第一個版本  對android中MIME類型的了解

初始MIME類型,是在學習ContentProvider的時候。

      當在建立自己的ContentProvider的時,需要從抽象類ContentProvider中派生出自己的子類,并實作其中5個抽象方法:

  • query(Uri, String[], String, String[], String) which returns data to the caller
  • insert(Uri, ContentValues) which inserts new data into the content provider
  • update(Uri, ContentValues, String, String[]) which updates existing data in the content provider
  • delete(Uri, String, String[]) which deletes data from the content provider
  • getType(Uri) which returns the MIME type of data in the content provider

       至于前四個方法,不是本文想要讨論的重點,就不做備援的闡述了;有意思的是這個方法getType(Uri),根據幫助文檔的解釋,它傳回一個MIME類型。

       首先,先百度了一下MIME類型,根據百度百科的解釋:MIME:全稱Multipurpose Internet Mail Extensions,多功能Internet 郵件擴充服務。它是一種多用途網際郵件擴充協定,在1992年最早應用于電子郵件系統,但後來也應用到浏覽器。MIME類型就是設定某種擴充名的檔案用一種應用程式來打開的方式類型,當該擴充名檔案被通路的時候,浏覽器會自動使用指定應用程式來打開。多用于指定一些用戶端自定義的檔案名,以及一些媒體檔案打開方式。

      看完百度百科的解釋,相信大家和我一樣,仍然不解。結合一個例子,與老師交流之後,我的了解是這樣的:

       在ContentProvider的getType(Uri)方法中,可以顯示的傳回一個MIME類型,該方法傳回一個字元串,可以是任意的字元串,當我們顯示的傳回該MIME類型的時候,相當于通過該方法的驗證,Provider可以識别自身其他方法傳回的Cursor的内容,不需要在進行更多的驗證;如果傳回其他的字元串(非android能夠識别的MIME類型,例如直接傳回目前的包名),則Provider在執行其他方法後,傳回Cursor類型的時候,需要再次進行驗證。

    還是雲裡霧裡的?下面來看一個使用了MIME類型的自定義ContentProvider的例子:

import android.net.Uri;

public class Shopping {

// 定義資料庫的名字

public static final String DATABASE_NAME = "shopping_db";

// 定義資料庫的版本

public static final int DATABASE_VERSION = 1;

// 表的名字

public static final String TABLE_NAME = "t_shopping";

// 定義資料庫的字段

public static final String FIELD_ID = "_id";

public static final String FIELE_NAME = "product_name";

// 定義通路的類型

public static final int ITEM = 1;

public static final int ITEM_ID = 2;

// 定義MIME類型,通路單個記錄

public static final String CONTENT_ITEM_TYPE = "vnd.android.cursor.item/vnd.com.stone.shopping";

// 通路資料集

public static final String CONTENT_ITEM = "vnd.android.cursor.dir/vnd.stone.shopping";

// 定義通路ContentProvider權限

public static final String AUTHORITY = "com.stone.shopping";

// 定義URI

public static final Uri URI = Uri.parse("content://" + AUTHORITY + "/item");

import android.content.Context;

import android.database.sqlite.SQLiteDatabase;

import android.database.sqlite.SQLiteOpenHelper;

import android.database.sqlite.SQLiteDatabase.CursorFactory;

public class MyDbHelper extends SQLiteOpenHelper {

public MyDbHelper(Context context, String name, CursorFactory factory, int version) {

super(context, name, factory, version);

}

@Override

public void onCreate(SQLiteDatabase db) {

String sql = "CREATE TABLE " + Shopping.TABLE_NAME + " ( " + 

Shopping.FIELD_ID + " INTEGER primary key autoincrement, " + " " + Shopping.FIELE_NAME + " TEXT)";

db.execSQL(sql);

public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {

String sql = "DROP TABLE IF EXISTS " + Shopping.TABLE_NAME;

onCreate(db);

}  

import android.content.ContentProvider;

import android.content.ContentUris;

import android.content.ContentValues;

import android.content.UriMatcher;

import android.database.Cursor;

import android.text.TextUtils;

public class MyProvider extends ContentProvider {

private MyDbHelper myDbHelper;

private static final UriMatcher mUriMatcher; // 進行比對的Uri的設定

static {

mUriMatcher = new UriMatcher(UriMatcher.NO_MATCH);

mUriMatcher.addURI(Shopping.AUTHORITY, "item", Shopping.ITEM);

mUriMatcher.addURI(Shopping.AUTHORITY, "item/#", Shopping.ITEM_ID);

public boolean onCreate() {

// 建立資料庫

myDbHelper = new MyDbHelper(getContext(), Shopping.DATABASE_NAME, null, Shopping.DATABASE_VERSION);

return true;

public int delete(Uri uri, String selection, String[] selectionArgs) {

SQLiteDatabase db = myDbHelper.getWritableDatabase();

int count = 0;

switch (mUriMatcher.match(uri)) {

case Shopping.ITEM:

ount = db.delete(Shopping.TABLE_NAME, selection, selectionArgs);

break;

case Shopping.ITEM_ID:

// 通過Uri擷取Id,根據主鍵進行删除

String id = uri.getPathSegments().get(1);

System.out.println(String.valueOf(uri.getPathSegments().size()));

count = db.delete(Shopping.TABLE_NAME,Shopping.FIELD_ID + "=" + id, selectionArgs);

default:

throw new IllegalArgumentException();

// 通知資料發生改變

getContext().getContentResolver().notifyChange(uri, null);

return count;

public Uri insert(Uri uri, ContentValues values) {

long row = 0;

if (mUriMatcher.match(uri) != Shopping.ITEM) {

row = db.insert(Shopping.TABLE_NAME, Shopping.FIELD_ID, values);

if (row > 0) {

Uri noteUri = ContentUris.withAppendedId(Shopping.URI, row);

return noteUri;

return null;

public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder) {

SQLiteDatabase db = myDbHelper.getReadableDatabase();

Cursor cursor = null;

cursor = db.query(Shopping.TABLE_NAME, projection, selection, selectionArgs, null, null, sortOrder);

cursor = db.query(Shopping.TABLE_NAME, projection, Shopping.FIELD_ID + "=" + id + (!TextUtils.isEmpty(selection) ? " AND (" + selection + ')' : ""),

selectionArgs, null, null, sortOrder);

cursor.setNotificationUri(getContext().getContentResolver(), uri);

return cursor;

public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs) {

count = db.update(Shopping.TABLE_NAME, values, selection, selectionArgs);

count = db.update(Shopping.TABLE_NAME, values, Shopping.FIELD_ID + "=" + id + (!TextUtils.isEmpty(selection) ? " AND (" + selection + ')' : ""),

selectionArgs);

public String getType(Uri uri) { // 進行Uri比對完成不同的處理工作

return Shopping.CONTENT_ITEM;

return Shopping.CONTENT_ITEM_TYPE;

    在上面的例子中,首先有一個Shopping類,定義了一系列的常量。包括通路的資料庫的相關資訊和URI的定義,其中最重要的就是下面的兩句,MIME類型的定義:

public static final String CONTENT_ITEM_TYPE = "vnd.android.cursor.item/vnd.stone.shopping";

public static final String CONTENT_ITEM = "vnd.android.cursor.dir/vnd.stone.shopping"; 

    其次是一個MyDbHelper類,繼承自SQLiteOpenHelper類,用于一些資料庫相關操作,這裡就不贅述了。

    最後的MyProvider類使我們的重頭戲,首先我們來看這一段代碼:

   mUriMatcher = new UriMatcher(UriMatcher.NO_MATCH);

   mUriMatcher.addURI(Shopping.AUTHORITY, "item", Shopping.ITEM);

   mUriMatcher.addURI(Shopping.AUTHORITY, "item/#", Shopping.ITEM_ID);

    UriMatcher表示一個Uri的比對器,它會對我們請求的Uri進行比對,而比對的格式就是這裡我們通過addURI()方法添加格式。

    接下來,首先執行的就是getType(Uri)方法,下面來看該方法體中的代碼:

}   

    當請求過來的Uri通過mUriMatcher.match(uri)方法進行比對,根據不同的比對值來傳回不同的MIME類型。下面我們來結合query(Uri, String[], String, String[], String)這個方法來解釋一下:

    在該方法中,傳回一個Cursor遊标對象。而Cursor中是單條的記錄還是一個集合,需要和在getType()方法中傳回的類型保持一緻。當傳回的MIME類型是Shopping.CONTENT_ITEM時,Cursor應該是一個集合;當傳回的MIME類型是Shopping.CONTENT_ITEM_TYPE時,Cursor應該是單條記錄。

    由于在getType()方法裡面,我們顯示的傳回了android平台可以識别的MIME類型,是以在執行query()方法傳回Cursor對象的時候,系統将不需要再進行驗證,進而可以說是節省了系統開銷。

    話已至此,那麼何謂android平台可以識别的MIME類型呢?下面來分析一下MIME類型的結構:

    其實,MIME類型其實就是一個字元串,中間有一個 “/” 來隔開,“/”前面的部分是系統識别的部分,就相當于我們定義一個變量時的變量資料類型,通過這個“資料類型”,系統能夠知道我們所要表示的是個什麼東西。至于 “/” 後面的部分就是我們自已來随便定義的“變量名”了。

    那麼,既然MIME類型就是一個字元串,那麼我們的getType()自然也可以随便傳回一個系統不能識别的字元串啦?沒錯,有些時候我們确實也這樣處理,比如說可以這樣寫:

public String getType(Uri uri) {

return getContext().getPackageName(); 

    這裡,我們把目前上下文的包名傳回了。這樣處理的結果是怎樣的呢?

    簡單的說,系統不能夠識别它了,也就不會做任何處理。仍然以query()方法來說,當執行完方法體(這裡需要注意一下:在這種情況下,即使我們沒有通過傳回MIME類型字元串來進行驗證處理,但是在query()方法中再次對Uri進行了比對并根據不同的Uri類型進行了不同的操作)傳回Cursor對象的時候,這時候系統不能肯定傳回的Cursor對象是否合法,是以需要對其進行驗證,這樣對系統資源算是一個浪費了吧。是以,我們最好還是顯示的傳回一個MIME類型吧,當然要寫正确了,讓我們android平台可以識别。

sourceurl:http://blog.csdn.net/h3g2010/article/details/6093366