1.为什么需要ContentProvider?
大家都知道,涉及到数据的访问就要考虑到数据的安全性。怎样在保证数据的安全性的同时,又能方便的访问数据呢?
Android是基于Linux的,也继承了Linux的文件管理方式,通常每个应用都是独立的进程,也就是不同的用户。Android为每个应用程序分配了独立的用户ID和用户组ID。并且由这个应用程序创建出来的文件被赋予了相应的读写权限。其他应用程序无权访问。
这样虽然保证了数据的安全性,但是这对数据的共享给第三方造成了不便。Android系统的开发者为了解决了这个问题,设计了ContentProvider类。Content Provider很好的兼顾了二者。
2.怎样使用它?
ContentProvider(内容提供者)
1.创建UserInfoProvider类,它继承自ContentProvider并实现了ContentProvider的六个必须实现的抽象方法。
public class UserInfoProvider extends ContentProvider {
UserDBHelper userDBHelper;
public static final int USER_INFO=1;
public static final int PHONE=2;
public static final String AUTHORITIES="com.hyh.provider";//必须与AndroidManifest.xml里的android:authorities保持一致
public static UriMatcher uriMatcher = new UriMatcher(UriMatcher.NO_MATCH);
static{
uriMatcher.addURI(AUTHORITIES,"user",USER_INFO);
uriMatcher.addURI(AUTHORITIES,"phone",PHONE);
}
@Override
public boolean onCreate() {
Log.e("androidLog","onCreate()");
userDBHelper = UserDBHelper.newInstance(getContext(),"userInfo.db",null,1);
return false;
}
@Nullable
@Override
public Cursor query(@NonNull Uri uri, @Nullable String[] projection, @Nullable String selection, @Nullable String[] selectionArgs, @Nullable String sortOrder) {
Log.e("androidLog","query()");
switch (uriMatcher.match(uri)){
case USER_INFO:
cursor = userDBHelper.query();
//监听改变通知
cursor.setNotificationUri(getContext().getContentResolver(),uri);
break;
case PHONE:
break;
}
return cursor;
}
@Nullable
@Override
public String getType(@NonNull Uri uri) { return null; }
@Nullable
@Override
public Uri insert(@NonNull Uri uri, @Nullable ContentValues values) {
Log.e("androidLog","insert()");
Uri newUri=uri;
switch (uriMatcher.match(uri)){
case USER_INFO:
long rowId=userDBHelper.insert(values);
newUri = ContentUris.withAppendedId(uri,rowId);
getContext().getContentResolver().notifyChange(uri,null);
break;
case PHONE:
break;
}
return newUri;
}
@Override
public int delete(@NonNull Uri uri, @Nullable String selection,@Nullable String[] selectionArgs) {
Log.e("androidLog","delete()");
int rowCount=0;
switch (uriMatcher.match(uri)){
case USER_INFO:
rowCount = userDBHelper.delete(selection,selectionArgs);
getContext().getContentResolver().notifyChange(uri,null);
break;
case PHONE:
break;
}
return rowCount;
}
@Override
public int update(@NonNull Uri uri, @Nullable ContentValues values,@Nullable String selection, @Nullable String[] selectionArgs) {
Log.e("androidLog","update()");
int rowCount=0;
switch (uriMatcher.match(uri)){
case USER_INFO:
rowCount = userDBHelper.update(values,selection,selectionArgs);
getContext().getContentResolver().notifyChange(uri,null);
break;
case PHONE:
break;
}
return rowCount;
}
}
2.接着我们需要注册这个BookProvider,如下所示。其中android:authorities是ContentProvider的唯一标识,通过这个属性外部应用就可以访问我们的UserInfoProvider,因此android:authorities必须是唯一的,这里建议在命名的时候加上包名前缀。
<provider
android:authorities="com.hyh.provider.test"
android:name=".UserInfoProvider"
android:permission="com.hyh.provider.USER"
android:exported="true"
android:process=":abc" />
ContentResolver (内容解析者)
1.ContentResolver(内容解析器)和ContentProvider(内容提供器)是配套使用的,ContentProvider封装的接口要通过ContentResolver来进行使用,ContentResolver中的方法和ContentProvider要实现的方法是对应的。
ContentResolver contentResolver = getContentResolver();
ContentValues contentValues = new ContentValues();
contentValues.put("name","abcdfeg");
Uri uri = Uri.parse("content://com.hyh.provider/user/10");
contentResolver.insert(uri,contentValues);
Cursor cursor = contentResolver.query(uri,null,null,null,null);
while(cursor.moveToNext()){
int id=cursor.getInt(0);
String name = cursor.getString(1);
Log.e("androidLog","id="+id+";name="+name);
}
Uri uri = Uri.parse("content://com.hyh.provider/user/10");
content:// 标准的前缀,用于标识该数据由ContentProvider管理,不需修改。
com.hyh.provider URI的authority部分,用于标识该Content Provider。
user Content Provider的路径部分,用于决定哪类数据被请求(可理解为请求哪个数据库表)。
10 被请求的特定记录的ID
ContentObserver(内容观察者)
getContext().getContentResolver().notifyChange(uri,null);
通知该uri中的内容改了表了,然后调用ContentOberver类中的onChange(boolean selfChange).
Uri uri = Uri.parse("content://sms/");
SmsContentObserver smsContentObserver = new SmsContentObserver(this,new Handler());
getContentResolver().registerContentObserver(uri,true,smsContentObserver);
class SmsContentObserver extends ContentObserver{
Context context;
public SmsContentObserver(Context context, Handler handler) {
super(handler);
this.context = context;
}
@Override
public void onChange(boolean selfChange, Uri uri) {
super.onChange(selfChange, uri);
String selection = String.format("date>%d",System.currentTimeMillis()-1000*60*60);
Cursor cursor = context.getContentResolver().query(uri,null,selection,null,null);
int columnCount = cursor.getColumnCount();
while(cursor.moveToNext()){
for (int i=0;i<columnCount;i++){
String columnName= cursor.getColumnName(i);
int type = cursor.getType(i);
switch (type){
case Cursor.FIELD_TYPE_INTEGER:
int n = cursor.getInt(i);
Log.i("androidLog",columnName+"="+n);
break;
case Cursor.FIELD_TYPE_STRING:
String str = cursor.getString(i);
Log.i("androidLog",columnName+"="+str);
break;
}
}
}
}
}
3.它的原理是什么?
- ContentProvider是通过IBinder实现通信过程的
- getContentResolver获得到的是ApplicationContentResolver(在ContextImpt中实现的)
- Client端ApplicationContentResolver使用ContentProviderProxy作为IBinder的Proxy(ContentProviderNative中实现)
- Provider端通过Transport作为IBinder的实现端(ContentProvider中实现)
4.附加知识有哪些?
注意事项:
- query、update、insert、delete方法都运行在Binder线程中。另外,onCreate运行在main线程中,也就是UI线程,所以我们不能在onCreate中做耗时操作。
- Provider是可以不采用权限而正常使用的。
- 如果provider中使用了权限就必须要有权限声明,否则外部程序即使引用权限也是没有用的。
- 当xml中权限发生改变时,必须要卸载程序重新安装,否则将不会生效。