Content Provider简介
1.ContentProvider是android四大组件之一,需要在AndroidManifest.xml中进行配置.
2.为了在应用程序之间交换数据,android提供了ContentProvider,是不同应用程序之间进行数据交换的标准API.
3.当应用程序需要把自己数据暴露给其他程序时,就可以通过提供的ContentProvider来实现.
4.其他程序通过ContentResolver根据Uri去访问操作ContentProvider暴露的数据.
Content Provider开发
1.继承ContentProvider,实现query(),insert(),update()和delete()方法
2.在AndroidManifest.xml文件中注册该ContentProvider,指定android:authorities
<application>
<provider
android:name=".xxxProvider"
android:authorities="com.example.provider"
android:exported="true"/>
</application>
Content Provider使用
scheme:协议,不仅包括传统的http等网络协议,还有content://来表示本地ContentProvider所提供的数据。
authority:域名部分,包括host和port,host是主机名称,port是通信端口。Android系统就是由这个部分来找到操作哪个ContentProvider。
path:资源路径(数据部分),当访问者需要访问不同资源时,这个部分是动态改变的。
Android为多媒体提供的ContentProvider的Uri如下:
MediaStore.Audio.Media.EXTERNAL_CONTENT_URI:存储在外置SD卡上音频内容的URI
MediaStore.Audio.Media.INTERNAL_CONTENT_URI:存储在内置SD卡上音频内容的URI
MediaStore.Images.Media.EXTERNAL_CONTENT_URI:存储在外置SD卡上图片文件的URI
MediaStore.Images.Media.INTERNAL_CONTENT_URI:存储在内置SD卡上图片文件的URI
MediaStore.Video.Media.EXTERNAL_CONTENT_URI:存储在外置SD卡上视频内容的URI
MediaStore.Video.Media.INTERNAL_CONTENT_URI:存储在内置SD卡上视频内容的URI
Android系统对联系人管理ContentProvider的几个Uri如下:
ContactsContract.Contacts.CONTENT_URI:管理联系人的Uri
ContactsContract.CommonDataKinds.Phone.CONTENT_URI:管理联系人电话的Uri
ContactsContract.CommonDataKinds.Email.CONTENT_URI:管理联系人Email的Uri
- ContentResolver根据Uri去访问操作ContentProvider数据,ContentResolver方法如下:
insert(Uri url, ContentValues values): 向Uri对应的ContentProvider中插入values对应的数据
delete(Uri url, String where, String[] selectionArgs): 删除Uri对应的ContentProvider中where提交匹配的数据
update(Uri uri, ContentValues values, String where, String[] selectionArgs): 更新Uri对应的ContentProvider中where提交匹配的数据
query(Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder): 查询Uri对应的ContentResolver中where提交匹配的数据
- ContentResolver的增删改查方法中会调用ContentProvider对应的增删改查方法,ContentProvider方法如下:
onCreate(): 在创建ContentProvider时调用
insert(Uri uri, ContentValues values): 用于添加数据到指定Uri的ContentProvider中
delete(Uri uri, String selection, String[] selectionArgs): 用于从指定Uri的ContentProvider中删除数据
update(Uri uri, ContentValues values, String selection, String[] selectionArgs): 用于更新指定Uri的ContentProvider中的数据
query(Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder): 用于查询指定Uri的ContentProvider,返回一个Cursor
getType(Uri uri): 用于返回指定的Uri中的数据的MIME类型
- ContentResolver与ContentProvider关系图
CRUD(增删改查)方法的第一个参数都是Uri
Uri是ContentResolver和ContentProvider进行数据交换的标识
Content Provider监听
1.继承ContentObserver基类,并重写onChange(boolean selfChange)方法
2.通过ContentResolver向指定Uri注册ContentObserver监听器,在不需要时,需要对监听器取消注册
Content Provider应用实例
package com.example.provider;
import android.content.Context;
import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteOpenHelper;
public class DatabaseHelper extends SQLiteOpenHelper {
private static final String DATABASE_NAME = "example.db";
private static final int DATABASE_VERSION = 1;
public DatabaseHelper(Context context) {
super(context, DATABASE_NAME, null, DATABASE_VERSION);
}
@Override
public void onCreate(SQLiteDatabase db) {
String sql = "CREATE TABLE user(_id integer primary key autoincrement,name varchar(10),phone varchar(11) NULL)";
db.execSQL(sql);
}
@Override
public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {}
}
- 创建ContentProvider 来对数据库进行共享
package com.example.provider;
import android.content.ContentProvider;
import android.content.ContentUris;
import android.content.ContentValues;
import android.content.UriMatcher;
import android.database.Cursor;
import android.database.sqlite.SQLiteDatabase;
import android.net.Uri;
public class UserProvider extends ContentProvider {
private DatabaseHelper mHelper;
private static final UriMatcher MARCHER = new UriMatcher(UriMatcher.NO_MATCH);
private static final int USERS = 1;
private static final int USER = 2;
private static final String AUTHORITY = "com.example.provider";
private static final String DATABASE_TABLE = "user";
/*
UriMatcher工具类提供了2个方法用来确定内容提供者实际能处理的Uri:
1>.void addRUI(String authority,String path,int code):用于向UriMathcher对象注册Uri.authority和path组合成一个Uri,而code则代表该Uri对应的标识符.
2>.int match(Uri uri):根据前面注册的Uri来判断指定uri对应的标识符,如果找不到匹配的标识码就返回-1.
*/
static {
MARCHER.addURI(AUTHORITY, "user", USERS);
MARCHER.addURI(AUTHORITY, "user/#", USER);
}
// 该方法用于返回当前Uri所代表数据的MIME类型
// 如果操作的数据数据集合类型,MIME类型字符串应该以vnd.android.cursor.dir/开头
// 如果要操作的数据属于非集合类型数据,那么MIME类型字符串应该以vnd.android.cursor.item/开头
@Override
public String getType(Uri uri) {
switch (MARCHER.match(uri)) {
case USERS:
return "vnd.android.cursor.dir/user";
case USER:
return "vnd.android.cursor.item/user";
default:
throw new IllegalArgumentException("Wrong Uri!");
}
}
@Override
public boolean onCreate() {
mHelper = new DatabaseHelper(getContext());
return true;
}
@Override
public Uri insert(Uri uri, ContentValues values) {
SQLiteDatabase db = mHelper.getWritableDatabase();
long _id = db.insert(DATABASE_TABLE, null, values);
if (_id > 0)
sendNotifyChange(uri);
return ContentUris.withAppendedId(uri, _id);
}
@Override
public int delete(Uri uri, String selection, String[] selectionArgs) {
SQLiteDatabase db = mHelper.getWritableDatabase();
int count = db.delete(DATABASE_TABLE, selection + "=?", selectionArgs);
if (count > 0)
sendNotifyChange(uri);
return count;
}
@Override
public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs) {
SQLiteDatabase db = mHelper.getWritableDatabase();
int count = db.update(DATABASE_TABLE, values, selection + "=?", selectionArgs);
if (count > 0) {
String name = values.getAsString("name");
sendNotifyChange(Uri.withAppendedPath(uri, name));
}
return count;
}
@Override
public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs,
String sortOrder) {
SQLiteDatabase db = mHelper.getReadableDatabase();
Cursor mCursor = db.query(DATABASE_TABLE, projection, selection + "=?", selectionArgs,
null, null, sortOrder);
return mCursor;
}
private void sendNotifyChange(Uri uri) {
getContext().getContentResolver().notifyChange(uri, null);
}
}
<application
<provider
android:name="com.example.provider.UserProvider"
android:authorities="com.example.provider"
android:exported="true" />
</application>
- 使用Content Provider操作并监听数据
package com.example.providerdemo;
import android.app.Activity;
import android.content.ContentResolver;
import android.content.ContentValues;
import android.content.Context;
import android.database.ContentObserver;
import android.database.Cursor;
import android.net.Uri;
import android.os.Bundle;
import android.os.Handler;
import android.os.Message;
import android.view.View;
import android.widget.EditText;
import android.widget.Toast;
public class MainActivity extends Activity {
private static final Uri USER_URI = Uri.parse("content://com.example.provider/user");
private ContentResolver mResolver;
private Context mContext;
private EditText name, phone;
private Handler mHandler = new Handler() {
@Override
public void handleMessage(Message msg) {
if (msg.what == 0x123) {
show("Date Changed!");
}
}
};
private ContentObserver observer = new ContentObserver(mHandler) {
@Override
public void onChange(boolean selfChange) {
mHandler.sendEmptyMessage(0x123);
}
};
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
mResolver = getContentResolver();
mContext = MainActivity.this;
name = (EditText) findViewById(R.id.name);
phone = (EditText) findViewById(R.id.phone);
Uri uri = Uri.withAppendedPath(USER_URI, "xiaoming");
mResolver.registerContentObserver(uri, false, observer);
}
@Override
protected void onDestroy() {
super.onDestroy();
mResolver.unregisterContentObserver(observer);
}
public void insert(View view) {
ContentValues values = new ContentValues();
values.put("name", name.getText().toString());
values.put("phone", phone.getText().toString());
Uri result = mResolver.insert(USER_URI, values);
if (result != null) {
show("Insert success!");
}
}
public void delete(View view) {
String whereClause = "name";
String[] whereArgs = {name.getText().toString()};
int result = mResolver.delete(USER_URI, whereClause, whereArgs);
if (result > 0) {
show("Delete success!");
}
}
public void update(View view) {
ContentValues values = new ContentValues();
values.put("name", name.getText().toString());
values.put("phone", phone.getText().toString());
String whereClause = "name";
String[] whereArgs = {name.getText().toString()};
int result = mResolver.update(USER_URI, values, whereClause, whereArgs);
if (result > 0) {
show("Update success!");
}
}
public void query(View view) {
String[] columns = {"_id", "name", "phone"};
String whereClause = "name";
String[] whereArgs = {name.getText().toString()};
Cursor result = mResolver.query(USER_URI, columns, whereClause, whereArgs, null);
if (result.moveToFirst()) {
String mPhone = result.getString(result.getColumnIndex("phone"));
show("query success. Phone:" + mPhone);
}
}
private void show(String str) {
Toast.makeText(mContext, str, Toast.LENGTH_SHORT).show();
}
}
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical" >
<EditText
android:id="@+id/name"
android:layout_width="match_parent"
android:layout_height="wrap_content" />
<EditText
android:id="@+id/phone"
android:layout_width="match_parent"
android:layout_height="wrap_content" />
<Button
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:onClick="insert"
android:text="Insert" />
<Button
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:onClick="delete"
android:text="Delete" />
<Button
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:onClick="update"
android:text="Update" />
<Button
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:onClick="query"
android:text="Query" />
</LinearLayout>
Content Provider实现原理
第一次访问Content Provider时启动
原理概要
1.Content Provider组件将要传输的共享数据抽象为一个游标
2.Content Provider组件通过Binder进程间通信机制来突破应用程序为边界的权限控制
3.以匿名共享内存作为数据传输媒介,从而提供了一种高效的数据共享方式
概要流程
xxActivity组件在请求xxProvider组件返回信息之前,首先在当前应用程序创建一个CursorWindow对象,CursorWindow内部包含了一块匿名共享内存。
通过Binder进程间通信机制将所创建的CursorWindow对象(连同它内部的匿名共享内存)传递给xxProvider组件。
xxProvider组件获得了xxActivity组件发送过来的CursorWindow对象之后,就会创建一个SQLiteCursor对象
通过调用setWindow将CursorWindow对象保存在父类的成员变量mWindow中。
SQLiteCursor对象创建完成之后,xxProvider组件就会将xxActivity组件所请求的数据保存在这个SQLiteCursor对象中
实际上是保存在与它所关联的CursorWindow对象内部的一块匿名共享内存中
xxActivity可以访问这块匿名共享内存,因此可以通过这块内存来获取xxProvider组件返回给它的数据。
SQLiteCursor对象并不是一个Binder本地对象,xxProvider组件不能直接将它返回给xxActivity组件使用。
xxProvider组件首先会创建一个CursorToBulkCursorAdaptor对象,用来适配前面创建的SQLiteCursor对象,并将这个对象保存在mCursor中
然后将CursorToBulkCursorAdaptor对象返回给xxActivity组件。
xxActivity组件收到xxProvider组件返回数据之前,除了创建CursorWindow对象外,还会创建一个BulkCursorToCursorAdaptor对象
并将CursorWindow对象保存在它的父类AbstractWindowedCursor的成员变量mWindow中。
xxActivity组件收到xxProvider组件返回的CursorToBulkCursorAdaptor对象之后,实际上获得的是CursorToBulkCursorAdaptor的代理对象
并将它保存BulkCursorToCursorAdaptor对象的mBulkCursor中,这时候xxActivity组件就可以通过这个BulkCursorToCursorAdaptor对象来读取xxProvider组件返回的数据了。
SQLiteCursor类实现关系图
注意:
CursorWindow包含了一块匿名共享内存
Content Provider 数据共享模型
注意:
CursorWindow引用了同一块匿名共享内存
实现原理结构图
原理概要
1.ContentProvider组件的数据更新通知机制类似于Android系统的广播机制,都是一种消息发布和订阅的事件驱动模型。
2.内容观察者ContentObserver负责接收数据更新通知,ContentProvider组件负责发送数据更新通知。
3.内容观察者ContentObserver在接收到通知之前,必须要注册到ContentService中,通过URI来描述需要接收什么样的数据更新通知。
注册ContentObserver
注意:
1.真正注册到ContentService中的并不是一个ContentObserver,而是与这个ContentObserver所关联的binder本地对象Transport(ContentObserver contentObserver)
2.mRootNode保存内容观察者
发送数据改变通知
回调onChange方法
数据监听结构图