描述
为了在应用程序之间交换数据,Android提供了ContentProvider,它是不同应用程序之间进行数据交换的标准API,当一个应用程序需要把自己的数据暴露给其他程序使用时,该应用程序就可通过提供ContentProvider来实现;其他应用程序就可通过ContentResolver来操作ContentResolver暴露的数据。
ContentProvider以指定Uri的形式对外提供数据,允许其他应用访问或修改数据;其他应用程序使用ContentResolver根据Uri去访问操作指定数据。
一旦某个应用程序通过 ContentProvider 暴露了自己的数据操作接口,那么不管该应用程序是否启动,其他应用程序都可通过该接口来操作该应用程序的内部数据,包括增加数据、删除数据、修改数据、查询数据等。
从源码分析ContentProvider的初始化
App进程启动 -> ActivityThread#main() -> ActivityThread#attach() -> ActivityManagerNative#attachApplication() ->
ActivityManagerService#attachApplication() -> ActivityManagerService#generateApplicationProvidersLocked() 。
generateApplicationProvidersLocked()这个方法通过 PackageManager 去获取解析后的应用的清单文件中 provider 信息,为每个 provider 新建 ContentProviderRecord 作为 ActivityManagerService 端的 ContentProvider 表现。
即ContentProvider的 onCreate()方法在ActivityThread#main()运行时间接调用,即ContentProvider是在APP启动的时候就初始化了。运行在主线程,不能做耗时的操作。
ContentProvider应用内数据共享
1、创建数据库类(使用数据库做数据共享)
public class DbHelper extends SQLiteOpenHelper {
// 数据库名
private static final String DATABASE_NAME = "pkqup.db";
// 数据库版本号
private static final int DATABASE_VERSION = 1;
// 表名
public static final String USER_TABLE_NAME = "user";
public static final String ADDRESS_TABLE_NAME = "address";
public static final String SPECIAL_CHARACTER = "/#";
//在构造方法中指定数据库的 数据库名 和 数据库版本号
public DbHelper(Context context) {
super(context, DATABASE_NAME, null, DATABASE_VERSION);
}
@Override
public void onCreate(SQLiteDatabase db) {
// 创建两个表格:用户表 和地址表
try {
db.execSQL("CREATE TABLE IF NOT EXISTS " + USER_TABLE_NAME + " (" + User.INDEX + " INTEGER PRIMARY KEY AUTOINCREMENT," + User.NAME + " TEXT, " + User.USER_ID + " TEXT)");
db.execSQL("CREATE TABLE IF NOT EXISTS " + ADDRESS_TABLE_NAME + " (" + Address.INDEX + " INTEGER PRIMARY KEY AUTOINCREMENT,"+ Address.NAME + " TEXT, " + Address.PHONE + " TEXT)");
} catch (Exception e) {
e.printStackTrace();
}
}
@Override
public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
try {
//数据库版本发生变化,删除旧表
db.execSQL("DROP TABLE IF EXISTS " + USER_TABLE_NAME);
db.execSQL("DROP TABLE IF EXISTS " + ADDRESS_TABLE_NAME);
} catch (Exception e) {
e.printStackTrace();
}
}
}
2、自定义 ContentProvider 类
public class MyContentProvider extends ContentProvider {
private DbHelper dbHelper;
private SQLiteDatabase db;
private ContentResolver resolver;
// UriMatcher类使用:在ContentProvider 中注册URI
private static final UriMatcher uriMatcher;
// 用户定义列名->数据库列名的映射
private static final HashMap<String, String> userHashMap;
private static final HashMap<String, String> addressHashMap;
// 定义ContentProvider的授权信息,即唯一标识
public static final String AUTHORITY = "com.pkqup.android.note";
// 定义Uri匹配返回码
public static final int USER_CODE = 1;
public static final int USER_CODE_SINGLE = 2;
public static final int ADDRESS_CODE = 3;
public static final int ADDRESS_CODE_SINGLE = 4;
// 设置URI
// Uri uri = Uri.parse("content://com.carson.provider/User/1")
// 上述URI指向的资源是:名为 com.carson.provider 的 ContentProvider 中表名 为`User` 中的 `id`为1的数据
// 特别注意:URI模式存在匹配通配符* 和 #
// *:匹配任意长度的任何有效字符的字符串
// 以下的URI 表示 匹配provider的任何内容
// content://com.example.app.provider/*
// #:匹配任意长度的数字字符的字符串
// 以下的URI 表示 匹配provider中的table表的所有行
// content://com.example.app.provider/table/#
// 在静态代码块中初始化 UriMatcher
static {
// 初始化 UriMatcher
// 常量UriMatcher.NO_MATCH ,不匹配任何路径的返回码 即初始化时不匹配任何东西
uriMatcher = new UriMatcher(UriMatcher.NO_MATCH);
// 在ContentProvider 中注册URI(即addURI())
uriMatcher.addURI(AUTHORITY, DbHelper.USER_TABLE_NAME, USER_CODE);
uriMatcher.addURI(AUTHORITY, DbHelper.USER_TABLE_NAME + DbHelper.SPECIAL_CHARACTER,
USER_CODE_SINGLE);
uriMatcher.addURI(AUTHORITY, DbHelper.ADDRESS_TABLE_NAME, ADDRESS_CODE);
uriMatcher.addURI(AUTHORITY, DbHelper.ADDRESS_TABLE_NAME + DbHelper.SPECIAL_CHARACTER,
ADDRESS_CODE_SINGLE);
// 若URI资源路径 = content://com.pkqup.android.note/user ,则返回注册码USER_CODE,
// 即 mMatcher.match(User.USER_CONTENT_URI)的返回值
// 若URI资源路径 = content://com.pkqup.android.note/address ,则返回注册码ADDRESS_CODE,
// 即 mMatcher.match(Address.ADDRESS_CONTENT_URI)的返回值
userHashMap = new HashMap<>();
userHashMap.put(User.INDEX, User.INDEX);
userHashMap.put(User.NAME, User.NAME);
userHashMap.put(User.USER_ID, User.USER_ID);
addressHashMap = new HashMap<>();
addressHashMap.put(Address.INDEX, Address.INDEX);
addressHashMap.put(Address.NAME, Address.NAME);
addressHashMap.put(Address.PHONE, Address.PHONE);
}
// onCreate()方法在ActivityThread#main()运行时间接调用,即ContentProvider是在APP启动的时候就初始化了。运行在主线程,不能做耗时的操作。
@Override
public boolean onCreate() {
resolver = getContext().getContentResolver();
dbHelper = new DbHelper(getContext());
db = dbHelper.getWritableDatabase();
return true;
}
@Override
public String getType(Uri uri) {
return null;
}
// 注:以下增删改成四个方法的说明:
// 1、下面4个方法由外部进程回调,并运行在ContentProvider进程的Binder线程池中(不是主线程)
// 2、存在多线程并发访问,需要实现线程同步
// 3、若ContentProvider的数据存储方式是使用SQLite &
// 一个,则不需要,因为SQLite内部实现好了线程同步,若是多个SQLite则需要,因为SQL对象之间无法进行线程同步
// 4、若ContentProvider的数据存储方式是内存,则需要自己实现线程同步
@Override
public Uri insert(Uri uri, ContentValues values) {
Uri newUri;
switch (uriMatcher.match(uri)) {
case USER_CODE:
long user_id = db.insert(DbHelper.USER_TABLE_NAME, "", values);
if (user_id < 0) {
throw new SQLiteException("Unable to insert " + values + " for " + uri);
}
newUri = ContentUris.withAppendedId(uri, user_id);
resolver.notifyChange(newUri, null);
break;
case ADDRESS_CODE:
long property_id = db.insert(DbHelper.ADDRESS_TABLE_NAME, "", values);
if (property_id < 0) {
throw new SQLiteException("Unable to insert " + values + " for " + uri);
}
newUri = ContentUris.withAppendedId(uri, property_id);
resolver.notifyChange(newUri, null);
break;
default:
throw new IllegalArgumentException("Error Uri: " + uri);
}
return newUri;
}
@Override
public int delete(Uri uri, String selection, String[] selectionArgs) {
int count;
switch (uriMatcher.match(uri)) {
case USER_CODE:
count = db.delete(DbHelper.USER_TABLE_NAME, selection, selectionArgs);
break;
case USER_CODE_SINGLE:
String booking_id = uri.getPathSegments().get(1);
count = db.delete(DbHelper.USER_TABLE_NAME,
User.INDEX + "=" + booking_id
+ (!TextUtils.isEmpty(selection) ? " AND (" + selection + ')' : ""),
selectionArgs);
break;
case ADDRESS_CODE:
count = db.delete(DbHelper.ADDRESS_TABLE_NAME, selection, selectionArgs);
break;
case ADDRESS_CODE_SINGLE:
String property_id = uri.getPathSegments().get(1);
count = db.delete(DbHelper.ADDRESS_TABLE_NAME,
User.INDEX + "=" + property_id
+ (!TextUtils.isEmpty(selection) ? " AND (" + selection + ')' : ""),
selectionArgs);
break;
default:
throw new IllegalArgumentException("Unnown URI" + uri);
}
getContext().getContentResolver().notifyChange(uri, null);
return count;
}
@Override
public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs) {
int count;
switch (uriMatcher.match(uri)) {
case USER_CODE:
count = db.update(DbHelper.USER_TABLE_NAME, values, selection, selectionArgs);
break;
case USER_CODE_SINGLE:
String booking_id = uri.getPathSegments().get(1);
count = db.update(DbHelper.USER_TABLE_NAME, values,
User.INDEX + "=" + booking_id
+ (!TextUtils.isEmpty(selection) ? " AND (" + selection + ')' : ""),
selectionArgs);
break;
case ADDRESS_CODE:
count = db.update(DbHelper.ADDRESS_TABLE_NAME, values, selection, selectionArgs);
break;
case ADDRESS_CODE_SINGLE:
String property_id = uri.getPathSegments().get(1);
count = db.update(DbHelper.ADDRESS_TABLE_NAME, values,
Address.INDEX + "=" + property_id
+ (!TextUtils.isEmpty(selection) ? " AND (" + selection + ')' : ""),
selectionArgs);
break;
default:
throw new IllegalArgumentException("Unnown URI" + uri);
}
getContext().getContentResolver().notifyChange(uri, null);
return count;
}
@Override
public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs,
String sortOrder) {
Cursor cursor = null;
SQLiteQueryBuilder qb = new SQLiteQueryBuilder();
String orderBy;
switch (uriMatcher.match(uri)) {
case USER_CODE:
qb.setTables(DbHelper.USER_TABLE_NAME);
// 用户定义列名->数据库列名的映射
qb.setProjectionMap(userHashMap);
if (TextUtils.isEmpty(sortOrder)) {
orderBy = User.DEFAULT_SORT_ORDER;
} else {
orderBy = sortOrder;
}
cursor = qb.query(db, projection, selection, selectionArgs, null, null, orderBy);
// 用来为Cursor对象注册一个观察数据变化的URI
cursor.setNotificationUri(getContext().getContentResolver(), uri);
break;
case ADDRESS_CODE:
qb.setTables(DbHelper.ADDRESS_TABLE_NAME);
// 用户定义列名->数据库列名的映射
qb.setProjectionMap(addressHashMap);
if (TextUtils.isEmpty(sortOrder)) {
orderBy = Address.DEFAULT_SORT_ORDER;
} else {
orderBy = sortOrder;
}
cursor = qb.query(db, projection, selection, selectionArgs, null, null, orderBy);
// 用来为Cursor对象注册一个观察数据变化的URI
cursor.setNotificationUri(getContext().getContentResolver(), uri);
break;
}
return cursor;
}
}
3、在AndroidManifest文件中注册创建的 ContentProvider类
<!-- 声明访问ContentProvider的权限-->
<permission
android:name="com.android.pkqup.androidnote.PROVIDER"
android:protectionLevel="normal" />
<provider
android:name=".content_provider_test.MyContentProvider"
//定义授权信息
android:authorities="com.pkqup.android.note"
//设置外部应用可访问
android:exported="true"
//设置自定义权限
android:permission="com.android.pkqup.androidnote.PROVIDER" />
4、进程内访问 ContentProvider的数据
4.1定义实体类
public class User {
public static final String INDEX = "_index";// 主键
public static final String NAME = "name";
public static final String USER_ID = "userId";
// 定义 user 表的 uri
public static final Uri USER_CONTENT_URI =
Uri.parse("content://" + MyContentProvider.AUTHORITY + "/" + DbHelper.USER_TABLE_NAME);
// 定义 user 表的 uri
public static final Uri USER_CONTENT_URI_SINGLE =
Uri.parse("content://" + MyContentProvider.AUTHORITY + "/" + DbHelper.USER_TABLE_NAME
+ DbHelper.SPECIAL_CHARACTER);
// 排序方式,定义为主键排序
public static final String DEFAULT_SORT_ORDER = INDEX;
private String name;
private String userId;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getUserId() {
return userId;
}
public void setUserId(String userId) {
this.userId = userId;
}
}
4.2、定义ContentProvider数据库操作类
public class UserUtils {
private Context context;
public UserUtils(Context context) {
this.context = context;
}
public void insertUser(String name, String age) {
ContentValues values = new ContentValues();
values.put(User.NAME, name);
values.put(User.USER_ID, age);
context.getContentResolver().insert(User.USER_CONTENT_URI, values);
}
public void deleteUser() {
context.getContentResolver().delete(User.USER_CONTENT_URI, null, null);
}
public void updateUser(String name, String age) {
ContentValues values = new ContentValues();
values.put(User.NAME, name);
values.put(User.USER_ID, age);
context.getContentResolver().update(User.USER_CONTENT_URI, values, null, null);
}
public User queryUser() {
User user = new User();
Cursor mCursor = context.getContentResolver().query(User.USER_CONTENT_URI, null, null, null,
User.DEFAULT_SORT_ORDER);
if (null != mCursor) {
while (mCursor.moveToNext()) {
String name = mCursor.getString(mCursor.getColumnIndexOrThrow(User.NAME));
String age = mCursor.getString(mCursor.getColumnIndexOrThrow(User.USER_ID));
user.setName(name);
user.setUserId(age);
}
mCursor.close();
}
return user;
}
// 数据库查询方法的参数说明
public void query(Uri uri, String[] projection, String selection, String[] selectionArgs,
String sortOrder) {
// projection表示要查询哪些列,比如查询媒体库,可能要关注音乐的艺术家,长度,文件位置等。
// selection表示查询条件,就是查询里面的where语句,
// selectionArgs是查询条件的值。
// sortOrder是排序方式。
}
// 根据id查询名称
public String getUserNameFrom(String userId) {
String userName = "";
Cursor mCursor = context.getContentResolver().query(User.USER_CONTENT_URI,
new String[] {User.NAME, User.USER_ID}, "userId=?", new String[] {userId},
User.DEFAULT_SORT_ORDER);
if (null != mCursor) {
while (mCursor.moveToNext()) {
userName = mCursor.getString(mCursor.getColumnIndexOrThrow(User.NAME));
}
mCursor.close();
}
return userName;
}
}
进程间数据共享
1、创建数据库类
见 DbHelper 类。同上。
2、自定义 ContentProvider 类
见 MyContentProvider 类。同上。
3、在AndroidManifest文件中注册创建的 ContentProvider类,注意配置权限和外部可使用属性。
同上。
4、在外部应用中也需要配置相同的权限
<!--自定义上个应用ContentProvider需要的权限-->
<permission
android:name="com.android.pkqup.androidnote.PROVIDER"
android:protectionLevel="normal" />
<!--使用该权限-->
<uses-permission android:name="com.android.pkqup.androidnote.PROVIDER"/>
5、外部应用通过提供的Uri 对ContentProvider进行增删改查。
见 UserUtils 类。同上。