概述
ContentProvider定义
1. ContentProvider,Android四大组件之一。主要作用是,在不同应用程序 之间进行数据共享。
ContentProvider就像一个中介,其他应用通过这个中介,对指定应用中的数据进行操作。
2. Android系统自带的应用中,提供了一些主要数据的共享,如:联系人、媒体资源(音频、视频、图片等)、
日历数据等。我们也可以自定一个Content Provider(本文主要讲如何自定义Content Provider)。
ContentProvider原理简述
每一个Content Provider组件都是通过一个URI来访问的。
URI:统一资源定位符,标识每个Content Provider,根据指定URI,找到对应的ContentProvider,对相应数据进行操作。
URI格式:[content://][com.way.note][/items][/11]
[content://]: 协议名称,固定为 “content://”。是Content Provider组件的专用访问协议。
[com.way.note]: 是Content Provider组件的 android:authority 属性值。必须保证该值全局唯一性,一般用Content Provider组件的包名来命名。
[/items]: 是一个资源的相对路径。
[/11] : 是一个资源的ID。
实现步骤,实例解析
实例概要
Note应用,是一个记事本。FloatingNote是一个悬浮便签窗口,该悬浮应用通过Content Provider访问Note应用的数据。
实例详解
该实例由两部分组成。
第一:建立了数据库,提供了ContentProvider的“Note记事本”应用。
第二:利用ContentResolver,通过指定的URI,访问对应ContentProvider指定的数据。由“FloatingNote悬浮便签”应用来完成。
第一:Note记事本应用(建立了数据库,提供了ContentProvider)
创建一个数据库,存储用户添加的记事本条目信息。 DBOpenHelper.java
public class DBOpenHelper extends SQLiteOpenHelper
SQLiteOpenHelper类, 用来创建数据库,版本的更新管理。是一个抽象类,所以,要使用它必须实现它的nCreate(SQLiteDatabase),onUpgrade(SQLiteDatabase, int, int)方法。
还有一个方法选用: onOpen() :当每次打开数据库时被调用。 数据库创建完成。
private static final String DB_NAME = "Notes.db";// 数据库名称
public DBOpenHelper(Context context) {
//在构造函数中创建数据库Notes.db
super(context, DB_NAME, null, DB_VERSION);
}
@Override
public void onCreate(SQLiteDatabase db) {
//创建数据表
db.execSQL(" CREATE TABLE IF NOT EXISTS " + TABLE_NAME + " ( " + ID
+ " integer primary key autoincrement , " + NOTE_CONTENT
+ " text , " + RINGTONE_URI + " text ," + RINGTONE_NAME
+ " text ," + ISVIBRATE + " int ," + RINGTONE_DATE + " text ,"
+ RINGTONE_TIME + " text ,"
+ NOTE_ALARM_ENABLE + " integer , " + NOTE_BG_COLOR
+ " integer , " + NOTE_IS_FOLDER + " int , "
+ NOTE_PARENT_FOLDER + " varchar, " + NOTE_TITLE + " text, "
+ NOTE_UPDATE_DATE + " long);");
}
@Override
public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
db.execSQL(" DROP TABLE IF EXISTS " + TABLE_NAME);
onCreate(db);
}
Note记事本UI:
Note记事本对应的数据库:
创建一个Content Provider。 NoteProvider.java
自定义NoteProvider extends ContentProvider,提供了数据操作的接口。对数据的操作主要是增删改查, 要重写ContentProvider提供的方法:query:查询
@Override public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder) { SQLiteQueryBuilder qb = new SQLiteQueryBuilder(); // Generate the body of the query Log.d(TAG, "query() uri = " + uri); int match = sURLMatcher.match(uri); switch (match) { case EVENTS: qb.setTables("items"); break; case EVENTS_ID: qb.setTables("items"); qb.appendWhere("_id="); qb.appendWhere(uri.getPathSegments().get(1)); break; default: throw new IllegalArgumentException("Unknown URL " + uri); } return qb.query(db, projection, selection, selectionArgs, null, null, sortOrder); }
insert:添加
@Override public Uri insert(Uri uri, ContentValues values) { Uri newUrl = null; SQLiteDatabase db = helper.getWritableDatabase(); switch (sURLMatcher.match(uri)) { case EVENTS: long rowId = db.insert("items", DBOpenHelper.NOTE_CONTENT, values); if (rowId < 0) { throw new SQLException("Failed to insert row"); } newUrl = ContentUris.withAppendedId( Uri.parse("content://com.way.note/items"), rowId); NoteDataManagerImpl noteDataManger = (NoteDataManagerImpl) NoteDataManagerImpl .getNoteDataManger(getContext()); noteDataManger.updateCacheFromDB(); break; } return newUrl; }
update:更新
@Override public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs) { int count; long rowId = 0; int match = sURLMatcher.match(uri); SQLiteDatabase db = helper.getWritableDatabase(); switch (match) { case EVENTS_ID: { String segment = uri.getPathSegments().get(1); rowId = Long.parseLong(segment); count = db.update("items", values, "_id=" + rowId, null); break; } default: { throw new UnsupportedOperationException("Cannot update URL: " + uri); } } return count; }
delete:删除
@Override public int delete(Uri uri, String selection, String[] selectionArgs) { SQLiteDatabase db = helper.getWritableDatabase(); int count; long rowId = 0; switch (sURLMatcher.match(uri)) { case EVENTS: count = db.delete("items", selection, selectionArgs); break; case EVENTS_ID: String segment = uri.getPathSegments().get(1); rowId = Long.parseLong(segment); if (TextUtils.isEmpty(selection)) { selection = "_id=" + segment; } else { selection = "_id=" + segment + " AND (" + selection + ")"; } count = db.delete("items", selection, selectionArgs); break; default: throw new IllegalArgumentException("Cannot delete from URL: " + uri); } return count; }
getType:得到数据类型
Android的MIME类型有两种形式:多条记录 单条记录
多条记录:vnd.android.cursor.dir/items
单条记录:vnd.android.cursor.item/items
在使用Intent时,用到MIME。
intent.setType(“vnd.android.cursor.item/items”);
@Override public String getType(Uri uri) { int match = sURLMatcher.match(uri); switch (match) { case EVENTS: return "vnd.android.cursor.dir/items"; case EVENTS_ID: return "vnd.android.cursor.item/items"; default: throw new IllegalArgumentException("Unknown URL"); } }
在这几个函数中,可以看到都有Uri参数的传入。这几个函数中,要执行具体的操作,需要对URI进行匹配。那么URI的匹配是怎么做呢,利用UriMatcher匹配。UriMatcher预先要addURI (String authority, String path, int code)
- authority:ContentProvider的authority参数。
- path:URI格式中authority后面部分。
- code:匹配成功后,返回的值。
private static final UriMatcher sURLMatcher = new UriMatcher( UriMatcher.NO_MATCH); //如果匹配不成功,返回UriMatcher.NO_MATCH static { sURLMatcher.addURI("com.way.note", "items", EVENTS); sURLMatcher.addURI("com.way.note", "items/#", EVENTS_ID); //符号#表示一个任意的数字 }
- 在AndroidManifest.xml中注册该Content Provider
数据库和ContentProvider都建立好了。这时候,要对NoteProvider进行注册:
<provider
android:name=".backup.DBOperations"
android:authorities="com.way.note"
android:exported="true" />
- android:exported: 这个属性用于指示该服务是否能够被其他应用程序组件调用或跟它交互。如果设置为true,则能够被调用或交互,否则不能。
- android:authorities:该值对应URI格式中authority值。
第二:FloatingNote悬浮便签 通过ContentProvider 访问Note记事本应用数据
如何访问ContentProvider提供的数据。
外界的程序通过ContentResolver接口可以访问ContentProvider提供的数据,通过getContentResolver()可以得到当前应用的ContentResolver实例。
public static final Uri CONTENT_URI = Uri.parse(“content://com.way.note/items”);查询: context.getContentResolver().query(CONTENT_URI, null, “(isfilefolder != 1)”, null, null);
添加: context.getContentResolver().insert(CONTENT_URI, cv);
删除: context.getContentResolver().delete(CONTENT_URI, where, null);
修改: context.getContentResolver().update(CONTENT_URI, cv, s4, null)
监听ContentProvider中数据的变化。
如果ContentProvider的访问者需要得到数据变化通知,必须使用ContentObserver对数据(数据采用uri描述)进行监听,当监听到数据变化通知时,系统就会调用ContentObserver的onChange()方法:
context.getContentResolver().registerContentObserver( NotesPopupUtils.CONTENT_URI, true, mObserver); private class NotesPopupContentObserver extends ContentObserver { public NotesPopupContentObserver() { super(handler); } @Override public boolean deliverSelfNotifications() { return true; } @Override public void onChange(boolean selfChange) { handler.sendEmptyMessageDelayed(MSG_UPDATE_GALLERY, 100); } }
如果ContentProvider的访问者需要知道ContentProvider中的数据发生变化,可以在ContentProvider发生数据变化时调用getContentResolver().notifyChange(uri, null)来通知注册在此URI上的访问者。
FloatingNote悬浮便签UI:
第三:ContentProvider使用中权限问题
ContentProvider中,有一些数据是私有的,需要读写权限。这就需要我们在AndroidManifest.xml中添加读写权限。
比如Launcher3的数据库:
1. 申请permission
<permission
android:name="com.philips.launcher3.permission.WRITE_SETTINGS"
android:permissionGroup="android.permission-group.SYSTEM_TOOLS"
android:protectionLevel="signatureOrSystem"
android:label="@string/permlab_write_settings"
android:description="@string/permdesc_write_settings"/>
2. 注册provider时,添加permission
<provider
android:name="com.philips.launcher3.LauncherProvider"
android:authorities="com.philips.launcher3.settings"
android:exported="true"
android:writePermission="com.philips.launcher3.permission.WRITE_SETTINGS"
android:readPermission="com.philips.launcher3.permission.READ_SETTINGS" />
3. 访问ContentProvider中数据的第三方应用,AndroidManifest.xml中添加权限。
```
<uses-permission android:name="com.philips.launcher3.permission.READ_SETTINGS" />
<uses-permission android:name="com.philips.launcher3.permission.WRITE_SETTINGS" />
```