這篇主要介紹下ContentProvider如何實現(xiàn)共享數(shù)據(jù)、及ContentResolver如何訪問其他進程等數(shù)據(jù)。
ContentProvider共享數(shù)據(jù)
簡介
ContentProvider管理對一組結(jié)構(gòu)化數(shù)據(jù)的訪問。 它們封裝了數(shù)據(jù),并提供了定義數(shù)據(jù)安全性的機制。 ContentProvider是將一個進程中的數(shù)據(jù)與在另一進程中運行的代碼相連接的標準接口。
當要訪問ContentProvider中的數(shù)據(jù)時,您可以使用應用程序上下文中的ContentResolver對象作為客戶端與提供者進行通信。 ContentResolver對象與Provider對象進行通信,該對象是實現(xiàn)ContentProvider的類的一個實例。提供者對象從客戶端接收數(shù)據(jù)請求,執(zhí)行請求的操作,并返回結(jié)果。
如果您不打算與其他應用程序共享數(shù)據(jù),則不需要開發(fā)自己的provider程序。但是,您需要自己的provider在您自己的應用程序中提供自定義搜索建議。如果要將應用程序中的復雜數(shù)據(jù)或文件復制并粘貼到其他應用程序,還需要自己的provider程序。
Android本身包括管理音頻,視頻,圖像和個人聯(lián)系信息等數(shù)據(jù)的ContentProvider。有一些限制,任何Android應用程序都可以訪問這些提供程序。
在開始構(gòu)建提供程序之前,請先決定是否需要ContentProvider。如果要提供一個或多個以下功能,則需要構(gòu)建內(nèi)容提供程序:
- 你想提供復雜的數(shù)據(jù)或文件到其他應用程序。
- 您希望允許用戶將應用程序中的復雜數(shù)據(jù)復制到其他應用程序中。
- 您希望使用搜索框架提供自定義搜索建議。
如果使用的完全是你自己應用程序中的SQLite數(shù)據(jù)庫,則并不需要一個ContentProvider。
接下來,按照以下步驟構(gòu)建ContentProvider:
- 為您的數(shù)據(jù)設(shè)計原始存儲。ContentProvider以兩種方式提供數(shù)據(jù):
- 文件數(shù)據(jù)
通常是文件的數(shù)據(jù),如照片,音頻或視頻。將文件存儲在應用程序的私有空間中。響應來自另一個應用程序的文件請求,您的ContentProvider可以提供該文件的句柄。 - “結(jié)構(gòu)化”數(shù)據(jù)
通常進入數(shù)據(jù)庫,數(shù)組或類似結(jié)構(gòu)的數(shù)據(jù)。以與行和列的表格兼容的表單存儲數(shù)據(jù)。一行代表一個實體,例如一個人或一個庫存中的一個物品。列表示實體的某些數(shù)據(jù),例如某人的名稱或項目的價格。存儲此類型數(shù)據(jù)的常見方法是在SQLite數(shù)據(jù)庫中,但您可以使用任何類型的永久存儲。
- 定義ContentProvider類及其所需方法的具體實現(xiàn)。
- 定義ContentProvider的authority字符串,其內(nèi)容URI和列名稱。如果您希望提供程序的應用程序處理意圖,還可以定義intent action,extras data和flags。還要定義要訪問數(shù)據(jù)的應用程序所需的權(quán)限。您應該考慮將所有這些值定義為單獨的合同類中的常量; 稍后,您可以將此類暴露給其他開發(fā)人員。
數(shù)據(jù)存儲這里就不說了,不了解數(shù)據(jù)庫的可以參考我這篇blog:Android 數(shù)據(jù)存儲 (三)SQLite Databases
Content URIs
Content URI是標識provider中的數(shù)據(jù)的URI。 Content URI包括整個provider(其權(quán)限)的符號名稱和指向表(路徑)的名稱。 可選的ID部分指向表中的單個行。每個數(shù)據(jù)訪問方法 ContentProvider都有一個內(nèi)容URI作為參數(shù); 這允許您確定要訪問的表,行或文件。
uri格式:
分為三個部分:
- content:// 這部分是Android規(guī)定,固定的。
- com.example.app.provider 這個部分是ContentProvider的authority。系統(tǒng)是根據(jù)這個部分來找到操作哪個ContentProvider。
- table 數(shù)據(jù)部分,訪問不同資源時,這個部分是動態(tài)改變的。例如,如果您有兩個表table1和 table2,則將該部分設(shè)置為table1和table2。
內(nèi)容URI模式使用通配符匹配內(nèi)容URI:
- *: 匹配任何長度的任何有效字符的字符串。
- #: 匹配任意長度的數(shù)字字符串。
uri功能豐富,如下
content://com.zpengyong.app.provider/table/1 訪問table數(shù)據(jù)中id為1的記錄。
content://com.zpengyong.app.provider/table/1/name 訪問table數(shù)據(jù)中id為1的name字段。
實現(xiàn)ContentProvider類
ContentProvider實例通過處理來自其他應用程序的請求來管理對一組結(jié)構(gòu)化數(shù)據(jù)的訪問。所有形式的訪問最終都會調(diào)用ContentResolver,然后調(diào)用一個具體的方法ContentProvider來獲取訪問權(quán)限。
必需的方法
抽象類ContentProvider定義了您必須實現(xiàn)的六個抽象方法,作為您自己的具體子類的一部分。所有這些方法除了 onCreate()被客戶端應用程序調(diào)用嘗試訪問您的ContentProvider。
- query()
從ContentProvider檢索數(shù)據(jù)。
該方法必須返回一個Cursor對象,如果失敗則會拋出異常。如果您使用SQLite數(shù)據(jù)庫作為數(shù)據(jù)存儲,則可以簡單地返回由SQLiteDatabase類的一個query()方法返回的Cursor。如果查詢與任何行都不匹配,應該返回Cursor實例(其getCount()方法為0)。只有在查詢過程中發(fā)生內(nèi)部錯誤時,才應返回null。如果不使用SQLite數(shù)據(jù)庫作為數(shù)據(jù)存儲,請使用Cursor的具體子類之一。例如,MatrixCursor類實現(xiàn)一個游標,其中每行都是Object的數(shù)組。使用這個類,使用addRow()來添加一個新行。 - insert()
在ContentProvider中插入一行。使用ContentValues參數(shù)中的值將新行添加到適當?shù)谋怼?如果列名不在ContentValues參數(shù)中,則可能需要在provider程序代碼中或數(shù)據(jù)庫模式中為其提供默認值。 此方法應返回新行的URI。 要構(gòu)造它,使用withAppendedId()將新行的_ID(或其他主鍵)值附加到表的URI。 - update()
更新ContentProvider中的現(xiàn)有行。使用參數(shù)來選擇要更新的表和行,并獲取更新的列值。返回更新的行數(shù)。 - delete()
從ContentProvider中刪除行。使用參數(shù)選擇要刪除的表和行。返回刪除的行數(shù)。 - getType()
返回與內(nèi)容URI對應的MIME類型。在“ 實現(xiàn)內(nèi)容提供者MIME類型 ”部分中更詳細地描述了該方法。 - onCreate()
初始化ContentProvider。Android系統(tǒng)在創(chuàng)建您的ContentProvider后立即調(diào)用此方法。請注意,在ContentResolver對象嘗試訪問它之前,provider程序不會被創(chuàng)建 。
實現(xiàn)這些方法應該說明如下:
- 除onCreate()之外的所有這些方法都可以由多個線程同時調(diào)用,因此它們必須是線程安全的。
- 避免在onCreate()中執(zhí)行長時間的操作。 延遲初始化任務,直到實際需要。
- 雖然您必須實現(xiàn)這些方法,但您的代碼除了返回預期的數(shù)據(jù)類型之外,不必執(zhí)行任何操作。 例如,您可能希望防止其他應用程序?qū)?shù)據(jù)插入某些表。 為此,您可以忽略對insert()的調(diào)用并返回0。
Android系統(tǒng)在啟動ContentProvider程序時調(diào)用onCreate()。 您應該在此方法中僅執(zhí)行快速運行的初始化任務,并延遲數(shù)據(jù)庫創(chuàng)建和數(shù)據(jù)加載,直到provider程序?qū)嶋H接收到對數(shù)據(jù)的請求。 如果在onCreate()中執(zhí)行冗長的任務,您將減慢provider的啟動速度。 反過來,這將減慢從provider到其他應用程序的響應。
例如,如果使用SQLite數(shù)據(jù)庫,則可以在ContentProvider.onCreate()中創(chuàng)建一個新的SQLiteOpenHelper對象,然后在第一次打開數(shù)據(jù)庫時創(chuàng)建SQL表。 為了方便起見,第一次調(diào)用getWritableDatabase()時,它會自動調(diào)用SQLiteOpenHelper.onCreate()方法。
數(shù)據(jù)存儲
數(shù)據(jù)存儲效果:
- 默認情況下,存儲在設(shè)備內(nèi)部存儲上的數(shù)據(jù)文件對您的應用程序和provider程序是私有的。
- 您創(chuàng)建的SQLiteDatabase數(shù)據(jù)庫對您的應用程序和provider程序是私有的。
- 默認情況下,您保存到外部存儲的數(shù)據(jù)文件是公共的和可讀的。您不能使用內(nèi)容提供商限制對外部存儲中的文件的訪問,因為其他應用程序可以使用其他API調(diào)用來讀取和寫入它們。
- 該方法調(diào)用在設(shè)備內(nèi)部存儲上打開、創(chuàng)建文件或SQLite數(shù)據(jù)庫 可能會給所有其他應用程序的讀取和寫入訪問。如果您使用內(nèi)部文件或數(shù)據(jù)庫作為ContentProvider的存儲庫,并將其提供為"world-readable" or "world-writeable"訪問權(quán)限,那么在其清單中為您的provider設(shè)置的權(quán)限將不會保護您的數(shù)據(jù)。內(nèi)部存儲中的文件和數(shù)據(jù)庫的默認訪問權(quán)限為“私有”,對于provider的存儲庫,您不應該更改此權(quán)限。
如果要使用ContentProvider權(quán)限來控制對數(shù)據(jù)的訪問,則應將數(shù)據(jù)存儲在內(nèi)部文件,SQLite數(shù)據(jù)庫或“云”(例如遠程服務器)中,并且應保留文件和數(shù)據(jù)庫對你的應用程序是私有的。
權(quán)限
即使底層數(shù)據(jù)是私有的,所有應用程序都可以讀取或?qū)懭肽腃ontentProvider程序,因為默認情況下,您的ContentProvider程序沒有設(shè)置權(quán)限。 要更改此設(shè)置,請使用<provider>元素的屬性或子元素為清單文件中的ContentProvider設(shè)置權(quán)限。 您可以設(shè)置適用于整個ContentProvider程序,特定表格,甚至某些記錄或三者的權(quán)限。
您的清單文件中包含一個或多個<permission>元素,為您的ContentProvider定義權(quán)限。 要使您的ContentProvider程序唯一的權(quán)限,請使用Java風格的范圍設(shè)置為android:name屬性。 例如,將讀取權(quán)限命名為com.example.app.provider.permission.READ_PROVIDER。
以下描述了ContentProvider程序權(quán)限的范圍,從適用于整個ContentProvider程序的權(quán)限開始,然后變得更細。 更細的權(quán)限優(yōu)先于較大范圍的權(quán)限:
- Single read-write provider-level permission(單一讀寫提供程序級)
該權(quán)限控制對整個ContentProvider的讀取和寫入訪問,由<provider>元素的android:permission屬性指定。 - Separate read and write provider-level permission(單獨的讀寫提供程序級)
對整個ContentProvider的讀取權(quán)限和寫入權(quán)限。您可以使用<provider>元素的屬性 android:readPermission和 android:writePermission屬性來 指定它們。它們優(yōu)先于android:permission。 - Path-level permission(路徑級)
ContentProvider的URI的讀取、寫入、讀?。瘜懭霗?quán)限??梢允褂?lt;provider>元素的<path-permission>子元素指定要控制的每個URI。 對于您指定的每個內(nèi)容URI,您可以指定讀/寫權(quán)限,讀權(quán)限或?qū)憴?quán)限,或全部三個。 讀權(quán)限、寫權(quán)限優(yōu)先于讀/寫權(quán)限。 此外,路徑級權(quán)限優(yōu)先于provider級權(quán)限。 - Temporary permission(臨時)
即使應用程序沒有通常需要的權(quán)限,也允許臨時訪問應用程序的權(quán)限級別。臨時訪問功能減少應用程序在其清單中的權(quán)限數(shù)量。當您啟用臨時權(quán)限時,只有您的provider需要“永久”權(quán)限的應用程序才能持續(xù)訪問您的所有數(shù)據(jù)。
當您希望允許外部圖像查看器應用程序從您的提供商顯示照片附件時,請考慮實施電子郵件提供商和應用程序所需的權(quán)限。為了給圖像查看器提供必要的訪問權(quán)限,而不需要權(quán)限,請為照片的內(nèi)容URI設(shè)置臨時權(quán)限。設(shè)計您的電子郵件應用程序,以便用戶想要顯示照片時,應用程序會向圖像查看器發(fā)送包含照片的內(nèi)容URI和權(quán)限標志的意圖。然后,圖像查看器可以查詢您的電子郵件提供商以檢索照片,即使查看器對您的提供商沒有正常的讀取權(quán)限。
要啟用臨時權(quán)限,可以設(shè)置<provider>元素的android:grantUriPermissions屬性,或者在<provider>元素中添加一個或多個<grant-uri-permission>子元素。如果您使用臨時權(quán)限,則每當您從提供程序中刪除對內(nèi)容URI的支持時,都必須調(diào)用Context.revokeUriPermission(),并且內(nèi)容URI與臨時權(quán)限相關(guān)聯(lián)。
該屬性的值決定了您的提供者的訪問量。如果屬性設(shè)置為true,則系統(tǒng)將向整個提供程序授予臨時權(quán)限,覆蓋您的提供程序級別或路徑級權(quán)限所需的任何其他權(quán)限。
如果此標志設(shè)置為false,則必須在<provider>元素中添加<grant-uri-permission>子元素。每個子元素指定授予臨時訪問權(quán)限的內(nèi)容URI或URI。
要臨時訪問應用程序,intent必須包含F(xiàn)LAG_GRANT_READ_URI_PERMISSION或FLAG_GRANT_WRITE_URI_PERMISSION標志,或兩者都包含。這些都是用setFlags()方法設(shè)置的。
如果android:grantUriPermissions屬性不存在,則假定為false。
<provider>元素
像Activity和Service組件一樣,子類ContentProvider 必須使用該 <provider>元素在其應用程序的清單文件中定義 。Android系統(tǒng)從元素獲取以下信息:
權(quán)限(android:authorities)
?標識系統(tǒng)中整個提供商的符號名稱。
提供者類名( android:name)
?實現(xiàn)的類ContentProvider。該類在“ 實現(xiàn)ContentProvider類 ”一節(jié)中有更詳細的描述 。
權(quán)限
?指定其他應用程序必須具有訪問提供程序數(shù)據(jù)的權(quán)限的屬性:
- android:grantUriPermssions:臨時許可標志。
- android:permission:單提供商范圍的讀/寫權(quán)限。
- android:readPermission:提供商范圍的讀取權(quán)限。
- android:writePermission:提供商范圍的寫入許可。
啟動和控制屬性
?這些屬性決定了Android系統(tǒng)如何以及何時啟動提供程序,提供程序的進程特性以及其他運行時設(shè)置:
- android:enabled:允許系統(tǒng)啟動提供程序的標志。
- android:exported:允許其他應用程序使用此提供程序的標志。
- android:initOrder:相對于同一進程中的其他提供者,應啟動此提供程序的順序。
- android:multiProcess:允許系統(tǒng)以與調(diào)用客戶端相同的過程啟動提供程序的標志。
- android:process:運行提供程序的進程的名稱。
- android:syncable:表示提供者的數(shù)據(jù)要與服務器上的數(shù)據(jù)同步的標志。
信息屬性
?provider的可選圖標和標簽:
- android:icon:包含provider的圖標資源?!?設(shè)置” >“ 應用程序” >“ 全部”中,應用程序列表中提供商標簽旁邊會顯示該圖標 。
- android:label:描述提供者或其數(shù)據(jù)的信息標簽,或兩者。標簽會顯示在“ 設(shè)置” >“ 應用程序” >“ 全部”中的應用列表中 。
代碼
效果:
這個是基于之前數(shù)據(jù)庫的demo改的。主界面主要是數(shù)據(jù)庫的相關(guān)操作。
ContentProvider代碼如下:
package com.zpengyong.app;
import com.zpengyong.app.db.UserSQLiteOpenHelper;
import android.content.ContentProvider;
import android.content.ContentResolver;
import android.content.ContentUris;
import android.content.ContentValues;
import android.content.Context;
import android.content.UriMatcher;
import android.database.Cursor;
import android.database.sqlite.SQLiteDatabase;
import android.net.Uri;
public class TestProvider extends ContentProvider {
private static final String TAG = "TestProvider";
private static final String CONTENT_AUTHORITY = "com.zpengyong.app.provider";
private static final String PATH_USER = "/user";
private static final int CODE_USER = 1;
private static final Uri URI_USER = Uri.parse("content://com.zpengyong.app.provider/user");
private static final UriMatcher mUriMatcher = new UriMatcher(UriMatcher.NO_MATCH);
static{
mUriMatcher.addURI(CONTENT_AUTHORITY, PATH_USER, CODE_USER);
}
private Context mContext;
private UserSQLiteOpenHelper mUserSQLiteOpenHelper;
private SQLiteDatabase mDatabase;
private ContentResolver mContentResolver;
@Override
public boolean onCreate() {
mContext = getContext(); //獲取上下文
mUserSQLiteOpenHelper = UserSQLiteOpenHelper.getInstance(mContext);
mDatabase = mUserSQLiteOpenHelper.getWritableDatabase();
mContentResolver = mContext.getContentResolver();
return false;
}
@Override
public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder) {
int match = mUriMatcher.match(uri);
Cursor result;
switch(match){
case CODE_USER:
result = mDatabase.query(UserSQLiteOpenHelper.DATABASE_TABLE_USER,
projection, selection, selectionArgs, null, null, sortOrder);
break;
default:
throw new UnsupportedOperationException("query not supported on uri:"+uri);
}
return result;
}
@Override
public String getType(Uri uri) {
return null;
}
@Override
public Uri insert(Uri uri, ContentValues values) {
int match = mUriMatcher.match(uri);
Uri result;
switch(match){
case CODE_USER:
long id = mDatabase.insert(UserSQLiteOpenHelper.DATABASE_TABLE_USER, null, values);
result = ContentUris.withAppendedId(uri, id);
break;
default:
throw new UnsupportedOperationException("Insert not supported on uri:"+uri);
}
if(result != null){
//通知數(shù)據(jù)庫的變化
mContentResolver.notifyChange(URI_USER, null);
}
return result;
}
@Override
public int delete(Uri uri, String selection, String[] selectionArgs) {
int match = mUriMatcher.match(uri);
int result;
switch(match){
case CODE_USER:
result = mDatabase.delete(
UserSQLiteOpenHelper.DATABASE_TABLE_USER,
selection, selectionArgs);
break;
default:
throw new UnsupportedOperationException("delete not supported on uri:"+uri);
}
if(result > 0){
mContentResolver.notifyChange(URI_USER, null);
}
return result;
}
@Override
public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs) {
int match = mUriMatcher.match(uri);
int result;
switch(match){
case CODE_USER:
result = mDatabase.update(
UserSQLiteOpenHelper.DATABASE_TABLE_USER,
values, selection, selectionArgs);
break;
default:
throw new UnsupportedOperationException("Update not supported on uri:"+uri);
}
if(result > 0){
mContentResolver.notifyChange(URI_USER, null);
}
return result;
}
}
ContentResolver訪問共享數(shù)據(jù)
先看效果圖:
該應用通過ContentResolver訪問另一個應用ContentProvider中的共享數(shù)據(jù),并就行刪除、添加、更改操作。監(jiān)聽數(shù)據(jù)庫等變化更新UI界面。
1 訪問數(shù)據(jù)庫:
private void refreshList() {
// 查詢數(shù)據(jù),更新到listview上
Cursor cursor = mContentResolver.query(URI_USER, null, null, null, "modifyTime desc");
if (null != cursor) {
List<UserBean> userList = new ArrayList<UserBean>();
while (cursor.moveToNext()) {
UserBean user = new UserBean();
user.set_id(cursor.getLong(cursor.getColumnIndex(COL_ID)));
user.setName(cursor.getString(cursor.getColumnIndex(COL_NAME)));
user.setPwd(cursor.getString(cursor.getColumnIndex(COL_PWD)));
user.setModifyTime(cursor.getLong(cursor.getColumnIndex(COL_TIME)));
userList.add(user);
}
mUserDataList = userList;
// 刷新listview
mAdapter.notifyDataSetChanged();
cursor.close();
}
}
2 添加數(shù)據(jù)
ContentValues values = new ContentValues();
values.put(COL_NAME, name);
values.put(COL_PWD, pwd);
values.put(COL_TIME, System.currentTimeMillis());
// 添加數(shù)據(jù)
Uri uri = mContentResolver.insert(URI_USER, values);
3 刪除數(shù)據(jù)
// 刪除數(shù)據(jù) 返回值表示刪除的行數(shù)
int result = mContentResolver.delete(URI_USER, "_id = ?", new String[] { id + "" });
4 修改數(shù)據(jù)
String name = nameEt.getText().toString();
String pwd = pwdEt.getText().toString();
if (pwd.length() > 0 || name.length() > 0) {
long id = mUserDataList.get(position).get_id();
ContentValues values = new ContentValues();
if (name.length() > 0) {
values.put(COL_NAME, name);
} else {
values.put(COL_PWD, pwd);
}
values.put(COL_TIME, System.currentTimeMillis());
// 修改數(shù)據(jù) 返回值表示修改的行數(shù)
int result = mContentResolver.update(URI_USER, values, "_id = ?", new String[] { id + "" });
Log.i(TAG, "update id=" + id + ",result=" + result);
}
5 監(jiān)聽數(shù)據(jù)庫變化
mContentObserver = new MyContentObserver(new Handler());
mContentResolver = mContext.getContentResolver();
//監(jiān)聽數(shù)據(jù)庫的變化
mContentResolver.registerContentObserver(URI_USER, true, mContentObserver);
class MyContentObserver extends ContentObserver {
public MyContentObserver(Handler handler) {
super(handler);
}
// 數(shù)據(jù)庫變化會回調(diào)該方法
@Override
public void onChange(boolean selfChange) {
super.onChange(selfChange);
Log.i(TAG, "onChange selfChange=" + selfChange);
refreshList();
}
}
6 取消監(jiān)聽數(shù)據(jù)庫變化
// 取消監(jiān)聽數(shù)據(jù)庫的變化
mContentResolver.unregisterContentObserver(mContentObserver);
相關(guān)文章:
Android 四大組件(一)Activity
Android 四大組件(二)Service
歡迎關(guān)注朋永的簡書!
歡迎掃一掃關(guān)注我的微信公眾號,不定期推送優(yōu)質(zhì)技術(shù)文章:
歡迎關(guān)注