2012年5月9日 星期三

Android 開發筆記 - 使用 Content Provider


來源:Calendar Provider data model


由於安全設計關係,預設 app 操作使用的 sqlite db 都是在 sandbox 環境,換句話說,也就是僅 app 自身可獨立存取得而已,其他 app 是不能存取的。因此,如果想要提供自身的 sqlite db 資料給其他程式使用,除了寫 Android Service 架構來達到 IPC 或寫 Socket 程式溝通資料外,還可以採用 Content Provider 的架構。當然,在此不討論 root 後突破權限管理的機制 XD 舉例來說,Android 系統內建 Calendar app,而使用 Android 會綁定一個 Gmail 帳號,那有沒辦法取得此帳號的行事曆呢?此時可以透過 Content Provider 來進行操作。


首先,想要讀寫 Calendar 資料,那在 app 的 AndroidManifest.xml 權限上,需要加上 android.permission.READ_CALENDAR 和 android.permission.WRITE_CALENDAR,這樣的設計是告知使用者此程式將會存取 calendar 資料。


接著 app 中,可以透過 ContentResolver 物件,向 Calendar app 進行存取,如 content://CONTENT_URI/DBTable,此例為 Calendars.CONTENT_URI 等於 content://calendar/calendars,但建議使用 Calendars.CONTENT_URI 變數來存取,因為在各個 Android 版本中,可能因為軟體升級或架構改變而儲存位置變更,因此透過 Calendars.CONTENT_URI 存取最為恰當(有些版本為 content://com.android.calendar/calendars)。


片段程式碼:


// Projection array. Creating indices for this array instead of doing
// dynamic lookups improves performance.
public static final String[] EVENT_PROJECTION = new String[] {
        Calendars._ID, // 0
        Calendars.ACCOUNT_NAME, // 1
        Calendars.CALENDAR_DISPLAY_NAME, // 2
        Calendars.OWNER_ACCOUNT // 3
};

// The indices for the projection array above.
private static final int PROJECTION_ID_INDEX = 0;
private static final int PROJECTION_ACCOUNT_NAME_INDEX = 1;
private static final int PROJECTION_DISPLAY_NAME_INDEX = 2;
private static final int PROJECTION_OWNER_ACCOUNT_INDEX = 3;


// Run query
Cursor cur = null;
ContentResolver cr = getContentResolver();
Uri uri = Calendars.CONTENT_URI;
String selection = "((" + Calendars.ACCOUNT_NAME + " = ?) AND ("
        + Calendars.ACCOUNT_TYPE + " = ?) AND ("
        + Calendars.OWNER_ACCOUNT + " = ?))";
String[] selectionArgs = new String[] {"sampleuser@gmail.com","com.google","sampleuser@gmail.com"};
// Submit the query and get a Cursor object back.
cur = cr.query(uri, EVENT_PROJECTION, selection, selectionArgs, null);


此例 SQL 語法 = "SELECT Calendars._ID, Calendars.ACCOUNT_NAME, Calendars.CALENDAR_DISPLAY_NAME, Calendars.OWNER_ACCOUNT FROM Calendars WHERE Calendars.ACCOUNT_NAME = sampleuser@gmail.com AND Calendars.ACCOUNT_TYPE = com.google AND Calendars.OWNER_ACCOUNT = sampleuser@gmail.com";。


剩下的就跟操作 SQLite 一樣:


// Use the cursor to step through the returned records
while (cur.moveToNext()) {
        long calID = 0;
        String displayName = null;
        String accountName = null;
        String ownerName = null;

        // Get the field values
        calID = cur.getLong(PROJECTION_ID_INDEX);
        displayName = cur.getString(PROJECTION_DISPLAY_NAME_INDEX);
        accountName = cur.getString(PROJECTION_ACCOUNT_NAME_INDEX);
        ownerName = cur.getString(PROJECTION_OWNER_ACCOUNT_INDEX);

        // Do something with the values...


        ...
}


此外,如果要更新某一筆資料,也一樣透過 URI 來進行操作,僅需在最後加上 Record ID,如 content://CONTENT_URI/DBTable/RecordID,在進行資料的操作:


long calID = 2;
ContentValues values = new ContentValues();
// The new display name for the calendar
values.put(Calendars.CALENDAR_DISPLAY_NAME, "Trevor's Calendar");
Uri updateUri = ContentUris.withAppendedId(Calendars.CONTENT_URI, calID);
int rows = getContentResolver().update(updateUri, values, null, null);


此例 SQL 約略為 "UPDATE Calendars SET Calendars.CALENDAR_DISPLAY_NAME = Trevor's Calendar WHERE Calendars._ID = 2";。


當然也免不了新增資料:


long calID = 3;
long startMillis = 0;
long endMillis = 0;
Calendar beginTime = Calendar.getInstance();
beginTime.set(2012, 9, 14, 7, 30);
startMillis = beginTime.getTimeInMillis();
Calendar endTime = Calendar.getInstance();
endTime.set(2012, 9, 14, 8, 45);
endMillis = endTime.getTimeInMillis();
...
ContentResolver cr = getContentResolver();
ContentValues values = new ContentValues();
values.put(Events.DTSTART, startMillis);
values.put(Events.DTEND, endMillis);
values.put(Events.TITLE, "Jazzercise");
values.put(Events.DESCRIPTION, "Group workout");
values.put(Events.CALENDAR_ID, calID);
values.put(Events.EVENT_TIMEZONE, "America/Los_Angeles");
Uri uri = cr.insert(Events.CONTENT_URI, values);


// get the event ID that is the last element in the Uri
long eventID = Long.parseLong(uri.getLastPathSegment());
//
// ... do something with event ID
//
//


以上在 Android Developers 官網都有非常詳細的描述,在此僅簡易筆記一下,更多的 Content Provider 請參閱 package android.provider,裡頭可以看到系統內建有哪些資料可以存取使用囉!


沒有留言:

張貼留言