Android學習筆記(四八):提供自己的Content Provider

來源:互聯網
上載者:User

在上一次的學習中,採用了原生的內容提供者Contact,Contact有多層映射關係,比較複雜,並非作為小例子的好選擇,在本次學習中,我們將學習如何建立Content Provider,並通過Uri進行增刪改查。如果應用的資料只需自己使用,並不需要content provider,相反避免這樣做,可直接存取資料;但是若希望資料可以被其他應用訪問,建立content provider就是常規手段

再談Content Provider的Uri

在上一次學習中,我們談到了Uri的格式。現在已content://com.wei.android.myproject/card/pin/17為例子,具體解構。

1、scheme部分:content://,表明這是個content的Uri,而不是一個http://的網路Uri;

2、authority(com.wei.andriod.myproject)緊跟scheme部分,是唯一的標識,通常我們用類的namespace的命名方法,表明歸屬。

3、在authority(com.wei.android.myproject)後面,在instance identifier(17)之前,用於表明路徑,表示內容的類型,即例子中的“card/pin”,這部分可以為空白。

4、最後是instance identifier,是整數,表明內容中資料的具體位置。一個內容的Uri如果沒有instance identifier,則表示內容的資料集(collection)。

內容中Uri指向的資料,有對應的MIME type pair,一個用於collection,一個用於instance。對於collection,MIME類型應是vnd.X.cursor.dir/Y,X是我們公司、組織或者項目的名字,而Y是用“.”分割。例如vnd.wei.cursor.dir/com.wei.android.myproject.card.pin。對於instance而言,MIME類型應為vnd.X.cursor.item/Y,其中X和Y與collection的一致。

建立內容提供者:建立自己的Provider類

通過繼承ContentProvider提供我們自己的子類。我們將採用SQLite的方式,在Android學習筆記(四一):SQLite的使用中學習過,很多代碼都可以重用。我們將通過Content Provider的方式封裝SQLite的資料提供者,允許其他應用通過Uri訪問資料。

步驟1:建立自己的Provider類,需要重寫6個抽象方法onCreate( ), query( ), insert( ), update( ), delete( )和返回MIME類型的getType()。由於一個內容提供者,允許多個用戶端同時訪問,用戶端A修改資料(insert,update,delete),需通知其他查詢的用戶端(query),以便其他用戶端可以進行資料同步。見例子藍色部分。對於SQLite,有些模板可以套用,見綠色部分。

步驟2:設定Uri以及Provider的屬性,見例子暗紅字部分。

//步驟1:通過繼承ContentProvider建立自己的Provider類,需要重寫6個抽象方法,以實現建立,讀取、更新和刪除
public class GravityProvider extends ContentProvider{
    private String DB_NAME = "gravity.db";
    public static final String TABLE="constants";
    private SQLiteDatabase db = null; 
    /* 步驟2:定義Uri以及Provider的屬性*/  
    /* 步驟2.1 通過繼承BaseColumns的靜態公用類倆描述,申明Uri,以及基本屬性以便用戶端是以哦那個。如果資訊儲存採用SQLite的方式,這些屬性通才就是表格的column,這樣我們可以很方便地在update,insert中傳遞資訊。 屬性的類型沒有特定要求,只要cursor能夠支援。在BaseColummns中已經含有ID=_id */ 
   public static final class Constants implements BaseColumns {
        /* 步驟2.2 定義Uri,提供content provider的每個collection的Uri,為了避免Uri的衝突,可以是使用Java類的namespace */  
        public static final Uri CONTENT_URI = Uri.parse("content://" + AUTHORITY + "/gravity");
        /* 步驟2.3 定義一些基本屬性,對於SQLite是column的名字 */
        public static final String TITLE="title";
        public static final String VALUE="value";
    }
    public static final String AUTHORITY = "com.wei.android.learning.provider"; 

   //【通用模板類】建立Uri的樹狀階層,以便於檢查Uri 的匹配情況:isCollectUri( )
    private static final UriMatcher sURIMatcher = new UriMatcher(UriMatcher.NO_MATCH);
    private static final int CONSTANTS = 1;
    private static final int CONSTANTS_ID = 2;
    static {
       //對於具體的instance,實際是在collection後面加上/<num>,例如……/1,用#表示數字
        sURIMatcher.addURI(AUTHORITY, "gravity", CONSTANTS);
        sURIMatcher.addURI(AUTHORITY, "gravity/#", CONSTANTS_ID); 
    }

    // 【通用模板類】設定query映射的map,對於SQLite而言,通過query傳遞的column名字,影射到具體資料庫column名字,這種方式對重新命名column很有協助。 
    private static HashMap<String,String> CONSTANTS_LIST_PROJECT = new HashMap<String, String>();
    static{
        CONSTANTS_LIST_PROJECT.put(GravityProvider.Constants._ID, GravityProvider.Constants._ID);
        CONSTANTS_LIST_PROJECT.put(GravityProvider.Constants.TITLE, GravityProvider.Constants.TITLE);
        CONSTANTS_LIST_PROJECT.put(GravityProvider.Constants.VALUE, GravityProvider.Constants.VALUE);
    }

    /* 步驟1.1:onCreate( ) 是ContentProvider的入口,用於初始化處理。如果資料是檔案格式,將檢測檔案路徑以及檔案是否存在,如果不存在則建立之,對於SQLite,則建立SQLite的關聯。 如果更新了ContentProvider中的資料結構,應檢測新舊資料結構是否相容。onCreate() 是Content Provider普通開始的入口,或者是釋放更新的入口 */
   public boolean onCreate() {  
        db=(new GravityDbHelper(getContext())).getWritableDatabase();
        return (db == null) ? false : true;
    }  

    /* 步驟1.2 : 處理查詢,即對應處理用戶端的manageQuery查詢,並返回Cursor。這些參數特別適合採用SQLite方式儲存資料,我們可以忽略某些參數,例如只根據Uri返回資料,這種情況,應在doc對方法的描述中進行說明。*/
    public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder)

       //對於SQLite,使用SQLiteQueryBuilder將參數放入一個SQL語句中,如下:
       SQLiteQueryBuilder qb = new SQLiteQueryBuilder();
        qb.setTables(getTableName()); 

        if(isCollectionUri(uri)){
           //如果是Collection的Uri,本例為content://com.wei.android.learning/constants,設定返回的的內容,用HashMap的結構,映射到Content Provider的封裝中。
            qb.setProjectionMap(CONSTANTS_LIST_PROJECT);
        }else{
           //如果是instance的Id,本例為content://com.wei.android.learning/constants/#,即需要查詢特定_ID的使用者,因此增加Where的條件說明 
            qb.appendWhere(GravityProvider.Constants._ID + "=" + uri.getPathSegments().get(1));
        }
       //擷取排序方式,或給出預設的排序方式
        String orderBy = null;
        if( TextUtils.isEmpty(sortOrder)){
            orderBy= getDefaultSortOrder();
        }else{
            orderBy  = sortOrder;
        }
       //尋找資料庫,擷取遊標
        Cursor c=qb.query(db, projection, selection, selectionArgs, null, null, orderBy);
       //【註冊通知】註冊該uri對應的資料發生變化時,向用戶端發出通知。內容提供者可以有多個應用(用戶端)來訪問,提供同步資料的通知。 
       c.setNotificationUri(getContext().getContentResolver(), uri); 
 
        return c;
    } 

   /* 步驟1.3 insert()處理增加資料,其中參數1:表示collection的Uri,參數2:是instance的內容,並返回一個instance的Uri */
    public Uri insert(Uri uri, ContentValues values)
        long rowId;
        ContentValues cv = null;
        if(values != null){
            cv = new ContentValues(values);
        }else{
            cv  = new ContentValues(); //可能允許空置的插入,所以null的情況不一定異常
        }
        //檢測Uri是否有效:若是instance的Uri,不是collection Uri,即不能增加Uri,拋出異常 
        if(!isCollectionUri(uri)){  
            throw new IllegalArgumentException("Unknown Uri " + uri);
        }
       //檢測提供的資料是否完畢:必有的Columns是否存在,否則拋出異常
        for(String colName : getRequiredColumns()){
            if( !cv.containsKey(colName) ){
                throw new IllegalArgumentException("Miss column : " + colName);
            }
        }
        //對空缺的非必要列進行預設值填入
        popularDefaultValues(cv);
        rowId = db.insert(getTableName(), getNullColumnHack(), cv);
        if(rowId > 0){
            Uri instanceUri = ContentUris.withAppendedId(getContentUri(), rowId);
            //【通知註冊用戶端資料出現變更】出現Uri指向資料變更,通過notifyChange( )通知所有註冊者發生了更新。本例第二參數為null,如果非null,表明若變更由該oberver引起,則無需通知該observer 
            getContext().getContentResolver().notifyChange(instanceUri, null);
            return  instanceUri;
        }
        return null;
    }  

    /* 步驟1.4: 用於修改一個或者多個instance的值,通常只用於SQLite,否者一般忽略。*/
    public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs) { 
        int count = 0;
        if(isCollectionUri(uri)){ 
            count = db.update(getTableName(), values, selection, selectionArgs);
        }else{ //如果是instance,在Where處制定ID
            String segment = uri.getPathSegments().get(1);
            count = db.update(getTableName(), values, 
                    getIdColumnName() + "=" + segment + (TextUtils.isEmpty(selection) ? "" : " AND (" + selection + ")" ),
                    selectionArgs);
        }
        getContext().getContentResolver().notifyChange(uri, null);//【通知註冊用戶端資料出現變更】

        return count;
    }  

    /* 步驟1.5:處理刪除 */
    public int delete(Uri uri, String selection, String[] selectionArgs) { 
        int count = 0;
        if(isCollectionUri(uri)){
            count = db.delete(getTableName(), selection, selectionArgs);
        }else{
            String segment = uri.getPathSegments().get(1);
            count = db.delete(getTableName(),
                    getIdColumnName() + "=" +segment +(TextUtils.isEmpty(selection) ? "" : " AND (" + selection + ")"),
                    selectionArgs);
        }
        getContext().getContentResolver().notifyChange(uri, null); //【通知註冊用戶端資料出現變更】
        return count;
    }

    /* 步驟1.6 返回collection或者instance的MIME Type */
    public String getType(Uri uri) { 
        if( isCollectionUri(uri)){
            return getCollectionType();
        }else{
            return getInstanceType();
        }
    }

    //【通用模板類】對於SQLite的內容提供者的處理,已經有套路,下面的均可作為模板化處理
    private String[] getRequiredColumns(){
        return (new String[]{Constants.TITLE});
    }

    private void popularDefaultValues(ContentValues values){
        if(!values.containsKey(Constants.VALUE))
            values.put(Constants.VALUE, 0.0f);
    }
 
    private Uri getContentUri(){
        return Constants.CONTENT_URI;
    }

    private String getIdColumnName(){
        return Constants._ID;
    }

    private String getTableName(){
        return GravityProvider.TABLE;
    }

    private boolean isCollectionUri(Uri uri){
        return sURIMatcher.match(uri) == CONSTANTS;
    }

    private String getDefaultSortOrder(){
        return GravityProvider.Constants.TITLE;
    }

    private String getNullColumnHack(){
        return GravityProvider.Constants.TITLE;
    }

    //返回collection的MIME類型
    private String getCollectionType(){
        return "vnd.wei.cursor.dir/com.wei.android.learning.gravity";
    }

    //返回instance的MIME類型
    private String getInstanceType(){
        return "vnd.wei.cursor.item/com.wei.android.learning.gravity";
    } 

  /* 下面這部分是建立SQLite,我們直接採用Andriod學習筆記(四一)的例子,代碼不在重複*/
    private class GravityDbHelper extends SQLiteOpenHelper {
        …… ……
    }

}

步驟三:在AndroidManifest.xml中聲明provider,允許在該xml中定義的Application使用該content Provider的資料提供者。如下。如果有多個authortity,並全部列出。

<provider android:name=".GravityProvider" android:authorities="com.wei.android.learning.provider" />

相關連結:我的Andriod開發相關文章

相關文章

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在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.