ContentProvider原理及實例

1、  ContentProvider是個啥?

    ContentProvider——內容提供者。它是一個類,這個類主要是對Android系統中進行共享的數據進行包裝,並提供了一組統一的訪問接口供其他程序調用。這些被共享的數據,可以使系統自己的也可以使我們個人應用程序中的數據。

 

2、  爲什麼要有ContentProvider這個類?

    在Android中,數據的存儲有很多種方式,最常用的就是SQLite和XML文件方式。在不同的應用程序間,其實數據是不能直接被相互訪問和操作的,在這種情況下,ContentProvider很好的被用來解決了不同應用程序間數據共享的問題。

    其實在Android系統中,已經爲我們提供了許多ContentProvider,如:Contacts、Browser、CallLog、Settings等等。那麼,Android系統中提供了這麼多的ContentProvider,另外還有我們自己公開的共享數據,我們在寫程序的時候,怎麼才能讓我們的應用程序知道去哪兒取、如何取這些數據呢?我們自然的會想到URI。

 

3、  URI是個啥?在ContentProvider中有什麼用處?URI中的幾個方法。

    URI(Uniform Resource Identifier)——統一資源定位符,URI在ContentProvider中代表了要操做的數據。

    在Android系統中通常的URI格式爲:content://LiB.cprovider.myprovider.Users/User/21

    在萬維網訪問時通常用的URI格式爲:http://www.XXXX.com/AAA/123

o 

·        content://——schema,這個是Android中已經定義好的一個標準。我個人一直認爲這和我們的http://有異曲同工之妙,都是代表的協議。ContentProvider(內容提供者)的scheme已經由Android所規定爲:content://

·        LiB.cprovider.myprovider.Users——authority(主機名),用於唯一標識這個ContentProvider,外部調用者通過這個authority來找到它。相當於www.XXXX.com,代表的是我們ContentProvider所在的”域名”,這個”域名”在我們Android中一定要是唯一的,否則系統怎麼能知道該找哪一個Provider呢?所以一般情況下,建議採用完整的包名加類名來標識這個ContentProvider的authority。

·        /User/21——路徑,用來標識我們要操作的數據。/user/21表示的意思是——找到User中id爲21的記錄。其實這個相當於/AAA/123。

    綜上所述,content://LiB.cprovider.myprovider.Users/User/21所代表的URI的意思爲:標識LiB.cprovider.myprovider中Users表中_ID爲21的User項。

4 一個Content Provider類實現了一組標準的方法接口,從而能夠讓其他的應用保存或讀取此Content Provider的各種數據類型。
 
也就是說,一個程序可以通過實現一個Content Provider的抽象接口將自己的數據暴露出去。
 
外界根本看不到,也不用看到這個應用暴露的數據在應用當中是如何存儲的,或者是用數據庫存儲還是用文件存儲,還是通過網上獲得,這些一切都不重要,
 
重要的是外界可以通過這一套標準及統一的接口和程序裏的數據打交道,可以讀取程序的數據,也可以刪除程序的數據

 

5.我們來看看Android系統中爲我們提供了些有關操作URI的方法:

UriMatcher:用於匹配Uri,它的用法如下:
       1.
首先把你需要匹配Uri路徑全部給註冊上,如下:
       //
常量UriMatcher.NO_MATCH表示不匹配任何路徑的返回碼(-1)
      UriMatcher  uriMatcher = new UriMatcher(UriMatcher.NO_MATCH);
       //
如果match()方法匹配content://com.changcheng.sqlite.provider.contactprovider/contact路徑,返回匹配碼爲1
       uriMatcher.addURI(“com.changcheng.sqlite.provider.contactprovider”,“contact”, 1);//
添加需要匹配uri,如果匹配就會返回匹配碼
       //
如果match()方法匹配  content://com.changcheng.sqlite.provider.contactprovider/contact/230路徑,返回匹配碼爲2
      uriMatcher.addURI(“com.changcheng.sqlite.provider.contactprovider”,“contact/#”, 2);//#
號爲通配符
      
       2.
註冊完需要匹配的Uri後,就可以使用uriMatcher.match(uri)方法對輸入的Uri進行匹配,如果匹配就返回匹配碼,匹配碼是調用addURI()方法傳入的第三個參數,假設匹配content://com.changcheng.sqlite.provider.contactprovider/contact路徑,返回的匹配碼爲1

       ContentUris
:用於獲取Uri路徑後面的ID部分,它有兩個比較實用的方法:
•        withAppendedId(uri, id)
用於爲路徑加上ID部分
•        parseId(uri)
方法用於從路徑中獲取ID部分

       ContentResolver
:當外部應用需要對ContentProvider中的數據進行添加、刪除、修改和查詢操作時,可以使用ContentResolver 類來完成,要獲取ContentResolver 對象,可以使用Activity提供的getContentResolver()方法。 ContentResolver使用insertdeleteupdatequery方法,來操作數據。

 

6. ContentProvider中公開的幾個方法

o 

·        publicboolean onCreate():該方法在ContentProvider創建後就會被調用,Android系統運行後,ContentProvider只有在被第一次使用它時纔會被創建。

·        publicUri insert(Uri uri, ContentValues values):外部應用程序通過這個方法向 ContentProvider添加數據。

·        uri——標識操作數據的URI

·        values——需要添加數據的鍵值對

·        publicint delete(Uri uri, String selection, String[] selectionArgs):外部應用程序通過這個方法從 ContentProvider中刪除數據。

·        uri——標識操作數據的URI

·        selection——構成篩選添加的語句,如"id=1" 或者 "id=?"

·        selectionArgs——對應selection的兩種情況可以傳入null 或者 new String[]{"1"}

·        publicint update(Uri uri, ContentValues values, String selection, String[]selectionArgs):外部應用程序通過這個方法對ContentProvider中的數據進行更新。

·        values——對應需要更新的鍵值對,鍵爲對應共享數據中的字段,值爲對應的修改值

·        其餘參數同delete方法

·        publicCursor query(Uri uri, String[] projection, String selection, String[]selectionArgs, String sortOrder):外部應用程序通過這個方法從ContentProvider中獲取數據,並返回一個Cursor對象。

·        projection——需要從Contentprovider中選擇的字段,如果爲空,則返回的Cursor將包含所有的字段。

·        sortOrder——默認的排序規則

·        其餘參數同delete方法    



 

7.實現步驟:

想要自己的程序裏面的數據能夠被其他程序所訪問到,有以下步驟:

第一:首先生成一個繼承contentprovider的類.

第二:androidMainfest.xml裏面添加一個provider的標籤就可以了.

   <provider  android:name="zuoyeSQLlite.hall.MyContentProvider"
               android:authorities="zuoyeSQLite.hall.MyContentProvider"/>

是不是很簡單?其他程序訪問的時候只要按以下步驟就可以訪問到了:

Uriuri=Uri.Uri.parse("content://"+AUTHORY+"/userinfo1");

AUTHORY其實就是 android:authorities的值.,注意.這裏必須一樣..否則系統是找不到的.也是就是

StringAUTHORY="zuoyeSQLite.hall.MyContentProvider"

然後獲取一個

ContentResolvermContentResolver=getContentResolver();

這樣就其他程序就可以反問我們的數據了..

ContentResolver對應的幾個方法:

·  query(Uri, String[], String, String[], String) which returns data to the caller

·  insert(Uri, ContentValues) which inserts new data into the content provider

·  update(Uri, ContentValues, String, String[]) which updates existing data in the content provider

·  delete(Uri,String, String[]) which deletes data from the content provider

其實和contentprovider裏面的方法是一樣的..他們所對應的數據,最終是會被傳到我們在之前程序裏面定義的那個contentprovider類的方法,至於你想要在這幾個方法裏面做什麼事,那個就隨你意了..

 

8.實例

Manifest.xml<application></application>之間加入:

<provider android:name="MyProvider" android:authorities="com.snowdream.provider" />

MyProvider代表contentprovider的實現類,com.snowdream.provider代表uriauthority部分,訪問MyProvideruriauthority部分必須是com.snowdream.provider



定義一個類存放各種常量

[java] view plaincopy

1.   package com.snowdream.contentprovider;  

2.     

3.   import android.net.Uri;  

4.     

5.   public class Profile {  

6.         

7.       /** 

8.        * 表格名稱 

9.        */  

10.      public static final String TABLE_NAME = "profile";  

11.        

12.      /** 

13.       * 列表一,_ID,自動增加 

14.       */  

15.      public static final String COLUMN_ID = "_id";  

16.        

17.      /** 

18.       * 列表二,名稱 

19.       */  

20.      public static final String COLUMN_NAME = "name";  

21.         

22.         

23.      public static final String AUTOHORITY = "com.snowdream.provider";  

24.      public static final int ITEM = 1;  

25.      public static final int ITEM_ID = 2;  

26.         

27.      public static final String CONTENT_TYPE = "vnd.android.cursor.dir/vnd.snowdream.profile";  

28.      public static final String CONTENT_ITEM_TYPE = "vnd.android.cursor.item/vnd.snowdream.profile";  

29.         

30.      public static final Uri CONTENT_URI = Uri.parse("content://" + AUTOHORITY + "/profile");  

31.  }  


定義一個數據庫類,創建的數據表中必須含有"_id"這個字段,這個字段是自增長的,插入的時候不用管這個字段,數據庫會自己遞增地加上

[java] view plaincopy

1.   package com.snowdream.contentprovider;  

2.     

3.   import android.content.Context;  

4.   import android.database.SQLException;  

5.   import android.database.sqlite.SQLiteDatabase;  

6.   import android.database.sqlite.SQLiteOpenHelper;  

7.     

8.   public class DBHelper extends SQLiteOpenHelper {  

9.     

10.      /** 

11.       * 數據庫名稱 

12.       */  

13.      private static final String DATABASE_NAME = "test.db";    

14.        

15.      /** 

16.       * 數據庫版本 

17.       */  

18.      private static final int DATABASE_VERSION = 1;    

19.    

20.      public DBHelper(Context context) {  

21.          super(context, DATABASE_NAME, null, DATABASE_VERSION);  

22.      }  

23.    

24.      @Override  

25.      public void onCreate(SQLiteDatabase db)  throws SQLException {  

26.          //創建表格  

27.          db.execSQL("CREATE TABLE IF NOT EXISTS "+ Profile.TABLE_NAME + "("+ Profile.COLUMN_ID +" INTEGER PRIMARY KEY AUTOINCREMENT," + Profile.COLUMN_NAME +" VARCHAR NOT NULL);");  

28.      }  

29.    

30.      @Override  

31.      public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion)  throws SQLException {  

32.          //刪除並創建表格  

33.          db.execSQL("DROP TABLE IF EXISTS "+ Profile.TABLE_NAME+";");  

34.          onCreate(db);  

35.      }  

36.  }  

 

·  定義ContentProvider的子類

·  1contentprovider的調用者有可能是Activity,Service,Application這3種context,被誰調用,getContext就是誰

2ContentResolver是屬於context的,通過getContentResolver獲取,ContentResolver可以通過registerContentObserver註冊
觀察者(觀察者是ContentObserver 的派生類),一旦ContentProvider操作的數據變化後,調用ContentResolver
的notifyChange方法即可通知到觀察者(回調觀察者的onChange方法),註冊觀察者不是必須的,所有
notifyChange不是必須調用的

3ContentValues 一次只能放入一行數據(可以使多個字段,即多個名值對)

4onCreate只在ContentProvider第一次被調用的時候調用,多次調用共享的是一個ContentProvider

5Cursor即數據庫查詢結果的操作遊標,用戶隨機訪問查詢結果,獲取查詢結果的數目等

6在Content Resolver中有幾個需要注意的接口:
notifyChange (Uri uri, ContentObserver observer, boolean syncToNetwork);
registerContentObserver (Uri uri, boolean notifyForDescendents, ContentObserverobserver);
unregisterContentObserver (ContentObserver observer);

在Cursor中也有幾個類似的接口:
setNotificationUri (ContentResolver cr, Uri uri);
registerContentObserver (ContentObserver observer);
unregisterContentObserver (ContentObserver observer);

7.要在query中調用
setNotificationUri(ContentResolver cr, Uri notifyUri)
從而在ContentService中註冊contentservice的觀察者,這個觀察者是cursor的內部成員(cursor是一個接口,此處真正的cursor是sqlitecursor),這樣每個查詢返回的cursor都能在contentprovider對應數據改變時得到通知,因爲這些cursor都有一個成員註冊成了contentservice的觀察者
那麼數據改變時,是怎麼通知這些cursor的觀察者成員呢?
這就需要在delete,update,insert這些改變數據的方法中調用contentresolver的notifychange方法,這個notifychange實際調用的是contentservice的notifychange,在這個notifychange方法裏,contentservice查找所有在其中註冊的觀察者,找出對這次更新數據感興趣的觀察者(通過uri),然後通知它們數據改變
contentservice有很多觀察者,它是系統服務,管理系統中所有contentprovider,通過uri匹配查找觀察者,通知符合要求的觀察者(實際就是通知對應的cursor)
cursor得到通知以後做些什麼呢?
事實上,cursor也可以有很多觀察者,因爲一個查詢出來的結果集可能會被多個地方使用(比如多個listview使用一個cursor),cursor對應的數據改變的時候,它也會通知到所有關注它的觀察者(調用它們的onchange)
那麼,cursor的觀察者是怎麼註冊進去的呢?是通過cursor的registerContentObserver這個方法註冊進去的
以下例作爲解析,在simplecursoradapter(繼承自cursoradapter)的構造函數中,調用父類cursoradapter的構造函數,在這個構造函數裏,
調用了cursor的registerContentObserver,把一個繼承自contentobserver的成員對象作爲觀察者註冊進了cursor的觀察者裏。這樣cursor變化了,就會通知simplecursoradapter,simplecursoradapter裏就可以重新查詢結果並顯示在listview中

以上其實就是兩個觀察者模式,cursor觀察contentservice,同時cursor又被cursoradapter觀察(都是通過其成員變量觀察,不是直接觀察),我們也可以通過contentservice和cursoradapter提供的接口註冊我們自己的觀察者,也就是說contentservice的觀察者可以不是cursor,cursor的觀察者可以不是cursoradapter

· 

·  [java] view plaincopy

1.   package com.snowdream.contentprovider;    

2.       

3.   import android.content.ContentProvider;    

4.   import android.content.ContentUris;    

5.   import android.content.ContentValues;    

6.   import android.content.UriMatcher;    

7.   import android.database.Cursor;    

8.   import android.database.SQLException;    

9.   import android.database.sqlite.SQLiteDatabase;    

10.  import android.net.Uri;    

11.      

12.  public class MyProvider extends ContentProvider {    

13.      

14.      DBHelper mDbHelper = null;    

15.      SQLiteDatabase db = null;    

16.      

17.      private static final UriMatcher mMatcher;    

18.      static{    

19.          mMatcher = new UriMatcher(UriMatcher.NO_MATCH);    

20.          mMatcher.addURI(Profile.AUTOHORITY,Profile.TABLE_NAME, Profile.ITEM);    

21.          mMatcher.addURI(Profile.AUTOHORITY, Profile.TABLE_NAME+"/#", Profile.ITEM_ID);    

22.      }    

23.      

24.      @Override    

25.      public int delete(Uri uri, String selection, String[] selectionArgs) {    

26.          // TODO Auto-generated method stub    

27.          return 0;    

28.      }    

29.      

30.      @Override    

31.      public String getType(Uri uri) {    

32.          switch (mMatcher.match(uri)) {    

33.          case Profile.ITEM:    

34.              return Profile.CONTENT_TYPE;    

35.          case Profile.ITEM_ID:    

36.              return Profile.CONTENT_ITEM_TYPE;    

37.          default:    

38.              throw new IllegalArgumentException("Unknown URI"+uri);    

39.          }    

40.      }    

41.      

42.      @Override    

43.      public Uri insert(Uri uri, ContentValues values) {    

44.          // TODO Auto-generated method stub    

45.          long rowId;    

46.          if(mMatcher.match(uri)!=Profile.ITEM){    

47.              throw new IllegalArgumentException("Unknown URI"+uri);    

48.          }    

49.          rowId = db.insert(Profile.TABLE_NAME,null,values);    

50.          if(rowId>0){    

51.              Uri noteUri=ContentUris.withAppendedId(Profile.CONTENT_URI, rowId);    

52.              getContext().getContentResolver().notifyChange(noteUri, null);    

53.              return noteUri;    

54.          }    

55.      

56.          throw new SQLException("Failed to insert row into " + uri);    

57.      }    

58.      

59.      @Override    

60.      public boolean onCreate() {    

61.          // TODO Auto-generated method stub    

62.          mDbHelper = new DBHelper(getContext());    

63.      

64.          db = mDbHelper.getReadableDatabase();    

65.      

66.          return true;    

67.      }    

68.      

69.      @Override    

70.      public Cursor query(Uri uri, String[] projection, String selection,    

71.              String[] selectionArgs, String sortOrder) {    

72.          // TODO Auto-generated method stub    

73.          Cursor c = null;    

74.          switch (mMatcher.match(uri)) {    

75.          case Profile.ITEM:    

76.              c =  db.query(Profile.TABLE_NAME, projection, selection, selectionArgs, nullnull, sortOrder);    

77.              break;    

78.          case Profile.ITEM_ID:    

79.              c = db.query(Profile.TABLE_NAME, projection,Profile.COLUMN_ID + "="+uri.getLastPathSegment(), selectionArgs, nullnull, sortOrder);    

80.              break;    

81.          default:    

82.              throw new IllegalArgumentException("Unknown URI"+uri);    

83.          }    

84.      

85.          c.setNotificationUri(getContext().getContentResolver(), uri);    

86.          return c;    

87.      }    

88.      

89.      @Override    

90.      public int update(Uri uri, ContentValues values, String selection,    

91.              String[] selectionArgs) {    

92.          // TODO Auto-generated method stub    

93.          return 0;    

94.      }    

95.      

96.  }    


補充一下gettype的作用:
一個provider可能給你提供多種不同類型的數據,然後你並不知道給你返回的是什麼,因爲他可能給你返回音樂,視頻,或者圖片。這時候你就用這個方法獲得類型,再進行相應處理
這個方法是不會自動調用的,自己需要的時候調用

特別是不同類型的數據需要使用隱式intent讓不同的activity處理時,這些activity在mainefest的過濾器中配置了data的mimetype,比如:
<dataandroid:mimeType="vnd.android.cursor.item/vnd.google.note" />
這樣通過intent.addData(conentProvider.getType());可以讓其自動去匹配跳轉對應的組件



如果該Uri對應數據可能包括多條記錄,那麼MIME類型字符串應該以vnd.android.cursor.dir/開頭;如果該Uri對應的數據只包含一條記錄,那麼返回MIME類型字符串應該以vnd.android.cursor.item/開頭

[java] view plaincopy

1.   package com.snowdream.contentprovider;  

2.     

3.   import com.snowdream.contentprovider.R;  

4.     

5.   import android.app.ListActivity;  

6.   import android.content.ContentResolver;  

7.   import android.content.ContentUris;  

8.   import android.content.ContentValues;  

9.   import android.database.Cursor;  

10.  import android.database.SQLException;  

11.  import android.database.sqlite.SQLiteDatabase;  

12.  import android.net.Uri;  

13.  import android.os.Bundle;  

14.  import android.view.Menu;  

15.  import android.widget.SimpleCursorAdapter;  

16.    

17.    

18.  public class MainActivity extends ListActivity {  

19.      private SimpleCursorAdapter adapter= null;  

20.      private Cursor mCursor = null;  

21.      private ContentResolver mContentResolver = null;  

22.    

23.      @Override  

24.      public void onCreate(Bundle savedInstanceState) {  

25.          super.onCreate(savedInstanceState);  

26.          initData();  

27.          initAdapter();  

28.      }  

29.    

30.      public void initData(){  

31.          mContentResolver = getContentResolver();  

32.    

33.          //填充數據  

34.          for (int i = 0; i < 100; i++) {  

35.              ContentValues values = new ContentValues();  

36.              values.put(Profile.COLUMN_NAME, "張三"+i);  

37.              mContentResolver.insert(Profile.CONTENT_URI, values);  

38.          }  

39.      }  

40.    

41.    

42.      public void initAdapter(){  

43.          //查詢表格,並獲得Cursor  

44.          //查詢全部數據  

45.          mCursor = mContentResolver.query(Profile.CONTENT_URI, new String[]{Profile.COLUMN_ID,Profile.COLUMN_NAME}, nullnullnull);  

46.    

47.          //查詢部分數據  

48.          //String selection = Profile.COLUMN_ID + " LIKE '%1'";  

49.          //mCursor = mContentResolver.query(Profile.CONTENT_URI, new String[]{Profile.COLUMN_ID,Profile.COLUMN_NAME}, selection, null, null);  

50.    

51.    

52.          //查詢一個數據  

53.          //Uri uri = ContentUris.withAppendedId(Profile.CONTENT_URI, 50);  

54.          //mCursor = mContentResolver.query(uri, new String[]{Profile.COLUMN_ID,Profile.COLUMN_NAME}, null, null, null);  

55.    

56.          startManagingCursor(mCursor);  

57.    

58.          //設置adapter  

59.          adapter = new SimpleCursorAdapter(this, android.R.layout.simple_list_item_2, mCursor, new String[]{Profile.COLUMN_ID,Profile.COLUMN_NAME}, new int[]{android.R.id.text1,android.R.id.text2});  

60.          setListAdapter(adapter);  

61.      }  

62.    

63.    

64.      @Override  

65.      public boolean onCreateOptionsMenu(Menu menu) {  

66.          getMenuInflater().inflate(R.menu.activity_main, menu);  

67.          return true;  

68.      }  

69.    

70.    

71.  }  


效果:

· 

 


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