概述
ContentProvider:内容提供者,是Android四大组件之一,为其他app提供数据。其他app可以通过ContentResolver:内容解析器来增,删,查,改相关的数据。ContentProvider主要用于跨进程共享数据,它是一套标准的接口访问,其内部实现可以是SQLiteDatabase,文件,图片索引,媒体索引等。如果你不需要在多个应用间共享数据,可以使用SQLiteDatabase来实现。
ContentProvider简单使用
ContentProvider主要实现以下五个方法
- onCreate:初始化provider时调用
- insert:插入数据时调用
- delete:删除数据时调用
- query:用于查询数据
- update:用于修改数据
ContentProvider内容提供者 简单示例实现如下:
/**
* Created by xujinping on 2017/11/3.
* ContentProvider 简单使用示例
*/
public class MyContentProvider extends ContentProvider {
private static final String TAG = "MyContentProvider";
private final static String DB_NAME = "my_demo.db";
private final String TABLE_NAME = "Demo";
private final int DB_VERSION = 2;
private SQLiteDatabase mDataBase;
private Context mContext;
private static UriMatcher matcher = new UriMatcher(NO_MATCH);
private final static String TABS[] = {
Contract.DEMO_PATH
};
static {
matcher.addURI(Contract.AUTHORITY, Contract.DEMO_PATH, 0);
}
@Override
public boolean onCreate() {
Log.i(TAG, "onCreate: ContentProvider");
mContext = getContext();
SQLDBHelper sqldbHelper = new SQLDBHelper(mContext);
mDataBase = sqldbHelper.getWritableDatabase();
return false;
}
@Nullable
@Override
public Cursor query(@NonNull Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder) {
String tabName = getTabName(uri);
if (TextUtils.isEmpty(tabName)) {
return null;
}
Cursor cursor = mDataBase.query(tabName, projection, selection, selectionArgs, null, null, sortOrder);
return cursor;
}
@Nullable
@Override
public String getType(@NonNull Uri uri) {
return null;
}
@Nullable
@Override
public Uri insert(@NonNull Uri uri, ContentValues values) {
String tabName = getTabName(uri);
if (TextUtils.isEmpty(tabName)) {
return null;
}
long id = mDataBase.insert(tabName, null, values);
Uri resultUri = null;
if (id != -1) {
resultUri = ContentUris.withAppendedId(uri, id);
ContentResolver resolver = mContext.getContentResolver();
resolver.notifyChange(resultUri, null);
}
return resultUri;
}
@Override
public int delete(Uri uri, String selection, String[] selectionArgs) {
String tabName = getTabName(uri);
if (TextUtils.isEmpty(tabName)) {
return -1;
}
int count = mDataBase.delete(tabName, selection, selectionArgs);
return count;
}
@Override
public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs) {
String tabName = getTabName(uri);
if (TextUtils.isEmpty(tabName)) {
return -1;
}
int count = mDataBase.update(tabName, values, selection, selectionArgs);
return count;
}
private String getTabName(Uri uri) {
int code = matcher.match(uri);
if (code == NO_MATCH) {
return null;
}
return TABS[code];
}
private class SQLDBHelper extends SQLiteOpenHelper {
public SQLDBHelper(Context context) {
super(context, DB_NAME, null, DB_VERSION);
}
@Override
public void onCreate(SQLiteDatabase db) {
//第一次安装使用该应用时调用
createDemoTable1(db);
final int firstVersion = 1;//该值是第一个版本的版本号,一直不变即可
onUpgrade(db, firstVersion, DB_VERSION);
}
@Override
public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
//只有数据库版本大于之前数据库版本,才会执行该方法,否者 都执行onCreate方法
for (int i = oldVersion; i < newVersion; i++) {
switch (i) {
case 1://升级为版本2
alertDemoTable1(db);
break;
case 2://升级为版本3
break;
}
}
}
}
private void createDemoTable1(SQLiteDatabase db) {
try {
String sql = "create table " + TABLE_NAME + " ("
+ "id integer primary key autoincrement, "
+ "name text, "
+ "age integer)";
db.execSQL(sql);
Log.i(TAG, "createDemoTable1: 创建demo表成功");
} catch (Exception e) {
Log.i(TAG, "createDemoTable1: 创建demo表失败");
}
}
/**
* 升级数据库
* @param db
*/
private void alertDemoTable1(SQLiteDatabase db) {
try {
db.execSQL("alter table " + TABLE_NAME + " add " + "address" + " text ");
Log.i(TAG, "alertDemoTable1: 升级数据库成功");
} catch (Exception e) {
Log.i(TAG, "alertDemoTable1: 升级数据库失败");
}
}
}
ContentProvider 客户端实现如下
public class ContentProviderDemo extends Activity {
private static final String TAG = "ContentProviderDemo";
private ContentResolver provider;
private TextView mContent;
private int mIndex;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_content_provider);
provider = getContentResolver();
mContent = (TextView) findViewById(R.id.content);
}
public void insert(View view) {
mIndex++;
ContentValues values = new ContentValues();
values.put("name", "王二" + mIndex);
values.put("age", 21 + mIndex);
values.put("address", "珠海" + mIndex);
Uri uri = provider.insert(Contract.DEMO_URI, values);
Log.i(TAG, "insert: uri===" + uri.getPath());
}
public void query(View view) {
StringBuilder sb = new StringBuilder();
Cursor cursor = provider.query(Contract.DEMO_URI, null, null, null, null);
if (cursor != null && cursor.getCount() > 0) {
while (cursor.moveToNext()) {
int nameIndex = cursor.getColumnIndex("name");
int ageIndex = cursor.getColumnIndex("age");
int addressIndex = cursor.getColumnIndex("address");
String name = cursor.getString(nameIndex);
sb.append("name:").append(name);
sb.append(" ");
int age = cursor.getInt(ageIndex);
sb.append("age:").append(age);
sb.append(" ");
String address = cursor.getString(addressIndex);
sb.append("address:").append(address);
sb.append("\n");
}
cursor.close();
mContent.setText(sb.toString());
}
}
}
ContentProvider 共享数据
AndroidManifest.xml配置文件
<--声明其他进程访问该provider所需要的权限!-->
<permission
android:name="com.example.xjp.demo.contentprovider.READ_PROVIDER"
android:protectionLevel="normal" />
<permission
android:name="com.example.xjp.demo.contentprovider.WRITE_PROVIDER"
android:protectionLevel="normal" />
<provider
android:name=".contentprovider.MyContentProvider"
android:authorities="com.example.xjp.demo.contentprovider"
android:exported="true"
android:multiprocess="true"//是否允许多个进程多个实例?默认为false,即只有一个ContentObserver实例
android:readPermission="com.example.xjp.demo.contentprovider.READ_PROVIDER"
android:writePermission="com.example.xjp.demo.contentprovider.WRITE_PROVIDER"/>
如果你想要让其他app能访问你的ContentProvider数据,必须做如下事情:
- 在配置文件中设置ContentProvider 的authorities属性
- 在配置文件中设置ContentProvider 的exported属性为true
- 在配置文件中设置ContentProvider 的读写权限属性
- 在配置文件中注册ContentProvider 的读写权限
为了让第三方应用使用你的ContentProvider,第三方应用应该在AndroidManifest.xml中声明ContentProvider读写权限:
<uses-permission android:name="com.example.xjp.demo.contentprovider.READ_PROVIDER"/>
<uses-permission android:name="com.example.xjp.demo.contentprovider.WRITE_PROVIDER"/>
ContentProvider多线程问题
ContentProvider为多个应用提供数据,那么ContentProvider肯定有多线程访问的问题,那么ContentProvider是不是线程安全的呢?答案是:ContentProvider是线程不安全的。所以如果你的应用中涉及到ContentProvider多线程问题,ContentProvider中insert,delete,update方法必须同步,否则将会出现数据错乱问题。解决方案如下:给这三个方法都加上类锁即可。
@Nullable
@Override
public Uri insert(@NonNull Uri uri, ContentValues values) {
//加类锁
synchronized (MyContentProvider.class) {
String tabName = getTabName(uri);
if (TextUtils.isEmpty(tabName)) {
return null;
}
long id = mDataBase.insert(tabName, null, values);
Uri resultUri = null;
if (id != -1) {
resultUri = ContentUris.withAppendedId(uri, id);
ContentResolver resolver = mContext.getContentResolver();
resolver.notifyChange(resultUri, null);
}
return resultUri;
}
}
@Override
public int delete(Uri uri, String selection, String[] selectionArgs) {
//加类锁
synchronized (MyContentProvider.class) {
String tabName = getTabName(uri);
if (TextUtils.isEmpty(tabName)) {
return -1;
}
int count = mDataBase.delete(tabName, selection, selectionArgs);
return count;
}
}
@Override
public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs) {
//加类锁
synchronized (MyContentProvider.class) {
String tabName = getTabName(uri);
if (TextUtils.isEmpty(tabName)) {
return -1;
}
int count = mDataBase.update(tabName, values, selection, selectionArgs);
return count;
}
}
ContentObserver
ContentObserver用于监听ContentProvider数据变化,我们上面的例子在插入数据时,会通过ContentResolver#notifyChange()方法来发送一个数据更新通知,让外部监听器知道该数据发生变化了。ContentObserver使用示例如下:
provider = getContentResolver();
provider.registerContentObserver(Contract.DEMO_URI, true, new MyContentObserver(new Handler()));
private class MyContentObserver extends ContentObserver {
public MyContentObserver(Handler handler) {
super(handler);
}
@Override
public void onChange(boolean selfChange, Uri uri) {
Log.i(TAG, "onChange: uri====" + uri.toString());
}
}
ContentProvider 初始化时机
ContentProvider初始化时机有点神秘,在app起来了就已经初始化了,但是也不知道具体什么时机?有谁来初始化它?我们可以根据堆栈来看看
右上图可以看出,ContentProvider 是在创建应用的Application时由ActivityThread创建的。所以app一起动时,该app的所有ContentProvider都初始化了。
注意:在ContentProvider 的onCreate初始化方法中不要做耗时操作,否则会影响整个app的启动速度