在Android中一共提供了5種資料存放區方式,分別為:
(1)Files:通過FileInputStream和FileOutputStream對檔案進行操作。具體使用方法可以參閱博文《Android學習筆記34:使用檔案儲存體資料》。
(2)Shared Preferences:常用來儲存索引值對形式的資料,對系統配置資訊進行儲存。具體使用方法可以參閱博文《Android學習筆記35:使用Shared Preferences方式儲存資料》。
(3)Content Providers:資料共用,用於應用程式之間資料的訪問。
(4)SQLite:Android內建的輕量級關係型資料庫,支援SQL語言,用來儲存大量的資料,並且能夠對資料進行使用、更新、維護等操作。具體使用方法可以參閱博文《Android學習筆記36:使用SQLite方式儲存資料》。
(5)Network:通過網路來儲存和擷取資料。
本篇博文介紹第三種方式,通過Content Providers實現應用程式之間的資料共用。
1.Content Providers簡介
在Android系統中,不存在一個公用的資料存放區區供所有的應用程式訪問,也就是說資料在各個應用程式中是私人的。那麼,如何在一個應用程式中訪問另一個應用程式中的資料,實現應用程式之間的資料共用呢?
當然,你可以通過《Android學習筆記34:使用檔案儲存體資料》一文中講到的設定openFileOutput()方法中的第二個參數mode為Context.MODE_WORLD_READABLE和Context.MODE_WORLD_WRITEABLE,讓別的應用程式可以讀寫該應用程式中的檔案。但是,使用這種方式的弊端也是顯而易見的,不僅需要知道該檔案的儲存路徑,而且會將該檔案內容完全的暴露出去,對於內容提供者和內容訪問者來說都是不方便和不安全的。
為此,Android系統提供了Content Providers,用以方便安全的實現應用程式間的資料共用。
1.1ContentResolver
所有的Content Providers都會實現一些共同的介面,包括資料的查詢、添加、更改和刪除。在應用程式中,我們可以通過使用getContentResolver()方法來取得一個ContentResolver對象,然後就可以通過這個ContentResolver對象來操作你需要的Content Provider了。ContentResolver類提供的用來操作Content Provider的方法主要有insert()、delete()、update()和query()。
通常,對於開發人員而言,並不需要同Content Provider對象直接打交道。系統運行時,會將所有的ContentProvider對象執行個體化,對於每一種類型的ContentProvider只有一個執行個體。這個執行個體可以與在不同的程式或進程中的多個ContentResolver對象進行通訊。而這些進程間的互動則是由ContentResolver和ContentProvider類進行處理的。
對於Content Providers而言,最重要的就是資料存放區結構和URI。
1.2資料存放區結構
Content Providers將其儲存的資料以資料表的形式提供給訪問者,在資料表中,每一行為一條記錄,每一列為具有特定類型和意義的資料。比如,連絡人的Content Provider資料存放區結構1所示。
圖1 Content Provider資料存放區結構樣本
可以看出,每條記錄都有一個_ID欄位用來唯一的標識該記錄,類似於資料庫中的主鍵。
1.3URI
每一個Content Provider都對外提供一個能夠唯一標識自己資料集的公開URI,如果一個Content Provider管理多個資料集,則需要為每一個資料集都分配一個獨立的URI。
Android規定,所有Content Provider的URI都必須以“content://”開頭。通常,URI由3部分組成:“content://”、資料的路徑、標識ID(可選)。
比如,以下是系統提供的一些URI:
(1)content://media/internal/images
(2)content://contacts/people/2
(3)content://contacts/people
其中,(1)將返回裝置上儲存的所有圖片;(2)將返回連絡人資訊中ID為5的連絡人記錄;(3)將返回裝置上所有的連絡人資訊。
每個ContentResolver對象都將URI作為其第一個參數,URI決定了ContentResolver將與哪一個Content Provider對話。
2.擷取Content Provider內容
Android系統為一些常見的資料類型(如音頻、視頻、映像、通訊錄連絡人等)內建了一系列的Content Provider。以下就以通訊錄連絡人為例,講講如何擷取Content Provider內容。
首先,我們需要在模擬器中運行“連絡人”應用程式程式,並在其中新增連絡人...。2所示。
圖2 新增連絡人...
2所示,我在“連絡人”應用程式程式中添加了兩個連絡人:李明和王磊。
然後,我們需要建立一個自己的工程,該工程的主要功能就是得到持有連絡人資訊的Content Provider中的資料。這裡,我在布局檔案中定義了一個TextView控制項,用來將獲得的連絡人資料顯示出來,運行後的效果3所示。
圖3 擷取Content Provider內容
由圖3可以看出,我們自己建立的應用程式確實從“連絡人”應用程式程式中獲得了資料(ID和Name欄位),當然,如果你需要,你可以擷取圖1中的更多的欄位資訊。
下面的代碼給出了實現這一功能的一種方案。
1 /* 2 * Function : 擷取連絡人清單資訊 3 * Author : 部落格園-依舊淡然 4 */ 5 public String getResult() { 6 7 String result = ""; 8 Uri uri = Uri.parse("content://contacts/people"); //連絡人Content Provider的URI 9 String[] columns = {People._ID, People.NAME}; //連絡人的ID和Name10 11 ContentResolver contentResolver = getContentResolver(); //擷取ContentResolver對象12 Cursor cursor = contentResolver.query(uri, columns, null, null, null); //查詢Content Provider13 int peopleId = cursor.getColumnIndex(People._ID); //獲得ID欄位的列索引14 int peopleName = cursor.getColumnIndex(People.NAME); //獲得Name欄位的列索引15 16 //遍曆Cursor對象,提取資料17 for(cursor.moveToFirst(); (!cursor.isAfterLast()); cursor.moveToNext()) {18 result = result + cursor.getString(peopleId) + "\t\t";19 result = result + cursor.getString(peopleName) + "\t\n";20 }21 cursor.close();22 return result;23 }
通過以上的代碼可以看出,要擷取Content Provider內容,我們需要知道Content Provider的URI以及Content Provider的資料存放區形式(欄位名稱和欄位類型)。然後,我們便可以通過使用ContentResolver對象的query()方法對Content Provider進行查詢了,查詢的結果需要使用CursorObject Storage Service(有關Cursor的介紹可以參閱《Android學習筆記36:使用SQLite方式儲存資料》一文)。最後遍曆Cursor對象,取出各個欄位的資訊即可。
因為該應用程式需要訪問連絡人資訊,所以還需要在AndroidManifest.xml檔案中加入相應的許可權許可,具體如下:
<uses-permission android:name="android.permission.READ_CONTACTS"/>
至此,我們便完成了擷取連絡人Content Provider內容的功能。
3.提供Content Provider內容
上面介紹了如何從別的應用程式中擷取Content Provider內容,那麼如何在自己的應用程式中提供Content Provider內容供別的應用程式訪問呢?
一般來說,讓自己的資料被別的應用程式訪問有兩種方式:建立自己的Content Provider(即繼承自Content Provider的子類),或者是將自己的資料加入到已有的Content Provider中去。將自己的資料加入到已有的Content Provider中去有一定的局限性,因為要保證自己的資料和現有的Content Provider資料類型相同,並且具有該Content Provider的寫入許可權。
下面將說說如何建立一個自己的Content Provider,大致可以分為3個步驟。
3.1建立資料的儲存系統
很顯然,要將自己應用程式中的資料共用給他人,肯定需要建立自己的資料存放區系統。當然了,選擇什麼樣的儲存系統(檔案儲存體系統、SQLite資料庫等)完全由開發人員決定。
在《Android學習筆記36:使用SQLite方式儲存資料》一文中,我們搭建了一個簡單的SQLite資料庫系統。在該資料庫中,我們建立了一張具有3個欄位(studentId、studentName、studentAge)的表,用來儲存學生資訊。
這裡,我們就以該工程為例,講講如何將該工程中的學生資訊通過Content Provider方式共用出去。
3.2擴充Content Provider類
在該工程中,我們需要建立了一個繼承自ContentProvider的類。用來將要共用的資料進行封裝並以ContentResolver對象和Cursor對象能夠訪問的形式對外展示。這裡,我將這個類命名為了“StudentContentProvider”。
在ContentProvider類中提供了6個抽象方法,分別為:
(1)public abstract Cursor query (Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder);
(2)public abstract Uri insert (Uri uri, ContentValues values);
(3)public abstract int update (Uri uri, ContentValues values, String selection, String[] selectionArgs);
(4)public abstract int delete (Uri uri, String selection, String[] selectionArgs);
(5)public abstract String getType (Uri uri);
(6)public abstract boolean onCreate ();
其中,query()方法用於將查詢到的資料以Cursor對象的形式返回;insert()方法用於向Content Provider中插入新資料記錄,該方法中的第二個參數ContentValues對象表示資料記錄的列名和列值的映射;update()方法用於更新Content Provider中的已存在的資料記錄;delete()方法用於從Content Provider中刪除資料記錄;getType()方法用於返回Content Provider中資料的(MIME)類型;onCreate()方法當Content Provider啟動時被調用。
以上的6個方法將會在ContentResolver對象中被調用,所以很好的實現這些抽象方法就會為ContentResolver提供一個完善的外部介面。
當然了,你可以根據自己應用程式的需要,有選擇的實現上述6個方法,比如,你可以只實現query()方法,這樣別的應用程式就自能對你提供的Content Provider進行查詢操作,而無法對你提供的Content Provider進行添加、刪除等操作,從而保證了資料的安全性。
如下的代碼實現了query()方法。
1 /*2 * Function : 查詢方法3 * Author : 部落格園-依舊淡然4 */5 public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder) {6 db = mySQLiteOpenHelper.getWritableDatabase();7 Cursor cursor = db.query("tab_student", projection, selection, selectionArgs, null, null, sortOrder);8 return cursor;9 }
可以看出,查詢的核心其實還是調用SQLiteDatabase類提供的query()方法,將查詢到的結果儲存在Cursor對象中,最後直接將Cursor對象返回即可。
此外,在繼承自ContentProvider類的“StudentContentProvider”中,我們還需要做一件很重要的事,那就是指定Content Provider的URI。該URI必須是唯一的,不能和系統的URI相同,更不能與其他應用程式提供的Content Provider的URI相同。
這裡我定義了Content Provider的URI為“content://com.example.sqlite.studentProvider/student”。
3.3聲明Content Provider的許可權
建立好的Content Provider必須在應用程式的AndroidManifest.xml檔案中進行聲明,否則,該Content Provider對於Android系統是不可見的。
具體的聲明方式如下:
1 <!-- 聲明內容提供者 -->2 <provider
android:name="com.example.android_datastorage_sqlite.provider.StudentContentProvider"3 android:authorities="com.example.sqlite.studentProvider" >4 </provider>
其中,<provider></provider>標籤位於<application></application>標籤下。android:name屬性用於指明StudentContentProvider的全稱類名,android:authorities屬性唯一的標識了一個Content Provider。
至此,我們便在該工程中建立了自己的Content Provider,並提供了query()方法供別的應用程式查詢該工程中的SQLite資料表。
3.4驗證
在應用程式Android_DataStorage_SQLite的SQLite資料表中,我們添加了3條記錄,4所示。
圖4 SQLite資料表資訊
建立一個應用程式Android_DataStorage_ContentProviders,用來擷取自訂的Content Provider內容。擷取自訂的Content Provider內容的方法,和前面講的擷取連絡人應用程式的Content Provider內容的方法類似。使用自訂的Content Provider的URI,並查詢資料表中相應的欄位即可。可以看到查詢到的資訊5所示。
圖5 查詢自訂的Content Provider內容
可以看出,在應用程式Android_DataStorage_ContentProviders中確實訪問到了應用程式Android_DataStorage_SQLite中的資料表資訊,通過Content Provider方式實現了資料在應用程式之間的共用。