天天看點

ContentProvider使用總結

  近日來學習ContentProvider相關的知識,做了一個demo,想和網友分享下。

  首先說一點相關的知識:

  一:作用

  ContentProvider是不同應用程式共享資料的接口,跟共享資料的别的方法相比,ContentProvider更好地提供了資料共享接口的統一性。CP(ContentProvider的簡稱)通過一張或者多張表的形式向外部應用程式提供資料(就像關系型資料庫中看見的表那樣)。

  二:Content URIS

  URI(Uniform Resource Identifier)統一資源标示符,能表示provider中的資料。由三部分組成,分别是scheme,authority,path.它的scheme,Android系統規定為"content://",authority唯一表示了要通路的CP名字,而path表示了要通路的CP中的資料。在使用ContentResolver對象通路CP時,需要一個Uri參數。構造Uri時可能會用到三個類:

  Uri.Builder類可用來通過String建構Uri

  ContentUris.withAppendedId()可在Uri後面加一個id

  UriMatcher:在CP中這個類可幫你從接受到Uri中選擇出要執行的ACTION

  三:MIME type

  MIME,多用途網際網路郵件擴充,是一種網際網路标準。它的作用是:伺服器會通過MIME值來告訴用戶端所傳送的多媒體資料的類型。CP也是一種C/S架構,是以需要使用到這個值。它的格式為:type/subtype,比如text/html,代表所傳輸的文本為html的格式。在Android中MIME可提供兩類值:統一的MIME資料類型和定制的MIME類型字元串。在CP中前者在傳輸檔案時會用到,而後者更常用,發送結構化的資料,譬如SQLiteDatabase時會使用定制的MIME類型字元串。在android中後者有自己的規定:

  如果傳回單行資料,type被設定為"vnd.android.cursor.item"

  如果傳回多行資料,type被設定為"vnd.android.cursor.dir"

  而subtype部分由CP類自己設定。

  接下來進入正題,說代碼環節,這篇代碼是通過結構化資料來闡述CP的。

  一:用戶端

  在用戶端,是通過一個ContentResolver對象作為client和CP互動的。ContentResolver對象可調用CP中的同名方法,可提供“增删改查”的功能。

  Step1:在用戶端的manifest檔案申請要通路的CP的權限(該權限在CP端已向系統注冊,下文會講)

ContentProvider使用總結
ContentProvider使用總結

1 <uses-permission android:name="com.example.cpserver.permission" />      

View Code

  Step1.在“增删改查”之前需要判斷将要請求的Uri的有效性,代碼如下:

ContentProvider使用總結
ContentProvider使用總結
1 //判斷所要使用的Provider是否有效
 2     private boolean checkValidProvider(Uri uri)
 3     { 
 4         ContentProviderClient client = getContentResolver().acquireContentProviderClient(uri);
 5         if(client == null)
 6         {
 7             System.out.println("provider is invalid!");
 8             return false;
 9         }
10         else
11         {
12             client.release();
13             return true;
14         }
15     }      

  Step3:"增删改查"功能,這些方法都是SQL中DDL語言的一個封裝,具體每個方法的傳回值,參數意義不展開講了,否則很長,可留言詢問。

ContentProvider使用總結
ContentProvider使用總結
1 private int insert()
 2     {
 3         if(!checkValidProvider(Contract.CONTENT_URI))
 4             return -1;
 5         ContentValues values = new ContentValues();
 6         values.put(Contract.COLUMN_NAME_1, "小衛的春天");
 7         values.put(Contract.COLUMN_NAME_2, "翟衛華");
 8         values.put(Contract.COLUMN_NAME_3, "100");
 9         Uri uri = getContentResolver().insert(Contract.CONTENT_URI, values);
10         String lastPath = uri.getLastPathSegment();
11         if(TextUtils.isEmpty(lastPath)) 
12         {
13             System.out.println("insert failure!");
14         } 
15         else 
16         {
17             System.out.println("insert success! the id is " + lastPath);
18         }
19         return Integer.parseInt(lastPath);
20     }
21     
22     //删除所有行
23     private int delete()
24     {
25         if(!checkValidProvider(Contract.CONTENT_URI))
26             return -1;
27         int count = getContentResolver().delete(Contract.CONTENT_URI, null, null);
28         return count;
29     }
30     
31     //将所有行的資料進行一個修改
32     private int update()
33     {
34         if(!checkValidProvider(Contract.CONTENT_URI))
35             return -1;
36         ContentValues values = new ContentValues();
37         values.put(Contract.COLUMN_NAME_1,"小寶的春天");
38         values.put(Contract.COLUMN_NAME_2, "翟小寶");
39         values.put(Contract.COLUMN_NAME_3, "200");
40         int count = getContentResolver().update(Contract.CONTENT_URI, values, null, null);
41         if(count == 0)
42         {
43             System.out.println("update failed!");
44         }
45         return count;
46     }
47     
48     //row:要查找的行号, "-1"代表查找多行
49     private void query(int row)
50     {
51         if(!checkValidProvider(Contract.CONTENT_URI))
52             return;
53         Cursor cursor = null;
54         if(row != -1)
55         {
56             
57         }
58         else
59         {
60             cursor = getContentResolver().query(Contract.CONTENT_URI,
61                     new String[]{Contract.COLUMN_NAME_1,Contract.COLUMN_NAME_2,Contract.COLUMN_NAME_3},null, null, null);
62         }
63         if(cursor == null)
64             System.out.println("query failure!");
65         else
66         {
67             String strDisplay = getDataFromCursor(cursor);    
68             tvDisplay.setText(strDisplay);
69             cursor.close();
70         }
71     }      

  二:CP端

  在CP端要通過繼承ContentProvider來實作。CP是Android的四大元件之一,比較重要,而且系統本身就能提供很多的ContentProvider供開發者使用,比如可通過CP請求道所有的圖檔,音頻,視訊等資料。在Android系統中CP預設是可被别的任何應用程式請求到的,如果你不設定權限加以限制的話。是以第一步應該設定一個permission字段,并把它添加到provider标簽裡面去。

ContentProvider使用總結
ContentProvider使用總結
1 <permission 
 2     android:name="com.example.cpserver.permission"
 3     android:label="Example Data"
 4     android:protectionLevel="signature" />
 5 
 6 <provider
 7             android:name="provider.TestProvider"
 8             android:authorities="com.example.cpserver.provider"
 9             android:permission="com.example.cpserver.permission"
10             android:exported="true"
11             android:enabled="true" />      

  Step1:實作一個SQLiteOpenHelper作為Provider的資料存儲庫

ContentProvider使用總結
ContentProvider使用總結
1 package db;
 2 
 3 import com.example.cpserver.Contract;
 4 
 5 import android.content.Context;
 6 import android.database.sqlite.SQLiteDatabase;
 7 import android.database.sqlite.SQLiteOpenHelper;
 8 /*
 9  * 實作一個SQLiteOpenHelper作為Provider的資料存儲庫
10  */
11 public final class MainDatabaseHelper extends SQLiteOpenHelper {
12 
13     //建立一張表的SQL語句
14     private static final String SQL_CREATE_MAIN = "CREATE TABLE " +
15     Contract.TABLE_NAME + "(" +                           
16     " _ID INTEGER PRIMARY KEY, " +
17     Contract.COLUMN_NAME_1 + " TEXT," +
18     Contract.COLUMN_NAME_2 + " TEXT," +
19     Contract.COLUMN_NAME_3 + " INTEGER )";
20     
21     public MainDatabaseHelper(Context context) 
22     {
23         super(context, Contract.DB_NAME, null, 1);
24     }
25 
26     /*
27      *當Provider設法打開資料存儲庫,并且資料庫不存在時該方法被調用
28      */
29     @Override
30     public void onCreate(SQLiteDatabase db) {
31         // Creates the main table
32         db.execSQL(SQL_CREATE_MAIN);
33     }
34 
35     @Override
36     public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
37         // TODO Auto-generated method stub
38         
39     }
40 }      

  Step2:實作ContentProvider的子類,并且擴充抽象方法,代碼注釋很詳細。

ContentProvider使用總結
ContentProvider使用總結
1 package provider;
  2 
  3 import com.example.cpserver.Contract;
  4 import db.MainDatabaseHelper;
  5 import android.content.ContentProvider;
  6 import android.content.ContentUris;
  7 import android.content.ContentValues;
  8 import android.content.UriMatcher;
  9 import android.database.Cursor;
 10 import android.database.SQLException;
 11 import android.database.sqlite.SQLiteDatabase;
 12 import android.net.Uri;
 13 import android.text.TextUtils;
 14 
 15 /*
 16  * 1.除了onCreate方法,别的方法都可能會被多線程調用,是以這些方法要設計成線程安全的
 17  * 2.在onCreate中避免耗時操作
 18  * 3.雖然以下方法都要被繼承,但不必重寫每個方法,除了getType
 19  */
 20 public class TestProvider extends ContentProvider 
 21 {
 22     //建立一個UriMatcher對象,該對象幫你從接受的URI中選擇出要執行的動作
 23     private static final UriMatcher sUriMatcher = new UriMatcher(UriMatcher.NO_MATCH);
 24     private MainDatabaseHelper mOpenHelper;
 25     private SQLiteDatabase db = null;
 26     
 27     //調用addURI方法添加provider可以識别的所有URI類型
 28     static
 29     {
 30         sUriMatcher.addURI(Contract.authority, Contract.TABLE_NAME, 1);
 31         sUriMatcher.addURI(Contract.authority, Contract.TABLE_NAME+"/#", 2);
 32     }
 33     /*
 34      * 該方法作用:初始化該Provider
 35      * 注意:直到一個ContentResolver對象通路時,該方法才被調用
 36      * 這個方法裡不應做耗時的操作,因為這樣可能延緩Provider對别的應用程式的相應
 37      */
 38     @Override
 39     public boolean onCreate() {
 40         mOpenHelper = new MainDatabaseHelper(getContext());
 41         return true;
 42     }
 43 
 44     @Override
 45     public Cursor query(Uri uri, String[] projection, String selection,String[] selectionArgs, String sortOrder) 
 46     {
 47         int type = sUriMatcher.match(uri);
 48         switch (type)
 49         {
 50         //多行請求的URI
 51         case 1:
 52             if (TextUtils.isEmpty(sortOrder)) sortOrder = "_ID ASC";
 53             break;
 54         case 2:
 55             //單行請求的URI
 56             selection = selection + "_ID = " + uri.getLastPathSegment();
 57             break;
 58         default:
 59            //如果URI不比對,做一些錯誤提示,傳回null或者抛出異常
 60             throw new IllegalArgumentException("Unknown URI " + uri);
 61         }
 62         if(db == null)
 63             db = mOpenHelper.getWritableDatabase();
 64         Cursor cursor = db.query(Contract.TABLE_NAME, projection, selection, selectionArgs, null, null,sortOrder);
 65         return cursor;
 66     }
 67 
 68     //傳回content URI相應的MIME 類型
 69     @Override
 70     public String getType(Uri uri) 
 71     {
 72         int type = sUriMatcher.match(uri);
 73         switch (type) 
 74         {
 75         case 1:
 76             return Contract.CONTENT_TYPE;
 77         case 2:
 78             return Contract.CONTENT_ITEM_TYPE;
 79         default:
 80             throw new IllegalArgumentException("Unknown URI " + uri);
 81         }
 82     }
 83 
 84     @Override
 85     public Uri insert(Uri uri, ContentValues initialValues) 
 86     {
 87         int type = sUriMatcher.match(uri);
 88         if(type != 1)
 89             throw new IllegalArgumentException("Unknown URI " + uri);
 90         
 91         //建立一個可寫資料庫,将調用MainDatabaseHelper的onCreate方法,如果資料庫還不存在的話
 92         if(db == null)
 93             db = mOpenHelper.getWritableDatabase();
 94         
 95         //確定所有的域都被設定
 96         ContentValues values;
 97         if (initialValues != null) 
 98             values = new ContentValues(initialValues);
 99         else 
100             values = new ContentValues();
101         if (values.containsKey(Contract.COLUMN_NAME_1) == false) {
102             values.put(Contract.COLUMN_NAME_1, "");
103         }
104         if (values.containsKey(Contract.COLUMN_NAME_2) == false) {
105             values.put(Contract.COLUMN_NAME_2, "");
106         }
107         if (values.containsKey(Contract.COLUMN_NAME_3) == false) {
108             values.put(Contract.COLUMN_NAME_3, "");
109         }
110         
111         long rowId = db.insert(Contract.TABLE_NAME,null, values);
112         if(rowId > 0)
113         {
114             Uri noteUri = ContentUris.withAppendedId(Contract.CONTENT_URI, rowId);
115             getContext().getContentResolver().notifyChange(noteUri, null);
116             return noteUri;
117         }
118         throw new SQLException("Failed to insert row into " + uri);
119     }
120 
121     @Override
122     public int delete(Uri uri, String selection, String[] selectionArgs) 
123     {
124         if(db == null)
125             db = mOpenHelper.getWritableDatabase();
126         int type = sUriMatcher.match(uri);
127         int count;
128         switch (type)
129         {
130             case 1:
131                 count = db.delete(Contract.TABLE_NAME, selection, selectionArgs);
132                 break;
133             case 2:
134                 String noteId = uri.getLastPathSegment();
135                 count = db.delete(Contract.TABLE_NAME, "_ID" + "=" + noteId +
136                         (!TextUtils.isEmpty(selection) ? " AND (" + selection + ')' : ""), selectionArgs);
137                 break;
138             default:
139                 throw new IllegalArgumentException("Unknown URI " + uri);
140         }
141         getContext().getContentResolver().notifyChange(uri, null);
142         return count;
143     }
144 
145     @Override
146     public int update(Uri uri, ContentValues values, String selection,String[] selectionArgs) 
147     {
148         if(db == null)
149             db = mOpenHelper.getWritableDatabase();
150         int type = sUriMatcher.match(uri);
151         int count;
152         switch (type)
153         {
154             case 1:
155                 count = db.update(Contract.TABLE_NAME, values, selection, selectionArgs);
156                 break;
157             case 2:
158                 String noteId = uri.getLastPathSegment();
159                 count = db.update(Contract.TABLE_NAME, values,"_ID" + "=" + noteId +
160                         (!TextUtils.isEmpty(selection) ? " AND (" + selection + ')' : ""), selectionArgs);
161                 break;
162             default:
163                 throw new IllegalArgumentException("Unknown URI " + uri);
164         }
165         getContext().getContentResolver().notifyChange(uri, null);
166         return count;
167     }
168 
169     //如果Provider提供file資料,要用這個方法傳回MIME類型
170     @Override
171     public String[] getStreamTypes(Uri uri, String mimeTypeFilter) {
172         // TODO Auto-generated method stub
173         return super.getStreamTypes(uri, mimeTypeFilter);
174     }
175 
176 }      

  我認為CP之是以不那麼容易了解,是因為它涉及到的東西較多,還涉及到計算機網絡中的的URI,MIME等概念,确實不是那麼容易,作為碼農的我們隻能去啃了,無别的辦法。總結一下,CP機制中主要涉及到這麼幾點知識:permission權限,uri,MIME,使用UriMatcher來比對uri的類型,還要掌握一些基本的SQL語言等。關于使用CP來實作File分享,又是很長的一個篇幅,等得空再研究吧。我的項目用百度雲做個下載下傳連結吧,有需求的朋友可拿去用。歡迎留言交流。

http://pan.baidu.com/s/1gdkPia3

 

  Author:Andy Zhai

  2014-02-11    20:44:00