ContentProvider

一、ContentProvider的概念   ContentProvider:爲存儲和獲取數據提供統一的接口。可以在不同的應用程序之間共享數據。可支持在多個應用中存儲和讀取數據。這也是跨應用共享數據的唯一方式。在android系統中,沒有一個公共的內存區域,供多個應用共享存儲數據。Android提供了一些主要數據類型的Contentprovider,比如音頻、視頻、圖片和私人通訊錄等。可在android.provider包下面找到一些android提供的Contentprovider。可以獲得這些Contentprovider,查詢它們包含的數據,當然前提是已獲得適當的讀取權限。

如果想公開自己的數據,那麼可有兩種辦法:

1)創建自己的 Content provider ,需要繼承ContentProvider類;

2)如果你的數據和已存在的 Content provider 數據結構一致,可以將數據寫到已存在的 Content provider 中,當然前提是獲取寫該 Content provider 的權限。比如把OA中的成員通訊信息加入到系統的聯繫人 Content provider 中。

1、ContentProvider使用表的形式來組織數據

無論數據的來源是什麼,ContentProvider都會認爲是一種表,然後把數據組織成表格

2、ContentProvider提供的方法

1
2
3
4
5
6
query:查詢
insert:插入
update:更新
delete:刪除
getType:得到數據類型
onCreate:創建數據時調用的回調函數

3、每個ContentProvider都有一個公共的URI,這個URI用於表示這個ContentProvider所提供的數據。Android所提供的ContentProvider都存放在android.provider包當中。

4.數據模型

Contentprovider展示數據類似一個單個數據庫表。其中:

每行有個帶唯一值的數字字段,名爲_ID,可用於對錶中指定記錄的定位; Contentprovider 返回的數據結構,是類似JDBC的ResultSet,在android中,是Cursor對象。

5.URI

每個contentprovider定義一個唯一的公開的URI,用於指定到它的數據集。一個contentprovider可以包含多個數據集(可以看作多張表),這樣,就需要有多個URI與每個數據集對應(可以看作是數據庫)。這些URI要以這樣的格式開頭:

1
content://

表示這個URI指定一個contentprovider如果你想創建自己的contentprovider,最好把自定義的URI設置爲類的常量,這樣簡化別人的調用,並且以後如果更新URI也很容易。android定義了CONTENT_URI常量用於URI,比如:

1
2
android.provider.Contacts.Phones.CONTENT_URI
android.provider.Contacts.Photos.CONTENT_URI

查詢 Content provider

要想使用一個contentprovider,需要以下信息:

1)定義這個 content provider 的URI 返回結果的字段名稱 這些字段的數據類型

2)如果需要查詢contentprovider數據集的特定記錄(行),還需要知道該記錄的ID的值。

構建查詢

查詢就是輸入URI等參數,其中URI是必須的,其他是可選的,如果系統能找到URI對應的contentprovider將返回一個Cursor對象。

可以通過ContentResolver.query()或者Activity.managedQuery()方法。兩者的方法參數完全一樣,查詢過程和返回值也是相同的。區別是,通過Activity.managedQuery()方法,不但獲取到Cursor對象,而且能夠管理Cursor對象的生命週期,比如當Activity暫停(pause)的時候,卸載該Cursor對象,當Activity restart的時候重新查詢。另外,也可以對一個沒有處於Activity管理的Cursor對象做成被Activity管理的,通過調用 Activity.startManaginCursor()方法。

類似這樣:

1
Cursor cur = managedQuery(myPerson, nullnullnullnull);//其中第一個參數myPerson是Uri類型實例。

 如果需要查詢的是指定行的記錄,需要用_ID值,比如ID值爲23,URI將是類似:

1
content://. . . ./23

android提供了方便的方法,讓開發者不需要自己拼接上面這樣的URI,比如類似:

 Uri myPerson = ContentUris.withAppendedId(People.CONTENT_URI, 23);

 或者:

Uri myPerson = Uri.withAppendedPath(People.CONTENT_URI, "23");

二者的區別是一個接收整數類型的ID值,一個接收字符串類型。

6.其他幾個參數:(從這邊可以看出非常類似於數據庫)

1)names,可以爲null,表示取數據集的全部列,或者聲明一個String數組,數組中存放列名稱,比如: People._ID 。一般列名都在該 Content provider 中有常量對應;

2)針對返回結果的過濾器,格式類似於SQL中的WHERE子句,區別是不帶WHERE關鍵字,如果返回null表示不過濾,比如 name=? 

3)前面過濾器的參數,是String數組,是針對前面條件中?佔位符的值;

4)排序參數,類似SQL的ORDER BY字句,不過不需要寫ORDER BY部分,比如 name desc ,如果不排序,可輸入null。

下面實例適用於android 2.0及以上版本,從android通訊錄中得到姓名字段:

 

Cursor cursor = getContentResolver().query(ContactsContract.CommonDataKinds.Phone.CONTENT_URI, null, null, null, null);

 

返回結果爲:

返回值的內容類似上圖,不同的contentprovider會有不同的列和名稱,但是會有兩個相同的列,上面提到過的一個是_ID,用於唯一標識記錄,還有一個_COUNT,用於記錄整個結果集的大小,可以看到上面圖中的_COUNT的值是相同的。

讀取返回的數據

如果在查詢的時候使用到ID,那麼返回的數據只有一條記錄。在其他情況下,一般會有多條記錄。和JDBC的ResultSet類似,需要操作遊標遍歷結果集,在每行,再通過列名獲取到列的值,可以通過getString()、getInt()、getFloat()等方法獲取值。比如類似下面:

 

while (cursor.moveToNext()) {
    builder
            .append(
                    cursor.getString(cursor.getColumnIndex(ContactsContract.CommonDataKinds.Phone.DISPLAY_NAME)))
            .append("-");
}

從這路也可以看出其實是非常相似的!(區別:和JDBC中不同,沒有直接通過列名獲取列值的方法,只能先列名獲取到列的整型索引值,然後再通過該索引值定位獲取列的值。(SQLite數據庫也是如此)

二、編輯數據

可以通過contentprovider實現以下編輯功能:

1)增加新的記錄;

2)在已經存在的記錄中增加新的值;

3)批量更新已經存在的多個記錄;

4)刪除記錄。

所有的編輯功能都是通過ContentResolver的方法實現。一些Contentprovider對權限要求更嚴格一些,需要寫的權限,如果沒有會報錯。

a)增加記錄

要想增加記錄到contentprovider,首先,要在ContentValues對象中設置類似map的鍵值對,在這裏,鍵的值對應contentprovider中的列的名字,鍵值對的值,是對應列希望的類型。然後,調用ContentResolver.insert()方法,傳入這個ContentValues對象,和對應Contentprovider的URI即可。返回值是這個新記錄的URI對象。這樣你可以通過這個URI獲得包含這條記錄的Cursor對象。比如:

ContentValues values = new ContentValues();
values.put(People.NAME, "Abraham Lincoln"); Uri uri = getContentResolver().insert(People.CONTENT_URI, values);//如何通過Uri獲取這條記錄?

b)在原有記錄上增加值

如果記錄已經存在,可在記錄上增加新的值,或者編輯已經存在的值。首先要過去到原來的值對象,然後要清除原有的值,然後像上面增加記錄一樣即可

1
2
3
4
5
6
7
Uri uri=Uri.withAppendedPath(People.CONTENT_URI, "23");//獲取特定的行的uri
Uri phoneUri = Uri.withAppendedPath(uri, People.Phones.CONTENT_DIRECTORY);//獲取特定的數據列
 
values.clear();
values.put(People.Phones.TYPE, People.Phones.TYPE_MOBILE);
values.put(People.Phones.NUMBER, "1233214567");
getContentResolver().insert(phoneUri, values);

(這個例子貌似揭示了聯繫人的聯繫方式也是使用一個表來存儲的,也就是說,表中有表)

c)批量更新值

批量更新一組記錄的值,比如NY改名爲Eew York。可調用ContenResolver.update()方法。

d)刪除記錄

如果是刪除單個記錄,調用ContentResolver.delete()方法,URI參數,指定到具體行即可。

如果是刪除多個記錄,調用ContentResolver.delete()方法,URI參數指定Contentprovider即可,並帶一個類似SQL的WHERE子句條件。這裏和上面類似,不帶WHERE關鍵字。

三、ContentProvider的內部原理

自定義一個ContentProvider,來實現內部原理,步驟:

 

複製代碼
1、定義一個CONTENT_URI常量(裏面的字符串必須是唯一)
Public static final Uri CONTENT_URI = Uri.parse("content://com.WangWeiDa.MyContentprovider");
如果有子表,URI爲:
Public static final Uri CONTENT_URI = Uri.parse("content://com.WangWeiDa.MyContentProvider/users");
2、定義一個類,繼承ContentProvider
Public class MyContentProvider extends ContentProvider
3、實現ContentProvider的所有方法(query、insert、update、delete、getType、onCreate)
4、在AndroidMainfest.xml中申明
複製代碼

創建contentprovider,需要:設置存儲系統。大多數 content provider 使用文件或者SQLite數據庫,不過你可以用任何方式存儲數據。android提供SQLiteoOpenHelper幫助開發者創建和管理SQLiteDatabase。 繼承ContentProvider,提供對數據的訪問。 在manifest文件中聲明content provider 。也就是遵循以下幾步:

複製代碼
1、定義一個CONTENT_URI常量(裏面的字符串必須是唯一)
Public static final Uri CONTENT_URI = Uri.parse("content://com.WangWeiDa.MyContentprovider");
如果有子表,URI爲:
Public static final Uri CONTENT_URI = Uri.parse("content://com.WangWeiDa.MyContentProvider/users");
2、定義一個類,繼承ContentProvider
Public class MyContentProvider extends ContentProvider
3、實現ContentProvider的所有方法(query、insert、update、delete、getType、onCreate)
4、在AndroidMainfest.xml中申明
複製代碼

創建contentprovider,需要:設置存儲系統。大多數 content provider 使用文件或者SQLite數據庫,不過你可以用任何方式存儲數據。android提供SQLiteoOpenHelper幫助開發者創建和管理SQLiteDatabase。 繼承ContentProvider,提供對數據的訪問。 在manifest文件中聲明content provider 。也就是遵循以下幾步:

 

複製代碼
a. 創建一個繼承了ContentProvider父類的類
b. 定義一個名爲CONTENT_URI,並且是public static final的Uri類型的類變量,你必須爲其指定一個唯一的字符串值,最好的方案是以類的全名稱, 如:
public static final Uri CONTENT_URI = Uri.parse( “content://com.google.android.MyContentProvider”);
c. 定義你要返回給客戶端的數據列名。如果你正在使用Android數據庫,必須爲其定義一個叫_id的列,它用來表示每條記錄的唯一性。
d. 創建你的數據存儲系統。大多數Content Provider使用Android文件系統或SQLite數據庫來保持數據,但是你也可以以任何你想要的方式來存儲。
e. 如果你要存儲字節型數據,比如位圖文件等,數據列其實是一個表示實際保存文件的URI字符串,通過它來讀取對應的文件數據。處理這種數據類型的Content Provider需要實現一個名爲_data的字段,_data字段列出了該文件在Android文件系統上的精確路徑。這個字段不僅是供客戶端使用,而且也可以供ContentResolver使用。客戶端可以調用ContentResolver.openOutputStream()方法來處理該URI指向的文件資源;如果是ContentResolver本身的話,由於其持有的權限比客戶端要高,所以它能直接訪問該數據文件。
f. 聲明public static String型的變量,用於指定要從遊標處返回的數據列。
g. 查詢返回一個Cursor類型的對象。所有執行寫操作的方法如insert(), update() 以及delete()都將被監聽。我們可以通過使用ContentResover().notifyChange()方法來通知監聽器關於數據更新的信息。
h. 在AndroidMenifest.xml中使用<provider>標籤來設置Content Provider。
複製代碼

關於notifyChange():

我們在ContentProvider的insert,update,delete等改變之後調用getContext().getContentResolver().notifyChange(uri, null);這樣就通知那些監測databases變化的observer了,而你的observer可以在一個service裏面註冊。

以Downloadmanger爲例子: 
定義ContentObserver,並且在onChange裏做你想做的事情:

 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
/**
     * Receives notifications when the data in the content provider changes
     */ 
    private class DownloadManagerContentObserver extends ContentObserver { 
   
        public DownloadManagerContentObserver() { 
            super(new Handler()); 
        
   
        /**
         * Receives notification when the data in the observed content
         * provider changes.
         */ 
        public void onChange(final boolean selfChange) { 
            if (Constants.LOGVV) { 
                Log.v(Constants.TAG, "Service ContentObserver received notification"); 
            
            updateFromProvider(); 
        
   
    }

 

在DownloadService的onCreate中註冊: 

 

複製代碼
 public void onCreate() {  
       super.onCreate();  
       if (Constants.LOGVV) {  
           Log.v(Constants.TAG, "Service onCreate");  
       }  
  
       mDownloads = Lists.newArrayList();  
  
       mObserver = new DownloadManagerContentObserver();  
       getContentResolver().registerContentObserver(Downloads.CONTENT_URI,  
               true, mObserver); //註冊監聽
.....}
複製代碼

 

解開註冊:

 

複製代碼
 public void onDestroy() {  
       getContentResolver().unregisterContentObserver(mObserver);  
       if (Constants.LOGVV) {  
           Log.v(Constants.TAG, "Service onDestroy");  
       }  
       super.onDestroy();  
   }
複製代碼

 

詳細一點可見:http://hi.baidu.com/lck0502/item/77d6ff3f46e52d617c034b60 

以下還未碰到:

 

i. 如果你要處理的數據類型是一種比較新的類型,你就必須先定義一個新的MIME類型,以供ContentProvider.geType(url)來返回。MIME類型有兩種形式:一種是爲指定的單個記錄的,還有一種是爲多條記錄的。這裏給出一種常用的格式:
  vnd.android.cursor.item/vnd.yourcompanyname.contenttype (單個記錄的MIME類型)
  比如, 一個請求列車信息的URI如content://com.example.transportationprovider/trains/122 可能就會返回typevnd.android.cursor.item/vnd.example.rail這樣一個MIME類型。
  vnd.android.cursor.dir/vnd.yourcompanyname.contenttype (多個記錄的MIME類型)
  比如, 一個請求所有列車信息的URI如content://com.example.transportationprovider/trains 可能就會返回vnd.android.cursor.dir/vnd.example.rail這樣一個MIME 類型。

這裏似乎可以這麼理解:

1)contentprovider本質上還是類似於數據庫SQLite的東西,我們的數據實際上還是使用數據庫和文件來存儲的,而contentprovider的特殊之處是在於它可以將自己存儲的數據提供給外界的APP使用,也就是說,使用contentprovider其實是爲了暴露數據,沒有這樣的需求,使用SQLite即可;

2)contentprovider除了提供一種將應用本身的數據暴露給別的APP的手段,實際上它還扮演着代理的功能,它的接口將實際的數據操作全部封裝了一遍,所有的contentprovider都可以使用相同的接口進行類似的數據操作);

query()方法,返回值是Cursor實例,用於迭代請求的數據。Cursor是一個接口。android爲該接口提供了一些只讀的(和 JDBC的ResultSet不一樣,後者還提供可寫入的可選特性)Cursor實現。比如SQLiteCursor,可迭代SQLite數據庫中的數據。可以通過SQLiteDatabase類的query()方法獲取到該Cursor實例。還有其他的Cursor實現,比如 MatrixCursor,用於數據不是存儲在數據庫的情況下

因爲Contentprovider可能被多個ContentResolver對象在不同的進程和線程中調用,因此實現Contentprovider必須考慮線程安全問題。

作爲良好的習慣,在實現編輯數據的代碼中,要調用ContentResolver.notifyChange()方法,通知那些監聽數據變化的監聽器。

在實現子類的時候,還有一些步驟可以簡化Contentprovider客戶端的使用:

定義public static final Uri常量,名稱爲CONTENT_URI:

 

public static final UriCONTENT_URI = Uri.parse("content://com.example.codelab.transportationprovider");

如果有多個表,它們也是使用相同的CONTENT_URI,只是它們的路徑部分不同:

也就是說紅色框部分是一致的。

定義返回的列名,public static final,列名的值,比如使用SQLite數據庫作爲存儲,對應表的列名。在文檔中要寫出各個列的數據類型,便於使用者讀取。

四、聲明 Content Provider

創建ContentProvider後,需要在manifest文件中聲明,android系統才能知道它,當其他應用需要調用該ContentProvider時才能創建或者調用它。

語法類似:
<provider   android:name="com.easymorse.cp.MyContentProvider"
            android:authorities="com.easymorse.cp.mycp"></provider>

 

1)android:name要寫ContentProvider繼承類的全名。

2)android:authorities要寫和CONTENT_URI常量的B部分(見上面圖)。

注意不要把上圖C和D部分加到authorities中去。authorities是用來識別ContentProvider的,C和D部分實際上是ContentProvider內部使用的。
五、一些常用代碼
1)修改數據

 

private void updateRecord(int recNo, String name) {
     Uri uri = ContentUris.withAppendedId(People.CONTENT_URI, recNo);
    ContentValues values = new ContentValues();
    values.put(People.NAME, name);
     getContentResolver().update(uri, values, null, null);
 }

2)插入數據

複製代碼
private void insertRecords(String name, String phoneNo) {
    ContentValues values = new ContentValues();
    values.put(People.NAME, name);
    Uri uri = getContentResolver().insert(People.CONTENT_URI, values);
    Log.d(”ANDROID”, uri.toString());
    Uri numberUri = Uri.withAppendedPath(uri, People.Phones.CONTENT_DIRECTORY);
    values.clear();
    values.put(Contacts.Phones.TYPE, People.Phones.TYPE_MOBILE);
    values.put(People.NUMBER, phoneNo);
    getContentResolver().insert(numberUri, values);
}
複製代碼

3)刪除數據

private void deleteRecords() {
    Uri uri = People.CONTENT_URI;
    getContentResolver().delete(uri, null, null);
}

你也可以指定WHERE條件語句來刪除特定的記錄:

 
getContentResolver().delete(uri, “NAME=” + “‘XYZ XYZ’”, null);

這將會刪除name爲‘XYZ XYZ’的記錄。

 

 下面來一個完整的演示

 

 第一個類:

複製代碼
 public class MyUsers {    

public static final String AUTHORITY  = “com.wissen.MyContentProvider”;

    // BaseColumn類中已經包含了 _id字段   

public static final class User implements BaseColumns {        

public static final Uri CONTENT_URI  = Uri.parse(”content://com.wissen.MyContentProvider”);         // 表數據列        

public static final String  USER_NAME  = “USER_NAME”;    

}

}

複製代碼

第二個類(繼承ContentProvider):

 

複製代碼
package com.example.filestudy;

import android.content.ContentProvider;
import android.content.ContentUris;
import android.content.ContentValues;
import android.content.Context;
import android.database.Cursor;
import android.database.SQLException;
import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteOpenHelper;
import android.database.sqlite.SQLiteQueryBuilder;
import android.net.Uri;

public class MyContentProvider extends ContentProvider {
    private SQLiteDatabase    sqlDB;
    private DatabaseHelper    dbHelper;
    private static final String  DATABASE_NAME = "Users.db";
    private static final int  DATABASE_VERSION= 1;
    private static final String TABLE_NAME= "User";
    private static final String TAG = "MyContentProvider";

    private static class DatabaseHelper extends SQLiteOpenHelper {//###管理數據庫
        DatabaseHelper(Context context) {
            super(context, DATABASE_NAME, null, DATABASE_VERSION);
        }

        @Override
        public void onCreate(SQLiteDatabase db) {
            //創建用於存儲數據的表
                db.execSQL("Create table " + TABLE_NAME + "( _id INTEGER PRIMARY KEY AUTOINCREMENT, USER_NAME TEXT);");
        }

        @Override
        public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
            db.execSQL("DROP TABLE IF EXISTS " + TABLE_NAME);
            onCreate(db);
        }
    }

    @Override
    public int delete(Uri uri, String s, String[] as) {
        return 0;
    }

    @Override
    public String getType(Uri uri) {//這個方法和上面的未碰到的代碼有關
        return null;
    }

    @Override
    public Uri insert(Uri uri, ContentValues contentvalues) {
        sqlDB = dbHelper.getWritableDatabase();
        long rowId = sqlDB.insert(TABLE_NAME, "", contentvalues);//插入數據
        if (rowId > 0) {
         //Appends the given ID to the end of the path.(也就是說獲取插入數據行的Uri)
            Uri rowUri = ContentUris.appendId(MyUsers.User.CONTENT_URI.buildUpon(), rowId).build();
            getContext().getContentResolver().notifyChange(rowUri, null);//檢測變化
            return rowUri;
        }
        throw new SQLException("Failed to insert row into " + uri);
    }

    @Override
    public boolean onCreate() {//創建數據庫
        dbHelper = new DatabaseHelper(getContext());
        return (dbHelper == null) ? false : true;
    }

    @Override
    public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder) {
        SQLiteQueryBuilder qb = new SQLiteQueryBuilder();
        SQLiteDatabase db = dbHelper.getReadableDatabase();
        qb.setTables(TABLE_NAME);
        Cursor c = qb.query(db, projection, selection, null, null, null, sortOrder);
        c.setNotificationUri(getContext().getContentResolver(), uri);
        return c;
    }

    @Override
    public int update(Uri uri, ContentValues contentvalues, String s, String[] as) {
        return 0;
    }
}
複製代碼

 

第三個類:測試

 

複製代碼
package com.example.filestudy;

import android.net.Uri;
import android.os.Bundle;
import android.app.Activity;
import android.content.ContentValues;
import android.database.Cursor;
import android.graphics.PointF;
import android.graphics.Rect;
import android.view.Menu;
import android.view.View;
import android.widget.Button;
import android.widget.TextView;

public class MainActivity extends Activity {
    Button button;
    TextView view;
    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        
        view = (TextView)findViewById(R.id.textview);
        button = (Button)findViewById(R.id.button);
        button.setOnClickListener(new Button.OnClickListener(){
            @Override
            public void onClick(View arg0) {
                // TODO Auto-generated method stub
                insertRecord("LQM");
                displayRecords();
            }
        });
    }
    
    private void insertRecord(String userName) {
        ContentValues values = new ContentValues();
        values.put(MyUsers.User.USER_NAME, userName);
        getContentResolver().insert(MyUsers.User.CONTENT_URI, values);
    }

    private void displayRecords() {
        String columns[] = new String[] { MyUsers.User._ID, MyUsers.User.USER_NAME };
        Uri myUri = MyUsers.User.CONTENT_URI;
        Cursor cur = managedQuery(myUri, columns,null, null, null );
        if (cur.moveToFirst()) {
            String id = null;
            String userName = null;
            do {
                id = cur.getString(cur.getColumnIndex(MyUsers.User._ID));
                userName = cur.getString(cur.getColumnIndex(MyUsers.User.USER_NAME));
                view.setText(id + " : " + userName);
           } while (cur.moveToNext());
       }
    }
}
複製代碼

 

記好加入聲明:

<provider android:name="MyContentProvider" android:authorities="com.wissen.MyContentProvider" />
//前面說name要寫繼承類的全名,這裏我的MyContentProvider是和主Activity放置在同一個包裏面的,所以直接寫類名是可以的

 

點擊按鈕:最後的顯示結果:

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章