Chapter 12 資料分享 作者 : 林致孙 上一章中我們學到了資料的儲存方式, 也瞭解資料會依應用程式的 Package name 儲存於適當的資料夾內, 然而 Android 並沒有一個共同的儲存空間讓所有應用程式存取, 因此若要讓其它的應用程式使用我的資料, 就必須使用一些技巧, 本章主要在學習資料分享的方式 在先前的章節中已經數次提到, 一個 Android 應用程式是由四個構成要素所組成的 :Activity, Broadcast Receiver, Service 與 Content Provider 其中 Content Provider 是用來分享資料用的 [1], 然而筆者認為一個一般應用程式的開發者會需要寫一個 Content Provider 讓其它開發者使用他的資料的情況應屬少數, 頂多是開發者需要在自己的應用程式間共享資料, 因此本章只使用一個很簡單的程式範例, 來說明如何建立一個 Content Provider 事實上, 一個一般應用程式的開發者比較常使用的反而是去存取 Android 所提供的 Content Providers, 例如存取系統的通訊錄就是最常見的一個範例, 本章也會使用一個簡單的範例來說明如何存取系統的通訊錄 12.1 偏好設定與檔案的分享 首先先解說如何讀取別的應用程式的偏好設定, 首先讀者可引進光碟中 \ 範例程式 \Chapter12\Horoscope 這個專案, 應用程式的功能是讓使用者輸入生日並得知星座, 程式會將使用者的生日偏好利用 SharedPreferences 儲存起來, 這個專案和 \ 範例程式 \Chapter11\Horoscope 這個專案的差別很小, 只是將 getsharedpreferences 的第二個參數從 MODE_PRIVATE 換成 MODE_WORLD_READABLE, 這些常數是定義於 Context 類別 [2], 其中 MODE_WORLD_READABLE 是允許其它應用程式能讀取建立的偏好設定檔 接下來我們的重心將放在其它應用程式如何讀取這個偏好設定檔, 讀者可引進光碟中 \ 範例程式 \Chapter12\GetPrefBirthday 這個專案, 這個專案裡面只有一個名為 GetPref 的 Activity, 這個 Activity 的程式碼也相當簡短, 如下所示 : 1 public class GetPref extends Activity { 2 @Override 3 public void oncreate(bundle savedinstancestate) { 4 super.oncreate(savedinstancestate); 5 setcontentview(r.layout.main); 6
7 TextView tv = (TextView)findViewById(R.id.tv_birthday); 8 9 Context otherapp = null; 10 11 try { 12 otherapp = createpackagecontext("lincyu.horoscope", 0); 13 } catch (Exception e) { 14 Log.d("LINCYU", e.tostring()); 15 finish(); 16 } 17 18 SharedPreferences pref = 19 otherapp.getsharedpreferences("pref_birth", 20 MODE_WORLD_READABLE); 21 22 String pref_month = pref.getstring("pref_month", "1"); 23 Integer intday = pref.getint("pref_day", 1); 24 25 tv.settext(pref_month+" 月 "+intday+" 日 "); 26 } 27 } 讀者不難發現這個程式唯一沒學過的就只有第 12 行由 Context 類別所提供的 createpackagecontext 方法, 第一個參數是 \ 範例程式 \Chapter12\Horoscope 這個專案的 Package name, 第二個參數是設定旗標值, 此處填 0 即可 createpackagecontext 方法會回傳一個 Context 物件, 程式將之命名為 otherapp, 我們只要呼叫 otherapp 的 getsharedpreferences 方法就可以取得 Horoscope 這個應用程式的偏好設定值了 測試這個程式前記得要先安裝並執行 Horoscope 這個應用程式 ( 名為 星座計算 (V2) ), 可嘗試輸入一個生日, 如下圖左側所示, 接著再執行 GetPrefBirthday 這個應用程式, 如下圖右側所示, 可發現程式成功地讀取別的應用程式的偏好設定
存取別的應用程式的檔案也是同樣的方法, 先利用 createpackagecontext 取得別 的應用程式的 Context 物件, 再呼叫 Context 類別的 openfileinput 或 openfileoutput 方法即可 讀者可參閱 \ 範例程式 \Chapter12\ShareFile 專案與 \ 範例程式 \Chapter12\ReadSharedFile 專案 ShareFile 這個 Activity 包含一個 EditText 介面元件, 使用者所輸入的文字會被儲存在一個名為 share.txt 的檔案裡, 在第十一章我們已學過寫檔的方法, 首先必須呼叫 Context 類別的 openfileoutput 方法取得一個 FileOutputStream 物件, 其中 openfileoutput 的第二個參數在第十一章是設定成 MODE_PRIVATE, 在 ShareFile 這個應用程式中, 程式是設成 MODE_WORLD_READABLE, 讓別的應用程式可以讀取 share.txt ReadSharedFile 這個 Activity 一樣使用 createpackagecontext 方法取得 ShareFile 的 Context 物件後, 再呼叫 openfileinput 將檔案讀取出來並顯示於 TextView 介面元件上 讀者可先執行 ShareFile, 執行範例如下圖左側所示, 接著再執行 ReadSharedFile, 驗證其結果
最後要提醒讀者的是, 如果想讓別的應用程式也可改寫檔案,mode 參數可填入 MODE_WORLD_READABLE+MODE_WORLD_WRITETABLE 12.2 資料庫內容的分享與 Content Provider 簡介 在這一節中我們將學習如何分享資料庫裡的資料, 我們利用 Content Provider 來做資料庫資料的分享,Content Provider 可以讓一個應用程式分享它的資料給其它應用程式, 由於 Content Provider 是以資料庫的型式來呈現其資料, 因此使用 Content Provider 來分享資料庫裡的資料是蠻合適的 筆者修改了第十一章的 Notepad2 這個便條簿程式, 這個程式是使用資料庫的型式儲存便條資料, 因此可以拿來示範 Content Provider 的使用, 請讀者引進 \ 範例程式 \Chapter12\Notepad2CP 這個專案, 其和 Notepad2 的差別只有兩處 : 新增一個 NoteProvider.java,NoteProvider 繼承了 ContentProvider 類別, 是一個 Content Provider 修改 AndroidManifest.xml, 幫新增的 Content Provider 做註冊 首先我們先討論 NoteProvider.java, 完整的程式碼如下所示 : 1 public class NoteProvider extends ContentProvider { 2 3 Context mctx; 4 SQLiteDatabase db; 5 6 @Override
7 public boolean oncreate() { 8 mctx = getcontext(); 9 DatabaseHelper dbhelper; 10 dbhelper = new DatabaseHelper(mCtx); 11 db = dbhelper.getwritabledatabase(); 12 return true; 13 } 14 15 @Override 16 public Cursor query(uri uri, String[] projection, String selection, 17 String[] selectionargs, String sortorder) { 18 19 Cursor c = db.rawquery("select * from " + 20 NoteDB.DATABASE_TABLE + ";", null); 21 22 return c; 23 } 24 25 @Override 26 public int update (Uri uri, ContentValues values, String selection, 27 String[] selectionargs) { 28 return -1; 29 } 30 31 @Override 32 public Uri insert (Uri uri, ContentValues values) { 33 return null; 34 } 35 36 @Override 37 public int delete (Uri uri, String selection, String[] 38 selectionargs){ 39 return -1; 40 } 41 42 @Override 43 public String gettype(uri uri) { 44 return "";
45 } 46 } 首先在第 1 行我們可發現 NoteProvider 類別繼承了 ContentProvider 類別 [3], 總共有六個抽象方法實作 :oncreate, query, update, insert, delete, gettype oncreate 方法是在 Provider 被啟動時呼叫, 程式在 oncreate 方法了產生一個 SQLiteDatabase 物件, 讓我們可以進行資料庫的操作 要提醒讀者注意的是, DatabaseHelper 類別是原本 Notepad2 專案就已經寫好的, 其會開啟 notes.db 這個資料庫, 並建立一個名為 notetable 的表格 ( 如果此表格尚未被建立 ) 接著我們看覆寫的 query 方法, 內容相當簡單, 呼叫 SQLiteDatabase 類別的 rawquery 方法查詢表格, 並取得一個 Cursor 物件, 由於 query 方法需要回傳一 個 Cursor 物件, 程式便將取得的 Cursor 物件回傳 Content Provider 設計完後, 如同 Android 應用程式的其它構成要素, 我們需要在應用程式描述檔做註冊的動作, 方法是於 <application> 標籤內利用 <provider> 標籤來註冊, 內容如下 : <provider android:name=".noteprovider" android:authorities="lincyu.noteprovider"> </provider> 其中 android:name 的值填入 Content Provider 的類別名稱即可, 而 android:authorities 是用來辮識 Provider 的, 只要不和系統提供的 Providers 一樣就可以了 至此我們已經建立一個 Content Provider, 利用 Notepad2CP 所建立的便條可以供其 它應用程式來查詢, 接下來我們便要來說明其它應用程式如何查詢 Notepad2CP 所產生的便條 請讀者引進 \ 範例程式 \Chapter12\Notepad2CR 這個專案, 這個專案只有一個 Activity, 其功能是利用 Content Resolver 讀取 NoteProvider 提供的資料, 並將第一筆便條顯示於畫面上 Notepad2CR.java 的內容如下所示 : 1 public class Notepad2CR extends Activity { 2 @Override 3 public void oncreate(bundle savedinstancestate) { 4 super.oncreate(savedinstancestate); 5 setcontentview(r.layout.main); 6 7 Cursor c = getcontentresolver().query(
8 Uri.parse("content://lincyu.noteprovider"), 9 null, null, null, null); 10 11 TextView tv1 = (TextView)findViewById(R.id.tv_title); 12 TextView tv2 = (TextView)findViewById(R.id.tv_body); 13 14 if (c.getcount()!= 0) { 15 c.movetofirst(); 16 String title = c.getstring(c.getcolumnindex("title")); 17 String body = c.getstring(c.getcolumnindex("body")); 18 19 if (title == null body == null) return; 20 21 tv1.settext(title); 22 tv2.settext(body); 23 } 24 } 25 } 要讀取 Content Provider 的內容必須利用 ContentResolver 物件, 程式在第 7 行先呼叫 Context 類別的 getcontentresolver 方法取得一個 ContentResolver 物件, ContentResolver 類別提供了許多跟 ContentProvider 類別一樣的方法 (Methods)[4], 讓我們可以對 Content Provider 所提供的內容做處理, 其中一個方法是 query, 在程式中我們只使用到第一個參數, 第一個參數是填入一個 Uri 物件, 程式呼叫 Uri 類別的 parse 方法取得一個 Uri 物件,parse 方法的參數填入我們所要存取的 Content Provider query 方法會回傳一個 Cursor 物件, 若沒有任何的便條, 程式便不做任何的處理, 若有便條存在, 會將 Cursor 移到第一筆便條, 並讀取 title 欄位與 body 欄位的資料, 將之顯示於適當的 TextView 上 測試程式時, 必須先安裝 Notepad2CP( 應用程式的名稱為 便條簿 ), 並可嘗試 新增一個便條, 接著執行 Notepad2CR, 便可驗證程式是否正常運作 由於筆者認為一個一般應用程式的開發者會需要寫一個 Content Provider 讓其它開發者使用他的資料的情況應屬少數, 頂多是開發者需要在自己的應用程式間共享資料, 因此只使用一個很簡單的程式範例, 來說明如何建立一個 Content Provider, 若要對 Content Provider 有一個完整的瞭解, 請讀者自行閱讀 Android
開發者網站上的文件 [1] 12.3 取得系統通訊錄資料 對於一般應用程式的開發者而言, 比較會常用到的, 是使用系統提供的 Content Providers, 例如讀取通訊錄的內容, 或讀取系統的設定值 本節中我們將學習如 何讀取系統的通訊錄內容 首先要提醒讀者的是, 在 Android SDK 2.0 後, 讀取通訊錄所使用的類別有一些變化, 主要是使用 ContactsContract 類別 [5], 之前的版本是使用 Contacts 類別 [6] 筆者的程式是使用 ContactsContract 類別, 因此在測試程式時, 請記得在適當的模擬器 (Platform 必須是 2.0 以上 ) 上執行 請讀者引進光碟中 \ 範例程式 \Chapter12\ContactEmail 這個專案, 程式的功能很簡單, 讀取通訊錄裡的聯絡人姓名及電子郵件, 並將資料利用列表介面元件 (ListView) 顯示出來, 專案中只有 ContactEmail 這一個 Activity, 內容如下所示 : 1 public class ContactEmail extends ListActivity { 2 @Override 3 public void oncreate(bundle savedinstancestate) { 4 super.oncreate(savedinstancestate); 5 setcontentview(r.layout.main); 6 7 Cursor c = getcontentresolver().query( 8 Email.CONTENT_URI, null, null, null, null); 9 10 startmanagingcursor(c); 11 12 ListAdapter adapter = new SimpleCursorAdapter(this, 13 R.layout.my_list_item_2, c, 14 new String[] { Data.DISPLAY_NAME, Email.DATA}, 15 new int[] { R.id.tv_name, R.id.tv_email }); 16 setlistadapter(adapter); 17 } 18 } 首先先看第 7 行, 要存取系統的 Content Provider, 也是先用 getcontentresolver 方法取得一個 ContentResolver 物件, 再呼叫 query 方法取得所需的資料, 前一節 才提過 query 方法需要一個 Uri 物件當參數, 常數 Email.CONTENT_URI 是一個
Uri 物件, 定義在 ContactsContract.CommonDataKinds.Email 類別 [7], 讀者可將這個類別視為一個表格, 其中表示 Email.DATA 欄位代表電子郵件的資料, 文件中有提到,ContactsContract.Data 類別的所有欄位也可以使用, 其中 Data.DISPLAY_NAME 欄位是用來顯示完整姓名的, 其它的欄位意義請讀者自行閱讀相關說明文件 程式於第 10 行呼叫 Activity 類別的 startmanagingcursor 方法來幫忙做 Cursor 的 管理, 例如在必要時重新做查詢, 詳細的說明請參考 Activity 類別的說明文件 在第八章時我們有提過 ListView 需要一個接合器 (Adapter) 將 資料 和 介面元件 做接合, 之前已經學過如何利用 ArrayAdapter 產生 ListAdapter 物件, 這一章我們利用 SimpleCursorAdapter 類別來產生 ListAdapter 物件 [9], SimpleCursorAdapter 可將 Cursor 所指的表格中的某一欄的 資料 對應到 介面元件 上, 其建構子需要五個參數 : Context context: 第一個參數是一個 Context 物件, 填入 ContactEmail 的物件實體即可 int layout: 第二個參數是一個版面資源檔, 是列表元件裡每一個 項目 所呈現的版面, 讀者可以打開 /res/layout/my_list_item_2.xml, 裡面使用了一個 LinearLayout 包住了兩個 TextView, 且這兩個 TextView 會以垂直方式排列, 執行結果如下圖所示, 這個執行範例包含了兩個項目 Cursor c: 第三個參數需要一個 Cursor 物件, 填入 query 方法所回傳的 Cursor 物件即可 String[] from: 接合器是接合 資料 與 介面元件, 這個參數是用來指定 資料 的來源, 我們從 Cursor 所指的表格中取出兩欄 : Data.DISPLAY_NAME 與 Email.DATA, 分別代表使用者姓名與電子郵件資
料 int[] to: 這個參數是將 資料 對應到適當的地方, 我們將 Data.DISPLAY_NAME 的資料放入 tv_name 這個 TextView, 將 Email.DATA 的資料放入 tv_email 這個 TextView 至此讀者應已能瞭解所有程式細節, 我們可以做一些測試, 首先先利用模擬器提 供的 Contacts 應用程式新增幾筆聯絡人資料, 如下圖所示, 接著再執行 ContactsEmail 即可驗證是否有正確地讀取通訊錄資料 除了通訊錄,Android 還提供了其它許多的 Content Providers, 讀者可參閱 android.provider 套件的說明文件得知 Android 提供了哪些 Content Providers[10] 12.4 摘要 本章介紹了 Android 上存取其它應用程式的資料的方法, 若想存取別的應用程式的偏好設定與檔案, 可呼叫 createpackagecontext 方法取得別的應用程式的 Context 物件, 再呼叫 getsharedpreferences 方法或 openfileinput 相關方法即可 當然分享偏好設定與檔案的應用程式也必須對檔案的模式 (mode) 做適當的設定 若想分享資料庫裡的資料給其它應用程式, 可建立一個 Content Provider, 若想 使用其它應用程式所提供的 Content Provider 則必須使用 Content Resolver,
Android 提供了許多的 Content Providers 供程式開發者使用 12.5 作業 1. 寫一個新的應用程式, 讓這個應用程式能讀取第十一章的 Notepad1 這個便條 簿的 NoteList.txt 當然 Notepad1 專案內的程式也必須做適當的修改 提示 : 使用 createpackagecontext 方法 2. 寫一個需要使用到資料庫的應用程式, 並替這個應用程式建立一個 Content Provider 3. 修改 \ 範例程式 \Chapter12\ContactEmail 這個專案 將電子郵件換成電話號 碼 提示 : 參閱 ContactsContract.CommonDataKinds.Phone 類別 [11] 12.6 參考資料 [1] Content Providers Android Developers, http://developer.android.com/guide/topics/providers/content-providers.html [2] Context Android Developers, http://developer.android.com/reference/android/content/context.html [3] ContentProvider Android Developers, http://developer.android.com/reference/android/content/contentprovider.html [4] ContentResolver Android Developers, http://developer.android.com/reference/android/content/contentresolver.html [5] ContactsContract Android Developers, http://developer.android.com/reference/android/provider/contactscontract.html [6] Contacts Android Developers, http://developer.android.com/reference/android/provider/contacts.html [7] ContactsContract.CommonDataKinds.Email Android Developers, http://developer.android.com/reference/android/provider/contactscontract.commond atakinds.email.html
[8] ContactsContract.Data Android Developers, http://developer.android.com/reference/android/provider/contactscontract.data.html [9] SimpleCursorAdapter Android Developers, http://developer.android.com/reference/android/widget/simplecursoradapter.html [10] android.provder Android Developers, http://developer.android.com/reference/android/provider/package-summary.html [11] ContactsContract.CommonDataKinds.Phone Android Developers, http://developer.android.com/reference/android/provider/contactscontract.commond atakinds.phone.html