。。。。

來源:互聯網
上載者:User

源文:http://cw1057.blogspot.tw/2011/08/android-content-provider.html

Android content provider 可以看做是一種以  REST 方式存取的 data wrapper,其主要目的在於Android applications 間的資料分享,也就是說如果資料不需要與其他 app 共用的話,就不必提供 content provider。
Android 提供四種資料存取機制:
Preferences - 以 key/value 的形式儲存 app 的設定值。 Files - app 專屬的檔案。 SQLite - 專屬於建立該資料庫的 package。 Network - 透過網路存取外部資料。 以上四種資料存取機制都是專屬於本身的 app,其他 app 是無法共用的,如果要與其他 app 共用,則可以為該資料建立 content provider。


REST(REpresentational State Transfer)簡述

REST 舉例說明,若要取得所有的蘋果:

content://idv.neil.AppleProvider/apples

若要取得某一顆蘋果:

content://idv.neil.AppleProvider/apples/16

另外還有增修刪的 uri。

Android 內建的 content providers

Android 將通訊錄(Contacts)或系統設定(Settings)等資訊以 SQLite 資料庫的方式儲存,因為只有建立該資料庫的 app 可以透過 SQLite 存取該資料庫,也就是只有同一個 package 的 code 可以使用,所以其他 app 只能透過其開放出來的 content provider 存取。

透視 SQLite

開啟 Android plugin 的 File Explorer,瀏覽至 /data/data/com.android.providers.contacts/databases,可以看到 contacts.db,選取該檔案,按下 File Explorer 右上方的「Pull a file from the device」,就可以將通訊錄檔案抓到電腦裡,再去下載免費的 SQLite GUI,如 Sqliteman,就可以看光光了。

Authority

content provider uri 的格式為 content://authority/path1/path2/...,authority 定義在 AndroidManifest.xml 裡,只有 Android 可以使用縮寫的 authority,如 contacts,其他 app 得用全名。

<provider android:name="apple" android:authorities="idv.neil.AppleProvider"/>

MIME type

透過 content provider 取得的資料形式為類似資料庫由 row 與 column 組成的 cursor 物件。

Android 提供類似 HTTP 的方式讓 content provider 藉由 MIME type 知道回傳物件的型別。

MIME type 由兩部份字串組成,如 text/html,text為主型別,html 為子型別,目前常見的主型別有 application, audio, image, multipart, text, video,子型別若為 vnd. 開頭表示專用的子型別,如 vnd.me-excel,子型別若為 x- 開頭表示為接收雙方私底下的協定。

Anddroid 沿用 MIME type 的概念,進一步套用到因回傳物件數量不同的狀況,若回傳單筆物件,MIME type 格式為 vnd.android.cursor.item/vnd.neil.apple,若回傳多筆物件,則為 vnd.android.cursor.dir/vnd.neil.apple。

Android 運用 MIME type 的概念到 intent 上,也就是 intent 包含的 MIME type 會決定啟動該 intent 的 activity,所以 MIME type 必須加上 domain name 來做 unique。

WordCloud 範例

開發 ContentProvider 的步驟:
定義 ContentProvider 所有資訊 實做 ContentProvider 註冊 ContentProvider WordCloudProvider 只有一個 words table,words 除了 _id 以外只有兩個欄位,word 和 size,目的就是做的像部落格常見的標籤雲,使用頻率愈高的標籤字型愈大。 

輸入 Word 與 Size 按下 Add 就可以新增一筆 words 並重整頁面,單點頁面任一筆 words 就會帶出原資料供修改或刪除。


點下 Update 或 Delete 後就可以回到原畫面,點下 Empty 則會將 table 裡所有資料清空。

定義 ContentProvider 所有資訊
public class WordCloudMetaData {    /** content provider 的 unique key,會定義在 manifest 裡,也是對外開放的 uri */    public static final String AUTHORITY = "idv.neil.provider.WordCloudProvider";    /** 用 db 實做 content provider */    public static final String DB_NAME = "wordcloud.db";    /** 小心。只要版本一改,舊資料會被清空 */    public static final int DB_VERSION = 1;    private WordCloudMetaData() {    };    /** 定義 words 表格的所有資訊,一個 content provider 可以有多個 table */    public static final class WordsTableMetaData implements BaseColumns {        private WordsTableMetaData() {        };        public static final String TABLE_NAME = "words";        /** words 專用的 uri */        public static final Uri CONTENT_URI = Uri.parse("content://"                + AUTHORITY + "/words");        /** 複數與單數的 MIME 型別*/        public static final String CONTENT_DIR_TYPE = "vnd.android.cursor.dir/vnd.neil.words";        public static final String CONTENT_ITEM_TYPE = "vnd.android.cursor.item/vnd.neil.words";        /** 預設的排序 */        public static final String DEFAULT_SORT_ORDER = BaseColumns._ID                + " asc";        /** 定義所有欄位 */        // 不用定義 _id,因為 BaseColumns 已經定義了        public static final String WORDS_WORD = "word";        public static final String WORDS_SIZE = "size";    }}
實做 ContentProvider
public class WordCloudProvider extends ContentProvider {private static final String TAG = "WordCloudProvider";// 因為有時候 db 查詢因為使用 join 導致欄位名稱衝突或因為使用 table alias 讓欄位名稱變複雜// 所以定義一份對應關係,將 user 使用的欄位名稱對應到db使用的欄位名稱// 所有 user 會用到的欄位名稱都要定義,或者說 user 只能使用這個 map 裡定義的欄位名稱private static Map<String, String> projectionMap;static {projectionMap = new HashMap<String, String>();projectionMap.put(WordsTableMetaData._ID, WordsTableMetaData._ID);projectionMap.put(WordsTableMetaData.WORDS_WORD,WordsTableMetaData.WORDS_WORD);projectionMap.put(WordsTableMetaData.WORDS_SIZE,WordsTableMetaData.WORDS_SIZE);}// 因為一個 ContentProvider 可以處理多種 uri,例如取得一堆資料或者只取得一筆資料// Android 提供 UriMatcher 來簡化 uri 的識別工作private static final UriMatcher uriMatcher;private static final int WORDS_DIR_URI_INDICATOR = 1;private static final int WORDS_ITEM_URI_INDICATOR = 2;static {uriMatcher = new UriMatcher(UriMatcher.NO_MATCH);// 當 uri 符合前兩個參數組成的語法時,回傳第三個參數uriMatcher.addURI(WordCloudMetaData.AUTHORITY, "words",WORDS_DIR_URI_INDICATOR);uriMatcher.addURI(WordCloudMetaData.AUTHORITY, "words/#",WORDS_ITEM_URI_INDICATOR);}// db 連線工具private static class DBHelper extends SQLiteOpenHelper {public DBHelper(Context context) {super(context, WordCloudMetaData.DB_NAME, null,WordCloudMetaData.DB_VERSION);}@Overridepublic void onCreate(SQLiteDatabase db) {Log.d(TAG, "create db...");// 建立 table 的語法db.execSQL("create table " + WordsTableMetaData.TABLE_NAME + " ("+ WordsTableMetaData._ID + " integer primary key,"+ WordsTableMetaData.WORDS_WORD + " text not null, "+ WordsTableMetaData.WORDS_SIZE + " integer not null"+ "); ");}@Overridepublic void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {Log.d(TAG, "modify db from version " + oldVersion + " to version "+ newVersion + " ...");// 當 version 改變時,一般會 drop 掉舊 table,然後重建立立 table,所以要小心舊資料會被清掉db.execSQL("drop table if exists " + WordsTableMetaData.TABLE_NAME);this.onCreate(db);}}private DBHelper dbHelper;@Overridepublic boolean onCreate() {Log.d(TAG, "onCreate");// 建立或重建 dbthis.dbHelper = new DBHelper(this.getContext());return true;}@Overridepublic Cursor query(Uri uri, String[] projection, String selection,String[] selectionArgs, String sortOrder) {SQLiteQueryBuilder b = new SQLiteQueryBuilder();// 這兩個一定要b.setTables(WordsTableMetaData.TABLE_NAME);b.setProjectionMap(WordCloudProvider.projectionMap);// 因 uri 不同而導致查詢語法的不同switch (uriMatcher.match(uri)) {case WORDS_DIR_URI_INDICATOR:break;case WORDS_ITEM_URI_INDICATOR:b.appendWhere(WordsTableMetaData._ID + " = "+ uri.getPathSegments().get(1));break;default:throw new IllegalArgumentException("Unknown Uri - " + uri);}// 資料排序方式String orderBy = WordsTableMetaData.DEFAULT_SORT_ORDER;if (!TextUtils.isEmpty(sortOrder)) {orderBy = sortOrder;}// 取得 cursorSQLiteDatabase db = this.dbHelper.getReadableDatabase();Cursor c = b.query(db, projection, selection, selectionArgs, null,null, orderBy);// 因為是取得資料,所以當資料有改變時,需要被通知c.setNotificationUri(this.getContext().getContentResolver(), uri);return c;}@Overridepublic String getType(Uri uri) {switch (uriMatcher.match(uri)) {case WORDS_DIR_URI_INDICATOR:return WordsTableMetaData.CONTENT_DIR_TYPE;case WORDS_ITEM_URI_INDICATOR:return WordsTableMetaData.CONTENT_ITEM_TYPE;default:throw new IllegalArgumentException("Unknown Uri - " + uri);}}@Overridepublic Uri insert(Uri uri, ContentValues values) {// 只能是複數的 uriif (uriMatcher.match(uri) != WORDS_DIR_URI_INDICATOR) {throw new IllegalArgumentException("Unknown Uri - " + uri);}ContentValues cv;if (values == null) {cv = new ContentValues();}else {// 包一層的原因是待會可能會修改cv = new ContentValues(values);}// 檢查必要的欄位if (!cv.containsKey(WordsTableMetaData.WORDS_WORD)) {throw new IllegalArgumentException("The word is required!");}if (!cv.containsKey(WordsTableMetaData.WORDS_SIZE)) {throw new IllegalArgumentException("The size is required!");}// 在這邊也可以修正或補充一些可以有預設值的資料SQLiteDatabase db = this.dbHelper.getWritableDatabase();long rowId = db.insert(WordsTableMetaData.TABLE_NAME, null, cv);if (rowId > 0) {Log.d(TAG, "Insert " + rowId + " data");Uri addedUri = ContentUris.withAppendedId(WordsTableMetaData.CONTENT_URI, rowId);// 資料改變了,發出通知this.getContext().getContentResolver().notifyChange(addedUri, null);return addedUri;}throw new IllegalArgumentException("Failed to insert data to uri - "+ uri);}@Overridepublic int update(Uri uri, ContentValues values, String selection,String[] selectionArgs) {SQLiteDatabase db = this.dbHelper.getWritableDatabase();int cnt;switch (uriMatcher.match(uri)) {case WORDS_DIR_URI_INDICATOR:// 更新多筆資料cnt = db.update(WordsTableMetaData.TABLE_NAME, values, selection,selectionArgs);break;case WORDS_ITEM_URI_INDICATOR:// 更新單筆資料String rowId = uri.getPathSegments().get(1);cnt = db.update(WordsTableMetaData.TABLE_NAME,values,WordsTableMetaData._ID+ " = "+ rowId+ (TextUtils.isEmpty(selection) ? "" : " and ("+ selection + ") "), selectionArgs);break;default:throw new IllegalArgumentException("Unknown Uri - " + uri);}// 資料改變了,發出通知this.getContext().getContentResolver().notifyChange(uri, null);return cnt;}@Overridepublic int delete(Uri uri, String selection, String[] selectionArgs) {SQLiteDatabase db = this.dbHelper.getWritableDatabase();int cnt;switch (uriMatcher.match(uri)) {case WORDS_DIR_URI_INDICATOR:Log.d(TAG, "Delete all data...");// 如果 selection 是空的,可是會將整個 table 清空,這邊就是用來作為清空 table 的功能cnt = db.delete(WordsTableMetaData.TABLE_NAME, selection,selectionArgs);break;case WORDS_ITEM_URI_INDICATOR:// 刪除單筆資料String rowId = uri.getPathSegments().get(1);Log.d(TAG, "Delete data [" + rowId + "]...");cnt = db.delete(WordsTableMetaData.TABLE_NAME,WordsTableMetaData._ID+ " = "+ rowId+ (TextUtils.isEmpty(selection) ? "" : " and ("+ selection + ") "), selectionArgs);break;default:throw new IllegalArgumentException("Unknown Uri - " + uri);}// 資料改變了,發出通知this.getContext().getContentResolver().notifyChange(uri, null);return cnt;}}
註冊 ContentProvider
<application android:icon="@drawable/icon" android:label="@string/app_name">    <activity android:name=".WordCloudActivity">        <intent-filter>            <action android:name="android.intent.action.MAIN" />            <category android:name="android.intent.category.LAUNCHER" />        </intent-filter>    </activity>    <provider android:name=".WordCloudProvider"        android:authorities="idv.neil.provider.WordCloudProvider" /></application>
怎麼用 ContentProvider。
public class WordCloudActivity extends Activity {    private static final String TAG = "WordCloudActivity";    @Override    public void onCreate(Bundle savedInstanceState) {        Log.d(TAG, "onCreate");        super.onCreate(savedInstanceState);        setContentView(R.layout.wordcloud);        this.loadWords();    }    public void onclick(View v) {        Log.d(TAG, "onclick...");        TextView idTV = (TextView) this.findViewById(R.id.idValue);        EditText wordET = (EditText) this.findViewById(R.id.wordValue);        EditText sizeET = (EditText) this.findViewById(R.id.sizeValue);        Words w;        switch (v.getId()) {        case R.id.addBtn:            Log.d(TAG, "add word...");            w = new Words();            w.setWord(wordET.getText().toString());            w.setSize(Integer.parseInt(sizeET.getText().toString()));            WordsHelper.addWords(this.getContentResolver(), w);            this.resetForm();            this.showBtns(true);            break;        case R.id.updateBtn:            Log.d(TAG, "update word...");            w = new Words();            w.setId(Integer.parseInt(idTV.getText().toString()));            w.setWord(wordET.getText().toString());            w.setSize(Integer.parseInt(sizeET.getText().toString()));            WordsHelper.updateWords(this.getContentResolver(), w);            this.resetForm();            this.showBtns(true);            break;        case R.id.deleteBtn:            Log.d(TAG, "delete word...");            WordsHelper.deleteWords(this.getContentResolver(),                    Integer.parseInt(idTV.getText().toString()));            this.resetForm();            this.showBtns(true);            break;        case R.id.emptyBtn:            Log.d(TAG, "delete all words...");            WordsHelper.deleteAllWords(this.getContentResolver());            break;        }        // 重整頁面        this.loadWords();    }    private void loadWords() {        Log.d(TAG, "loadWords");        List<Words> wlist = WordsHelper.listWords(this);        LinearLayout tvs = (LinearLayout) this.findViewById(R.id.tvs);        // 清空 tvs        tvs.removeAllViews();        for (Words w : wlist) {            // 將 TextView 加入頁面裡            tvs.addView(this.createWordTextView(w), new LayoutParams(                    LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT));        }    }    private TextView createWordTextView(Words w) {        Log.d(TAG, "createWordTextView");        TextView tv = new TextView(this);        tv.setText(w.getWord());        tv.setTextSize(w.getSize());        tv.setId(w.getId());        tv.setClickable(true);        tv.setOnClickListener(new OnC

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.