前言
本篇博客講講ContentProvider,內容提供者。前面已經講過了數據持久化,但是除了共享內存(SDCard)的數據外,其他包括SQLite、SharedPreferences都是僅限於被當前所創建的應用訪問,而無法使它們的數據在應用程序之間交換數據,所以Android提供了ContentProvider,ContentProvider是不同應用程序之間進行數據交換的標准API。雖然Android附帶了需要有用的內容提供者,但是本片博客不涉及這方面的內容,而是專注講解如何創建自己的ContentProvider,並在其他應用中如何調用。
概述
ContentProvider可以理解為一個Android應用對外開放的接口,只要是符合它所定義的Uri格式的請求,均可以正常訪問執行操作。其他的Android應用可以使用ContentResolver對象通過與ContentProvider同名的方法請求執行,被執行的就是ContentProvider中的同名方法。所以ContentProvider很多對外可以訪問的方法,在ContentResolver中均有同名的方法,是一一對應的,如圖:
Uri
在Android中,Uri是一種比較常見的資源訪問方式。而對於ContentProvider而言,Uri也是有固定格式的:
<srandard_prefix>://<authority>/<data_path>/<id>
- <srandard_prefix>:ContentProvider的srandard_prefix始終是content://。
- <authority>:ContentProvider的名稱。
- <data_path>:請求的數據類型。
- <id>:指定請求的特定數據。
ContentProvider
ContentProvider也是Android應用的四大組件之一,所以也需要在AndroidManifest.xml文件中進行配置。而且某個應用程序通過ContentProvider暴露了自己的數據操作接口,那么不管該應用程序是否啟動,其他應用程序都可以通過這個接口來操作它的內部數據。
Android附帶了許多有用的ContentProvider,但是本篇博客不會涉及到這些內容的,以后有時間會再講解。Android附帶的ContentProvider包括:
- Browser:存儲如瀏覽器的信息。
- CallLog:存儲通話記錄等信息。
- Contacts:存儲聯系人等信息。
- MediaStore:存儲媒體文件的信息。
- Settings:存儲設備的設置和首選項信息。
在Android中,如果要創建自己的內容提供者的時候,需要擴展抽象類ContentProvider,並重寫其中定義的各種方法。然后在AndroidManifest.xml文件中注冊該ContentProvider即可。
ContentProvider是內容提供者,實現Android應用之間的數據交互,對於數據操作,無非也就是CRUD而已。下面是ContentProvider必須要實現的幾個方法:
- onCreate():初始化提供者。
- query(Uri, String[], String, String[], String):查詢數據,返回一個數據Cursor對象。
- insert(Uri, ContentValues):插入一條數據。
- update(Uri, ContentValues, String, String[]):根據條件更新數據。
- delete(Uri, String, String[]):根據條件刪除數據。
- getType(Uri) 返回MIME類型對應內容的URI。
除了onCreate()和getType()方法外,其他的均為CRUD操作,這些方法中,Uri參數為與ContentProvider匹配的請求Uri,剩下的參數可以參見SQLite的CRUD操作,基本一致,SQLite的內容在另外一篇博客中有講解:Android--數據持久化之SQLite。、
Tips:還有兩個非常有意思的方法,必須要提一下,call()和bulkInsert()方法,使用call,理論上可以在ContentResolver中執行ContentProvider暴露出來的任何方法,而bulkInsert()方法用於插入多條數據。
在ContentProvider的CRUD操作,均會傳遞一個Uri對象,通過這個對象來匹配對應的請求。那么如何確定一個Uri執行哪項操作呢?需要用到一個UriMatcher對象,這個對象用來幫助內容提供者匹配Uri。它所提供的方法非常簡單,僅有兩個:
- void addURI(String authority,String path,int code):添加一個Uri匹配項,authority為AndroidManifest.xml中注冊的ContentProvider中的authority屬性;path為一個路徑,可以設置通配符,#表示任意數字,*表示任意字符;code為自定義的一個Uri代碼。
- int match(Uri uri):匹配傳遞的Uri,返回addURI()傳遞的code參數。
在創建好一個ContentProvider之后,還需要在AndroidManifest.xml文件中對ContentProvider進行配置,使用一個<provider.../>節點,一般只需要設置兩個屬性即可訪問,一些額外的屬性就是為了設置訪問權限而存在的,后面會詳細講解:
- android:name:provider的響應類。
- android:authorities:Provider的唯一標識,用於Uri匹配,一般為ContentProvider類的全名。
下面通過一個示例來講解一下ContentProvider,在這個例子中,需要用到SQLite數據庫來存儲數據,定義了一個StudentDAO類,用於進行對SQLite的CRUD操作,這里就不提供數據訪問的源碼了,有興趣的朋友可以在下載源碼查看:
ContentProvider實現:
1 package com.example.contentproviderdemo; 2 3 import com.example.dao.StudentDAO; 4 import android.content.ContentProvider; 5 import android.content.ContentUris; 6 import android.content.ContentValues; 7 import android.content.UriMatcher; 8 import android.database.Cursor; 9 import android.net.Uri; 10 import android.os.Bundle; 11 import android.util.Log; 12 13 public class StudentProvider extends ContentProvider { 14 15 private final String TAG = "main"; 16 private StudentDAO studentDao = null; 17 private static final UriMatcher URI_MATCHER = new UriMatcher( 18 UriMatcher.NO_MATCH); 19 private static final int STUDENT = 1; 20 private static final int STUDENTS = 2; 21 static { 22 //添加兩個URI篩選 23 URI_MATCHER.addURI("com.example.contentproviderdemo.StudentProvider", 24 "student", STUDENTS); 25 //使用通配符#,匹配任意數字 26 URI_MATCHER.addURI("com.example.contentproviderdemo.StudentProvider", 27 "student/#", STUDENT); 28 } 29 30 public StudentProvider() { 31 32 } 33 34 @Override 35 public boolean onCreate() { 36 // 初始化一個數據持久層 37 studentDao = new StudentDAO(getContext()); 38 Log.i(TAG, "---->>onCreate()被調用"); 39 return true; 40 } 41 42 @Override 43 public Uri insert(Uri uri, ContentValues values) { 44 Uri resultUri = null; 45 //解析Uri,返回Code 46 int flag = URI_MATCHER.match(uri); 47 if (flag == STUDENTS) { 48 long id = studentDao.insertStudent(values); 49 Log.i(TAG, "---->>插入成功, id="+id); 50 resultUri = ContentUris.withAppendedId(uri, id); 51 } 52 return resultUri; 53 } 54 55 @Override 56 public int delete(Uri uri, String selection, String[] selectionArgs) { 57 int count = -1; 58 try { 59 int flag = URI_MATCHER.match(uri); 60 switch (flag) { 61 case STUDENT: 62 // delete from student where id=? 63 //單條數據,使用ContentUris工具類解析出結尾的Id 64 long id = ContentUris.parseId(uri); 65 String where_value = "id = ?"; 66 String[] args = { String.valueOf(id) }; 67 count = studentDao.deleteStudent(where_value, args); 68 break; 69 case STUDENTS: 70 count = studentDao.deleteStudent(selection, selectionArgs); 71 break; 72 } 73 } catch (Exception e) { 74 e.printStackTrace(); 75 } 76 Log.i(TAG, "---->>刪除成功,count="+count); 77 return count; 78 } 79 80 @Override 81 public int update(Uri uri, ContentValues values, String selection, 82 String[] selectionArgs) { 83 int count = -1; 84 try { 85 int flag = URI_MATCHER.match(uri); 86 switch (flag) { 87 case STUDENT: 88 long id = ContentUris.parseId(uri); 89 String where_value = " id = ?"; 90 String[] args = { String.valueOf(id) }; 91 count = studentDao.updateStudent(values, where_value, args); 92 break; 93 case STUDENTS: 94 count = studentDao.updateStudent(values, selection, 95 selectionArgs); 96 break; 97 } 98 } catch (Exception e) { 99 e.printStackTrace(); 100 } 101 Log.i(TAG, "---->>更新成功,count="+count); 102 return count; 103 } 104 105 @Override 106 public Cursor query(Uri uri, String[] projection, String selection, 107 String[] selectionArgs, String sortOrder) { 108 Cursor cursor = null; 109 try { 110 int flag = URI_MATCHER.match(uri); 111 switch (flag) { 112 case STUDENT: 113 long id = ContentUris.parseId(uri); 114 String where_value = " id = ?"; 115 String[] args = { String.valueOf(id) }; 116 cursor = studentDao.queryStudents(where_value, args); 117 break; 118 case STUDENTS: 119 cursor = studentDao.queryStudents(selection, selectionArgs); 120 break; 121 } 122 } catch (Exception e) { 123 e.printStackTrace(); 124 } 125 Log.i(TAG, "---->>查詢成功,Count="+cursor.getCount()); 126 return cursor; 127 } 128 129 130 @Override 131 public String getType(Uri uri) { 132 int flag = URI_MATCHER.match(uri); 133 String type = null; 134 switch (flag) { 135 case STUDENT: 136 type = "vnd.android.cursor.item/student"; 137 Log.i(TAG, "----->>getType return item"); 138 break; 139 case STUDENTS: 140 type = "vnd.android.cursor.dir/students"; 141 Log.i(TAG, "----->>getType return dir"); 142 break; 143 } 144 return type; 145 } 146 @Override 147 public Bundle call(String method, String arg, Bundle extras) { 148 Log.i(TAG, "------>>"+method); 149 Bundle bundle=new Bundle(); 150 bundle.putString("returnCall", "call被執行了"); 151 return bundle; 152 } 153 }
在AndroidManifest.xml中<application>節點中增加:
1 <provider 2 android:name=".StudentProvider" 3 android:authorities="com.example.contentproviderdemo.StudentProvider" > 4 </provider>
ContentResolver
ContentResolver,內容訪問者。可以通過ContentResolver來操作ContentProvider所暴露處理的接口。一般使用Content.getContentResolver()方法獲取ContentResolver對象。上面已經提到ContentResolver的很多方法與ContentProvider一一對應,所以它也存在insert、query、update、delete等方法。
下面就通過一個簡單的Demo來演示ContentResolver的使用,這里就不另外新建一個項目了,在原有項目上使用Android JUnit新建一個測試類,用於測試。在另外一個項目中的操作也是一樣的。關於JUnit,不了解的朋友可以參見另外一篇博客:JUnit單元測試。
JUnit測試類:
1 package com.example.contentproviderdemo; 2 3 import android.content.ContentResolver; 4 import android.content.ContentValues; 5 import android.database.Cursor; 6 import android.net.Uri; 7 import android.os.Bundle; 8 import android.test.AndroidTestCase; 9 import android.util.Log; 10 11 public class MyTest extends AndroidTestCase { 12 13 public MyTest() { 14 // TODO Auto-generated constructor stub 15 16 } 17 18 public void insert() { 19 ContentResolver contentResolver = getContext().getContentResolver(); 20 Uri uri = Uri 21 .parse("content://com.example.contentproviderdemo.StudentProvider/student"); 22 ContentValues values = new ContentValues(); 23 values.put("name", "Demo"); 24 values.put("address", "HK"); 25 Uri returnuir = contentResolver.insert(uri, values); 26 Log.i("main", "-------------->" + returnuir.getPath()); 27 } 28 29 public void delete() { 30 ContentResolver contentResolver = getContext().getContentResolver(); 31 // 刪除多行:content://com.example.contentproviderdemo.StudentProvider/student 32 Uri uri = Uri 33 .parse("content://com.example.contentproviderdemo.StudentProvider/student/2"); 34 contentResolver.delete(uri, null, null); 35 } 36 37 public void deletes() { 38 ContentResolver contentResolver = getContext().getContentResolver(); 39 Uri uri = Uri 40 .parse("content://com.example.contentproviderdemo.StudentProvider/student"); 41 String where = "address=?"; 42 String[] where_args = { "HK" }; 43 contentResolver.delete(uri, where, where_args); 44 } 45 46 public void update() { 47 ContentResolver contentResolver = getContext().getContentResolver(); 48 Uri uri = Uri 49 .parse("content://com.example.contentproviderdemo.StudentProvider/student/2"); 50 ContentValues values = new ContentValues(); 51 values.put("name", "李四"); 52 values.put("address", "上海"); 53 contentResolver.update(uri, values, null, null); 54 } 55 56 public void updates() { 57 ContentResolver contentResolver = getContext().getContentResolver(); 58 Uri uri = Uri 59 .parse("content://com.example.contentproviderdemo.StudentProvider/student"); 60 ContentValues values = new ContentValues(); 61 values.put("name", "王五"); 62 values.put("address", "深圳"); 63 String where = "address=?"; 64 String[] where_args = { "beijing" }; 65 contentResolver.update(uri, values, where, where_args); 66 } 67 68 public void query() { 69 ContentResolver contentResolver = getContext().getContentResolver(); 70 Uri uri = Uri 71 .parse("content://com.example.contentproviderdemo.StudentProvider/student/2"); 72 Cursor cursor = contentResolver.query(uri, null, null, null, null); 73 while (cursor.moveToNext()) { 74 Log.i("main", 75 "-------------->" 76 + cursor.getString(cursor.getColumnIndex("name"))); 77 } 78 } 79 80 public void querys() { 81 ContentResolver contentResolver = getContext().getContentResolver(); 82 Uri uri = Uri 83 .parse("content://com.example.contentproviderdemo.StudentProvider/student"); 84 String where = "address=?"; 85 String[] where_args = { "深圳" }; 86 Cursor cursor = contentResolver.query(uri, null, where, where_args, 87 null); 88 while (cursor.moveToNext()) { 89 Log.i("main", 90 "-------------->" 91 + cursor.getString(cursor.getColumnIndex("name"))); 92 } 93 } 94 95 public void calltest() { 96 ContentResolver contentResolver = getContext().getContentResolver(); 97 Uri uri = Uri 98 .parse("content://com.example.contentproviderdemo.StudentProvider/student"); 99 Bundle bundle = contentResolver.call(uri, "method", null, null); 100 String returnCall = bundle.getString("returnCall"); 101 Log.i("main", "-------------->" + returnCall); 102 } 103 104 }
效果演示:
執行insert(),數據庫在創建的時候存在初始數據:
執行update():
執行updates():
執行query()
執行delete()
getType()中的MIME
MIME類型就是設定某種擴展名的文件用一種應用程序來打開的方式類型。在ContentProvider中的getType方法,返回的就是一個MIME類型的字符串。如果支持需要使用ContentProvider來訪問數據,就上面這個Demo,getType()完全可以只返回一個Null,並不影響效果,但是覆蓋ContentProvider的getType方法對於用new Intent(String action, Uri uri)方法啟動activity是很重要的,如果它返回的MIME type和activity在<intent filter>中定義的data的MIME type不一致,將造成activity無法啟動。這就涉及到Intent和Intent-filter的內容了,以后有機會再說,這里不再詳解。
從官方文檔了解到,getType返回的字符串,如果URI針對的是單條數據,則返回的字符串以vnd.android.cursor.item/開頭;如果是多條數據,則以vnd.adroid.cursor.dir/開頭。
訪問權限
對於ContentProvider暴露出來的數據,應該是存儲在自己應用內存中的數據,對於一些存儲在外部存儲器上的數據,並不能限制訪問權限,使用ContentProvider就沒有意義了。對於ContentProvider而言,有很多權限控制,可以在AndroidManifest.xml文件中對<provider>節點的屬性進行配置,一般使用如下一些屬性設置:
- android:grantUriPermssions:臨時許可標志。
- android:permission:Provider讀寫權限。
- android:readPermission:Provider的讀權限。
- android:writePermission:Provider的寫權限。
- android:enabled:標記允許系統啟動Provider。
- android:exported:標記允許其他應用程序使用這個Provider。
- android:multiProcess:標記允許系統啟動Provider相同的進程中調用客戶端。
總結
以上就講解了內容提供者的簡單使用,關於內容提供者的內容涉及面很多,一篇博客無法一一說明,這里只是簡單的定義一個內容提供者,並且說明如何操作它。這個博客的示例源碼中,聲明的內容提供者和內容訪問者均在一個項目中,可能不太利於大家理解,但是在另外一個項目中的操作,與JUnit類中的操作一致,所以這里就沒有另外創建一個項目去說明它,不過源碼中有另外一個小項目,只是實現了Insert的操作,有興趣的朋友對照看一下就會發現其實是一樣的。