前言
Content Provider為存儲數據和獲取數據提供了統一的接口,它可以完成在不同應用程序下的數據共享,而在上一篇文章中Android開發歷程_16(SQLite的使用) 講到的SQLite只能在同一個程序中共享數據。另外android為一些常見的數據,比如說音頻,視頻,圖片,通訊錄等提供了Content Provider,這樣我們就可以很方便的對這些類型的數據操作了。使用ContentProvider的好處是開發人員不需要考慮數據內部是怎么存儲的,比如說如果我們想利用ContenProvider來存數據,只需告訴insert函數該ContentProvider的uri和想存入的數據(包括列名和數值),查詢時也是一樣,只需輸入Uri和查詢的表,列名和查詢條件,至於ContentProvider里面是怎么進行這些操作的我們不需要知道。
實驗基礎
在了解本實驗的內容,需要用到下面這幾個跟ContentProvider有關的類。
UriMatcher:
要了解UriMatcher,首先需要了解android中的Uri表示方法,眾所周知,Uri為通用資源標識符,它代表的是要操作的數據,Android中的每一種資源(比如文本,圖像,視頻等)都可以用Uri來表示。Android中的Uri由以下三部分組成:”content://”(即authory),數據的路徑,資源標識ID(可選),其中如果存在ID,則表示某一個具體的資源,如果不存在ID,則表示路徑下的整體。因此addUri()函數的3個參數也是對應上面的那3個。
網上有一篇文章將UriMatcher比較詳細,可以參考:http://blog.csdn.net/feng88724/article/details/6331396
UriMatcher的匹配過程分為3步,初始化UriMatcher;注冊需要用的Uri;與已經注冊的Uri進行匹配。
ContentResolver :
當使用ContentProvider在不同的應用程序中共享數據時,其數據的暴露方式是采取類似數據庫中表的方法。而ContentResolver 是恰好是采用類似數據庫的方法來從ContentProvider中存取數據的,它是通過Uri來查詢ContentProvider中提供的數據,查詢時,還需知道目的數據庫的名稱,數據段的數據類型,或者說資源的ID。
SQLiteQueryBuilder:
SQLiteQueryBuilder是一個用來生產SQL查詢語句的輔助類,可以方便的去訪問SQLiteDatabase. 在構造SQL查詢語句時,它同樣也需要指定表名,指定列名,指定where條件等。
實驗過程
本文通過一個實例來實現一個ContentProvider,其實一般情況下我們是不需要自己來實現的,而是使用andorid內置的ContentProvider,但是自己實現一個以后,對它的原理能更深刻的認識,以后使用內置的就得心應手了。這是mars老師的話,本人火候不夠,暫時還沒深刻的體會。Mars老師將實現ContentProvider的步驟總結為如下:
程序中需要4個java文件,下面就分別來介紹實現這些類需注意的事項:
FirstProviderMetaData類:
因為在繼承類FirstContentProvider得到的子類中要用到很多常量,所以這里我們新建了一個類專門用來存儲這些常量的,該類這里取名為FirstProviderMetaData,里面存着authorty名,數據庫名,數據庫版本號,表名,字表名,子表Uri,子表ContentProvider數據類型等等。其中字表是繼承BaseColumns類的,而BaseColumns類中已經有_ID和_COUNT這2列了。
DatabaseHelper類:
與android中使用SQLite類似,這里同樣需要一個繼承SQLiteOpenHelper的子類,子類名為DatabaseHelper,我們在子類的回調函數onCreate()中建立了一個表,表名和表中的列名都是引用FirstProviderMetaData類中定義好了的常量的。
FirstContentProvider類:
新建一個類,名為FirstContentProvider,繼承類ContentProvider這個類,且必須重寫父類的下面5個方法,否則會報錯。這5個方法分別為onCreate(), getType(), insert(), update(), delete().
onCreate()為回調函數,是指當ContentProvider創建的時候調用,本程序在該函數中使用DatabaseHelper新建了一個SQLite數據庫。
在getType()方法完成的功能是根據傳入的Uri,返回該Uri所表示的數據類型。函數內部是使用的UriMatcher來匹配該函數所傳進來的參數,來得到其數據類型。
insert()函數給指定的數據庫表中插入一個指定的值,插入完成后必然會生成一個新的記錄,然后利用該記錄和表的Uri重新生成一個新的Uri,這個Uri就代表了插入成功的那條記錄的Uri,該函數返回的也是這個Uri。
MainActivity類:
在MainActivity中,主要是有2個按鈕,分為為它們綁定了監聽器,來完成插入和查詢操作。
當單擊插入按鈕時,在監聽器函數中會首先得到一個ContentResolver,然后當在執行ContentResolver的insert方法時會自動調用ContentProvider的insert方法,因為ContentResolver的insert方法中的第一個參數就為某個ContentProvider的Uri。
AndroidManifest.xml:
ContentProvider的使用需要在AndroidManifest.xml中進行注冊,在activity標簽外加入如下聲明即可:
<provider android:name="com.example.cptest.FirstContentProvider" android:authorities="com.example.cptest.FirstContentProvider" />
實驗主要部分代碼及注釋:
MainActivity.java:
package com.example.cptest; //import com.example.cptest.FirstProviderMetaData; import com.example.cptest.FirstProviderMetaData.UserTableMetaData; import android.app.Activity; import android.content.ContentValues; import android.database.Cursor; import android.net.Uri; import android.os.Bundle; import android.view.Menu; import android.view.View; import android.view.View.OnClickListener; import android.widget.Button; public class MainActivity extends Activity { private Button insert = null; private Button query = null; @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); insert = (Button)findViewById(R.id.insert); insert.setOnClickListener(new InsertOnClickListener()); query = (Button)findViewById(R.id.query); query.setOnClickListener(new QueryOnClickListener()); System.out.println(getContentResolver().getType(FirstProviderMetaData.UserTableMetaData.CONTENT_URI)); } //往子表中插入一條記錄 public class InsertOnClickListener implements OnClickListener{ public void onClick(View arg0) { // TODO Auto-generated method stub ContentValues values = new ContentValues(); values.put(FirstProviderMetaData.UserTableMetaData.USER_NAME, "tornadomeet"); //實際上使用的是ContentResolver的insert方法 //該insert中有2個參數,第一個為代表了ContentProvider的Uri,第二個參數為要插入的值。此處的insert函數 //一執行,則自動調用ContentProvider的insert方法。 Uri uri = getContentResolver().insert(FirstProviderMetaData.UserTableMetaData.CONTENT_URI, values); System.out.println("uri--->" +uri.toString()); } } //查詢也是采用的ContentResolver中的query方法。 public class QueryOnClickListener implements OnClickListener{ public void onClick(View v) { // TODO Auto-generated method stub Cursor c = getContentResolver().query(FirstProviderMetaData.UserTableMetaData.CONTENT_URI, null, null, null, null); while(c.moveToNext()) System.out.println(c.getString(c.getColumnIndex(UserTableMetaData.USER_NAME))); } } @Override public boolean onCreateOptionsMenu(Menu menu) { getMenuInflater().inflate(R.menu.activity_main, menu); return true; } }
DatabaseHelper:
package com.example.cptest; import android.content.Context; import android.database.sqlite.SQLiteDatabase; import android.database.sqlite.SQLiteOpenHelper; import android.database.sqlite.SQLiteDatabase.CursorFactory; public class DatabaseHelper extends SQLiteOpenHelper { private static final int VERSON = 1;//默認的數據庫版本 //繼承SQLiteOpenHelper類的類必須有自己的構造函數 //該構造函數4個參數,直接調用父類的構造函數。其中第一個參數為該類本身;第二個參數為數據庫的名字; //第3個參數是用來設置游標對象的,這里一般設置為null;參數四是數據庫的版本號。 public DatabaseHelper(Context context, String name, CursorFactory factory, int verson){ super(context, name, factory, verson); } //該構造函數有3個參數,因為它把上面函數的第3個參數固定為null了 public DatabaseHelper(Context context, String name, int verson){ this(context, name, null, verson); } //該構造函數只有2個參數,在上面函數 的基礎山將版本號固定了 public DatabaseHelper(Context context, String name){ this(context, name, VERSON); } //該函數在數據庫第一次被建立時調用 @Override public void onCreate(SQLiteDatabase arg0) { // TODO Auto-generated method stub System.out.println("create a database"); //execSQL()為執行參數里面的SQL語句,因此參數中的語句需要符合SQL語法,這里是創建一個表 //arg0.execSQL("create table user1(id int, name varchar(20))");下面的語句格式是與該句類似 // arg0.execSQL("create table" + FirstProviderMetaData.USERS_TABLE_NAME // + "(" + FirstProviderMetaData.UserTableMetaData._ID // + " INTEGER PRIMARY KEY AUTOINCREMENT," + //ID類型為自增長的整型 // FirstProviderMetaData.UserTableMetaData.USER_NAME + " varchar(20));" // ); // arg0.execSQL("create table user1(id int, name varchar(20))"); arg0.execSQL("create table" + FirstProviderMetaData.USERS_TABLE_NAME + "(" + FirstProviderMetaData.UserTableMetaData._ID + " INTEGER PRIMARY KEY AUTOINCREMENT, " +FirstProviderMetaData.UserTableMetaData.USER_NAME + " varchar(20))"); System.out.println("create a database ok"); } @Override public void onUpgrade(SQLiteDatabase arg0, int arg1, int arg2) { // TODO Auto-generated method stub System.out.println("update a database"); } }
FirstProviderMetaData.java:
package com.example.cptest; import android.net.Uri; import android.provider.BaseColumns; public class FirstProviderMetaData { //這里的AUTHORTY為包的全名+ContentProvider子類的全名 public static final String AUTHORTY = "com.example.cptest.FirstContentProvider"; //數據庫的名稱 public static final String DATABASE_NAME = "FisrtProvider.db"; //數據庫的版本號 public static final int DATABASE_VERSION = 1; //數據庫中的表名 public static final String USERS_TABLE_NAME = "users"; //表中的字表 public static final class UserTableMetaData implements BaseColumns{ //子表名 public static final String TABLE_NAME = "users"; //CONTENT_URI為常量Uri; parse是將文本轉換成Uri public static final Uri CONTENT_URI = Uri.parse("content://" + AUTHORTY + "/users"); //返回ContentProvider中表的數據類型 public static final String CONTENT_TYPE = "vnd.android.cursor.dir/vnd.firstprovider.user"; //返回ContentProvider表中item的數據類型 public static final String CONTENT_TYPE_ITEM = "vnd.android.cursor.item/vnd.firstprovider.user"; //子表列名 public static final String USER_NAME = "name"; //表中記錄的默認排序算法,這里是降序排列 public static final String DEFAULT_SORT_ORDER = "_id desc"; } }
FirstContentProvider.java:
package com.example.cptest; import java.util.HashMap; import android.content.ContentProvider; import android.content.ContentUris; import android.content.ContentValues; import android.content.UriMatcher; import android.database.Cursor; import android.database.SQLException; import android.database.sqlite.SQLiteDatabase; import android.database.sqlite.SQLiteQueryBuilder; import android.net.Uri; import android.text.TextUtils; import com.example.cptest.FirstProviderMetaData.UserTableMetaData; public class FirstContentProvider extends ContentProvider { //定義一個UriMatcher類對象,用來匹配Uri的。 public static final UriMatcher uriMatcher; //組時的ID public static final int INCOMING_USER_COLLECTION = 1; //單個時的ID public static final int INCOMING_USER_SIGNAL = 2; private DatabaseHelper dh;//定義一個DatabaseHelper對象 static{ uriMatcher = new UriMatcher(UriMatcher.NO_MATCH);//UriMatcher.NO_MATCH表示不匹配任何路徑的返回碼 uriMatcher.addURI(FirstProviderMetaData.AUTHORTY, "users", INCOMING_USER_COLLECTION); uriMatcher.addURI(FirstProviderMetaData.AUTHORTY, "users/#", INCOMING_USER_SIGNAL);//后面加了#表示為單個 } public static HashMap<String, String> userProjectionMap;//新建一個HashMap,后面執行插入操作時有用 static { userProjectionMap = new HashMap<String, String>(); //這里可以直接調用另外一個類的public變量,這里put里面的2個參數一樣, //是因為這里是給數據庫表中的列取別名,因此取的是一樣的名字 userProjectionMap.put(UserTableMetaData._ID, UserTableMetaData._ID); userProjectionMap.put(UserTableMetaData.USER_NAME, UserTableMetaData.USER_NAME); } //得到ContentProvider的數據類型,返回的參數Uri所代表的數據類型 @Override public String getType(Uri arg0) { // TODO Auto-generated method stub System.out.println("getType"); switch(uriMatcher.match(arg0)){ //matcher滿足Uri的前2項(即協議+路徑)為第1種情況時,switch語句的值為Uri的第3項,此處為INCOMING_USER_COLLECTION case INCOMING_USER_COLLECTION: return UserTableMetaData.CONTENT_TYPE; case INCOMING_USER_SIGNAL://同上 return UserTableMetaData.CONTENT_TYPE_ITEM; default: throw new IllegalArgumentException("Unknown URI" + arg0);//throw是處理異常的,java中的語法 } } @Override public int delete(Uri uri, String selection, String[] selectionArgs) { // TODO Auto-generated method stub System.out.println("delete"); return 0; } @Override public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs) { // TODO Auto-generated method stub System.out.println("update"); return 0; } @Override public Uri insert(Uri uri, ContentValues values) { // TODO Auto-generated method stub System.out.println("insert"); // dh = new DatabaseHelper(getContext(), FirstProviderMetaData.DATABASE_NAME); SQLiteDatabase db = dh.getWritableDatabase(); long rowId = db.insert(UserTableMetaData.TABLE_NAME, null, values); // System.out.println("insert OK"); // System.out.println("" + rowId); if(rowId > 0){ //發出通知給監聽器,說明數據已經改變 //ContentUris為工具類 Uri insertedUserUri = ContentUris.withAppendedId(UserTableMetaData.CONTENT_URI, rowId); getContext().getContentResolver().notifyChange(insertedUserUri, null); return insertedUserUri; } throw new SQLException("Failed to insert row into" + uri); } //回調函數,在ContentProvider創建的時候調用 @Override public boolean onCreate() { // TODO Auto-generated method stub System.out.println("onCreate"); dh = new DatabaseHelper(getContext(), FirstProviderMetaData.DATABASE_NAME);//創建1個DatabaseHelper對象 return true; } @Override public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder) { // TODO Auto-generated method stub System.out.println("query"); SQLiteQueryBuilder qb = new SQLiteQueryBuilder(); switch(uriMatcher.match(uri)){ case INCOMING_USER_COLLECTION: qb.setTables(UserTableMetaData.TABLE_NAME);//設置表的名稱 qb.setProjectionMap(userProjectionMap);//其中userProjectionMap為上面建立好了的hashmap break; case INCOMING_USER_SIGNAL: qb.setTables(UserTableMetaData.TABLE_NAME);//設置表的名稱 qb.setProjectionMap(userProjectionMap);//其中userProjectionMap為上面建立好了的hashmap //uri.getPathSegments()得到Path部分,即把uri的協議+authory部分去掉,把剩下的部分分段獲取,這里取第 //一部分 qb.appendWhere(UserTableMetaData._ID + "=" +uri.getPathSegments().get(1));//設置where條件 break; } //排序 String orderBy; if(TextUtils.isEmpty(sortOrder)){ orderBy = UserTableMetaData.DEFAULT_SORT_ORDER;//傳入的排序參數為空的時候采用默認的排序 } else{ orderBy = sortOrder;//不為空時用指定的排序方法進行排序 } SQLiteDatabase db = dh.getWritableDatabase(); //采用傳入的參數進行查詢 Cursor c = qb.query(db, projection, selection, selectionArgs, null, null, sortOrder); //發出通知 c.setNotificationUri(getContext().getContentResolver(), uri); return c; } }
總結:
本次實驗是mars老師自己實現的一個ContentProvider類,這有利於對它建立的流程有個整體的了解,不過經過本次實驗,還是對代碼中很多地方不是特別理解。
參考資料:
http://blog.csdn.net/feng88724/article/details/6331396
http://www.mars-droid.com/bbs/forum.php