chapter 12 資料分享 - hscc homehscc.cs.nctu.edu.tw/~lincyu/android/chapter12.pdf · 12.2...
TRANSCRIPT
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