ContentProvider數據訪問詳解


ContentProvider數據訪問詳解

 

  Android官方指出的數據存儲方式總共有五種:Shared Preferences、網絡存儲、文件存儲、外儲存儲、SQLite,這些存儲方式一般都只是在一個單獨的應用程序中實現數據的共享,而對於需要操作其他應用程序中的數據時(如媒體庫、通訊錄等),可能就需要借助ContentProvider了。

 

1ContentProvider

  ContentProvider為存儲和獲取數據提供了統一的接口,使用表的形式來對數據進行封裝,使得開發者在后續的開發過程中不用關心數據存儲的細節。使用ContentProvider可以在不同的應用程序之間共享數據,Android為常見的數據類型提供了默認的ContentProvider(包括音頻、視頻、圖片和通訊錄等)。

  總的來說,利用ContentProvider來實現共享數據的好處是統一了數據的訪問方式。

 

2URIUniform Resource Identifier

  URI為系統中的每一個資源賦予一個名字,比方說通話記錄。每一個ContentProvider都擁有一個公共的URI,用於表示ContentProvider所提供的數據。 Android所提供的ContentProvider都位於android.provider包中, 可以將URI分為A、B、C、D 4個部分來理解。如對於content://com.wang.provider.myprovider/tablename/id:

  a、標准前綴——content://,用來說明一個Content Provider控制這些數據;

  b、URI的標識——com.wang.provider.myprovider,用於唯一標識這個ContentProvider,外部調用者可以根據這個標識來找到它。對於第三方應用程序,為了保證URI標識的唯一性,它必須是一個完整的、小寫的類名。這個標識在元素的authorities屬性中說明,一般是定義該ContentProvider的包.類的名稱;

  c、路徑——tablename,通俗的講就是你要操作的數據庫中表的名字,或者你也可以自己定義,記得在使用的時候保持一致就可以了;

  d、記錄ID——id,如果URI中包含表示需要獲取的記錄的ID,則返回該id對應的數據,如果沒有ID,就表示返回全部;

  對於第三部分路徑(path)做進一步的解釋,用來表示要操作的數據,構建時應根據實際項目需求而定。如:

      a、操作tablename表中id為11的記錄,構建路徑:/tablename/11;

      b、操作tablename表中id為11的記錄的name字段:tablename/11/name;

      c、操作tablename表中的所有記錄:/tablename;

      d、操作來自文件、xml或網絡等其他存儲方式的數據,如要操作xml文件中tablename節點下name字段:/ tablename/name;

      e、若需要將一個字符串轉換成Uri,可以使用Uri類中的parse()方法,如:

1 Uri uri = Uri.parse("content://com.wang.provider.myprovider/tablename");

 

3、UriMatcher

  Uri代表要操作的數據,在開發過程中對數據進行獲取時需要解析Uri,Android提供了兩個用於操作Uri的工具類,分別為UriMatcher和ContentUris 。掌握它們的基本概念和使用方法,對一個Android開發者來說是一項必要的技能。

  UriMatcher類用於匹配Uri,它的使用步驟如下:

  a、將需要匹配的Uri路徑進行注冊,代碼如下:

1 //常量UriMatcher.NO_MATCH表示不匹配任何路徑的返回碼
2 UriMatcher  sMatcher = new UriMatcher(UriMatcher.NO_MATCH);
3 //如果match()方法匹配“content://com.wang.provider.myprovider/tablename”路徑,返回匹配碼為1
4 sMatcher.addURI("content://com.wang.provider.myprovider", " tablename ", 1);
5  //如果match()方法匹配content://com.wang.provider.myprovider/tablename/11路徑,返回匹配碼為2
6 sMatcher.addURI("com.wang.provider.myprovider", "tablename/#", 2);

  注意,添加第二個URI時,路徑后面的id采用了通配符形式“#”,表示只要前面三個部分都匹配上了就OK。

  b、注冊完需要匹配的Uri后,可以使用sMatcher.match(Uri)方法對輸入的Uri進行匹配,如果匹配就返回對應的匹配碼,匹配碼為調用addURI()方法時傳入的第三個參數。

 1 switch (sMatcher.match(Uri.parse("content://com.zhang.provider.yourprovider/tablename/100"))) {
 2    case 1:
 3      //match 1, todo something
 4      break;
 5    case 2
 6      //match 2, todo something
 7      break;
 8    default:
 9      //match nothing, todo something
10      break;
11 }

 

4ContentUris

  ContentUris類用於操作Uri路徑后面的ID部分,它有兩個比較實用的方法:withAppendedId(Uri uri, long id)和parseId(Uri uri)。

  withAppendedId(Uri uri, long id)用於為路徑加上ID部分:

1 Uri uri = Uri.parse("content://com.wang.provider.myprovider/tablename");
2 Uri resultUri = ContentUris.withAppendedId(uri, 10);

      parseId(Uri uri)則從路徑中獲取ID部分:

1 Uri uri = Uri.parse("content://com.zhang.provider.myprovider/tablename/10")
2 long personid = ContentUris.parseId(uri);

 

5ContentProvider數據共享

  ContentProvider類主要方法的介紹:

  public boolean onCreate(),在ContentProvider創建后就會被調用,而ContentProvider是在其它應用第一次訪問它時被創建;

  public Uri insert(Uri uri, ContentValues values),供外部應用向ContentProvider添加數據;

  public int delete(Uri uri, String selection, String[] selectionArgs),供外部應用從ContentProvider刪除數據;

  public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs),供外部應用更新ContentProvider中的數據;

  public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder),供外部應用從ContentProvider中獲取數據;

  public String getType(Uri uri),返回當前Uri所代表數據的MIME類型;

  如果操作的數據屬於集合類型,那么MIME類型字符串應該以vnd.android.cursor.dir/開頭,如要得到所有tablename記錄的Uri為content://com.wang.provider.myprovider/tablename,那么返回的MIME類型字符串應該為:vnd.android.cursor.dir/table。

  如果要操作的數據屬於非集合類型數據,那么MIME類型字符串應該以vnd.android.cursor.item/開頭,如得到id為10的tablename記錄,Uri為content://com.wang.provider.myprovider/tablename/10,那么返回的MIME類型字符串為:vnd.android.cursor.item/tablename 。

 

6、ContentResolver操作數據

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

  ContentResolver 類提供了與ContentProvider類相同簽名的四個方法:

  public Uri insert(Uri uri, ContentValues values),往ContentProvider添加數據;

  public int delete(Uri uri, String selection, String[] selectionArgs),從ContentProvider刪除數據;

  public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs),更新ContentProvider中的數據;

  public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder),從ContentProvider中獲取數據;

  這些方法的第一個參數為Uri,代表要操作的ContentProvider和對其中的什么數據進行操作,其實和ContentProvider里面的方法是一樣的。他們所對應的數據,最終會被傳到我們在之前程序里面定義的那個ContentProvider類的方法,假設給定的是:Uri.parse("content://com.wang.provider.myprovider/tablename/10"),那么將會對主機名為com.wang.provider.myprovider的ContentProvider進行操作,操作的數據為tablename表中id為10的記錄。

      使用ContentResolver對ContentProvider中的數據進行操作的代碼如下:

 1 ContentResolver resolver = getContentResolver();
 2 Uri uri = Uri.parse("content://com.wang.provider.myprovider/tablename");
 3 //添加一條記錄
 4 ContentValues values = new ContentValues();
 5 values.put("name", "wang1");
 6 values.put("age", 28);
 7 resolver.insert(uri, values); 
 8 //獲取tablename表中所有記錄
 9 Cursor cursor = resolver.query(uri, null, null, null, "tablename data");
10 while(cursor.moveToNext()){
11    Log.i("ContentTest", "tablename_id="+ cursor.getInt(0)+ ", name="+ cursor.getString(1));
12 }
13 //把id為1的記錄的name字段值更改新為zhang1
14 ContentValues updateValues = new ContentValues();
15 updateValues.put("name", "zhang1");
16 Uri updateIdUri = ContentUris.withAppendedId(uri, 2);
17 resolver.update(updateIdUri, updateValues, null, null);
18 //刪除id為2的記錄,即字段age
19 Uri deleteIdUri = ContentUris.withAppendedId(uri, 2);
20 resolver.delete(deleteIdUri, null, null);

 

7、監聽數據變化

  如果ContentProvider的訪問者需要知道數據發生的變化,可以在ContentProvider發生數據變化時調用getContentResolver().notifyChange(uri, null)來通知注冊在此URI上的訪問者。只給出類中監聽部分的代碼:

1 public class MyProvider extends ContentProvider {
2    public Uri insert(Uri uri, ContentValues values) {
3       db.insert("tablename", "tablenameid", values);
4       getContext().getContentResolver().notifyChange(uri, null);
5    }
6 }

  而訪問者必須使用ContentObserver對數據(數據采用uri描述)進行監聽,當監聽到數據變化通知時,系統就會調用ContentObserver的onChange()方法:

 1 getContentResolver().registerContentObserver(Uri.parse("content://com.ljq.providers.personprovider/person"),
 2        true, new PersonObserver(new Handler()));
 3 public class PersonObserver extends ContentObserver{
 4    public PersonObserver(Handler handler) {
 5       super(handler);
 6    }
 7    public void onChange(boolean selfChange) {
 8       //to do something
 9    }
10 }

 

8、完整項目代碼

  a、對於實際測試中的Uri——com.wang.provider.myprovider,需要在AndroidManifest.xml文件中進行說明:

1 <provider android:name="MyProvider" android:authorities="com.wang.provider.myprovider" />

  b、定義Profile類,其主要負責定義Uri各組成部分名稱對應的String常量:

 1 package com.wang.testcontentprovider;
 2 
 3 import android.net.Uri;  
 4 
 5 public class Profile {  
 6       
 7     /** 
 8      * 表格名稱 
 9      */  
10     public static final String TABLE_NAME = "tablename";  
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     public static final String AUTOHORITY = "com.wang.provider.myprovider";  
23     public static final int ITEM = 1;  
24     public static final int ITEM_ID = 2;  
25        
26     public static final String CONTENT_TYPE = "vnd.android.cursor.dir/vnd.wang.tablename";  
27     public static final String CONTENT_ITEM_TYPE = "vnd.android.cursor.item/vnd.wang.tablename";  
28        
29     public static final Uri CONTENT_URI = Uri.parse("content://" + AUTOHORITY + "/tablename");  
30 }  

  c、實現存放數據的DBHelper類,繼承自SQLiteOpenHelper類,完成數據庫的一些常規操作:

 1 package com.wang.testcontentprovider;
 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 }  

  d、MyProvider類的實現,主要負責向ContentProvider中添加數據

 1 package com.wang.testcontentprovider;
 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, null, null, sortOrder);    
77             break;    
78         case Profile.ITEM_ID:    
79             c = db.query(Profile.TABLE_NAME, projection,Profile.COLUMN_ID + "="+uri.getLastPathSegment(), selectionArgs, null, null, 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 }

  e、作為主程序的MainActivity類,對ContentProvider中的數據進行獲取,並在界面上進行簡單的顯示:

 1 package com.wang.testcontentprovider;
 2 
 3 import android.support.v7.app.ActionBarActivity;
 4 import android.os.Bundle;
 5 import android.view.Menu;
 6 import android.view.MenuItem;
 7 
 8 import android.app.ListActivity;  
 9 import android.content.ContentResolver;  
10 import android.content.ContentUris;  
11 import android.content.ContentValues;  
12 import android.database.Cursor;  
13 import android.database.SQLException;  
14 import android.database.sqlite.SQLiteDatabase;  
15 import android.net.Uri;  
16 import android.widget.SimpleCursorAdapter;  
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         for (int i = 0; i < 100; i++) {  
33             ContentValues values = new ContentValues();  
34             values.put(Profile.COLUMN_NAME, "Wang "+i);  
35             mContentResolver.insert(Profile.CONTENT_URI, values);  
36         }  
37     }  
38   
39     public void initAdapter(){  
40         mCursor = mContentResolver.query(Profile.CONTENT_URI, new String[]{Profile.COLUMN_ID,Profile.COLUMN_NAME}, null, null, null);  
41   
42         startManagingCursor(mCursor);  
43   
44         //設置adapter  
45         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});  
46         setListAdapter(adapter);  
47     }  
48   
49     @Override  
50     public boolean onCreateOptionsMenu(Menu menu) {  
51         getMenuInflater().inflate(R.menu.main, menu);  
52         return true;  
53     }  
54   
55 }  

 

9、結果分析

  貼上一張不太美觀的結果圖:

  


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM