ContentProvider基本用法:
什么叫ContentProvider?使用场景?
ContentProvider(内容提供器),用于在不同应用程序之间实现数据共享的功能,它提供了一套完整的机制,允许一个程序访问另一个程序中的数据,同时还能保证被访数据的安全性。
如何实现ContentProvider?如何访问?
1、创建自己的数据列表;
2、自定义ContentProvider实现相关的抽象方法;
3、在AndroidManifest中声明provider以及定义相关访问权限;
4 、通过ContentResolver根据URI进行增删改查。
------------------------ContentProvider是一个抽象类,如果开发自己的ContentProvider就需要继承这个类并复写其方法,需要实现的方法有:
public boolean onCreate() //在创建ContentProvider时使用
public Cursor query() //用于查询指定uri的数据返回一个Cursor
public Uri insert() //用于向指定uri的ContentProvider中添加数据
public int delete() //用于删除指定uri的数据
public int update() //用户更新指定uri的数据
public String getType() //用于返回指定的Uri中的数据MIME类型
数据访问的方法insert,delete和update可能被多个线程同时调用,此时必须是线程安全的(是否需要注意加入类似synchronized关键字实现线程安全?)
----------------------------URI(Universal Resource Identifier)统一资源定位符,如果您使用过安卓的隐式启动就会发现,在隐式启动的过程中我们也是通过uri来定位我们需要打开的Activity,并且可以在uri中传递参数。URI的格式如下:
schema: Android中固定为content://;
authority: 用于唯一标识一个ContentProvider,一般是com.example.app.provider这种形式;
path: ContentProvider中数据表的表名;
id: 数据表中数据的标识,可选字段
------------------------------自定义一个PeopleDataBaseProvider 的类用来共享该应用的数据:
package com.example.demodatabase;
import android.content.ContentProvider;
import android.content.ContentValues;
import android.content.UriMatcher;
import android.database.Cursor;
import android.database.sqlite.SQLiteDatabase;
import android.net.Uri;
import android.util.Log;
import java.util.concurrent.ThreadPoolExecutor;
public class PeopleDataBaseProvider extends ContentProvider {
public static final String TAG = "PeopleDataBaseProvider";
public static final String TABLE_PEOPLE_PATH_ALL_LINE = "People";
public static final String TABLE_PEOPLE_PATH_SINGLE_LINE = "People/#";
public static final int PEOPLE_DIR = 0;
public static final int PEOPLE_ITEM = 1;
public static final String AUTHORITY = "com.example.demodatabase.provider";
private static UriMatcher uriMatcher;
private MyDatabaseHelper mDbHelper;
static {
uriMatcher = new UriMatcher(UriMatcher.NO_MATCH);
/**
* 通过uriMatcher.addURI()将authority(包名.provider)、表名/可选的数据表中的标识与匹配数字(MATCH_CODE)匹配,并add
*/
uriMatcher.addURI(AUTHORITY, TABLE_PEOPLE_PATH_ALL_LINE, PEOPLE_DIR);
uriMatcher.addURI(AUTHORITY, TABLE_PEOPLE_PATH_SINGLE_LINE, PEOPLE_ITEM);
}
@Override
public boolean onCreate() {
/**
* 获取DatabaseHelper帮助类实例
*/
mDbHelper = MyDatabaseHelper.getInstance(getContext());
return true;
}
@Override
public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder) {
final String[] xProjection = projection;
final String xSelection = selection;
final String[] xSelectionArgs = selectionArgs;
final String xSortOrder = sortOrder;
final SQLiteDatabase db = mDbHelper.getReadableDatabase();
final Cursor xCursor = null;
//匹配对应的uri并进行相应的操作
switch (uriMatcher.match(uri)){
case PEOPLE_DIR:
ThreadPoolExecutors.getInstance().execute(new Runnable() {
@Override
public void run() {
Cursor cursor = xCursor;
try {
cursor = db.query(Constant.TABLE_NAME_PEOPLE, xProjection, xSelection, xSelectionArgs, null,null, xSortOrder);
} catch (Exception e) {
Log.e(TAG, Log.getStackTraceString(e));
}finally {
try {
cursor.close();
} catch (Exception e) {
Log.e(TAG, Log.getStackTraceString(e));
}
}
}
}, ThreadPoolExecutors.ThreadType.FIXED_THREAD);
break;
case PEOPLE_ITEM:
final String peopleId = uri.getPathSegments().get(1);
ThreadPoolExecutors.getInstance().execute(new Runnable() {
@Override
public void run() {
Cursor cursor = xCursor;
try {
cursor = db.query(Constant.TABLE_NAME_PEOPLE, xProjection, "id = ?", new String[]{peopleId}, null, null, xSortOrder);
} catch (Exception e) {
Log.e(TAG, Log.getStackTraceString(e));
}finally {
try {
cursor.close();
} catch (Exception e) {
Log.e(TAG, Log.getStackTraceString(e));
}
}
}
}, ThreadPoolExecutors.ThreadType.FIXED_THREAD);
}
return xCursor;
}
public PeopleDataBaseProvider() {
}
@Override
public int delete(Uri uri, String selection, String[] selectionArgs) {
// Implement this to handle requests to delete one or more rows.
throw new UnsupportedOperationException("Not yet implemented");
}
@Override
public String getType(Uri uri) {
// TODO: Implement this to handle requests for the MIME type of the data
// at the given URI.
throw new UnsupportedOperationException("Not yet implemented");
}
@Override
public Uri insert(Uri uri, ContentValues values) {
// TODO: Implement this to handle requests to insert a new row.
throw new UnsupportedOperationException("Not yet implemented");
}
@Override
public int update(Uri uri, ContentValues values, String selection,
String[] selectionArgs) {
// TODO: Implement this to handle requests to update one or more rows.
throw new UnsupportedOperationException("Not yet implemented");
}
}
- 需要重写public boolean onCreate()、public Cursor query()、public Uri insert()、public int delete()、public int update()、public String getType()
- 在static静态代码块通过uriMatcher.addURI()将authority(包名.provider)、表名/可选的数据表中的标识与匹配数字(MATCH_CODE)匹配,并add
- 在onCreate()中获取DatabaseHelper帮助类实例
- 在相应重写的函数中实现增、删、改、查,在query、insert和delete方法中都是先调用uriMatcher.match(uri) 判断当前uri是不是匹配
其中以ContentProvider的public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder)为例:
(注意这里的query是以Cursor为返回值,什么是Cursor?Cursor 是每行的集合。使用 moveToFirst() 定位第一行。你必须知道每一列的名称。你必须知道每一列的数据类型。Cursor 是一个随机的数据源。所有的数据都是通过下标取得)
uri:确定查询哪张表
projection:指定查询的列名
selection:查询哪些行(指定where约束条件)
selectionArgs:行关键字、范围(为where的占位符提供具体的值)
sortOrder:对结果的排序方式
--------------------在xml文件中声明:
<provider
android:name=".PeopleDataBaseProvider"
android:authorities="com.example.demodatabase.provider"
android:enabled="true"
android:exported="true"/>
----------------------在数据库发生变化的时候系统框架层会自动调用notifyChange方法
通过ContentResolver获取数据:
getContentResolver().query(uri, projection, selection, selectionArgs, sortOrder);
uri:确定查询哪张表
projection:指定查询的列名
selection:查询哪些行(指定where约束条件)
selectionArgs:行关键字、范围(为where的占位符提供具体的值)
sortOrder:对结果的排序方式
private static final String URI = "content://com.example.demodatabase.provider/People";
final Uri uri = Uri.parse(URI);
Cursor cursor = null;
cursor = getContentResolver().query(uri, null, null, null, null, null);
ContentProvider的安全使用?
-------------通过在AndroidManifest.xml文件中指定android:exported属性为true,可以设置将ContentProvider公开给其它应用程序。对于API Level低于17的Android应用程序,Content Provider默认是public的,除非显式指定android:exported=“false”。例如:
<provider
android:exported="true"
android:name="MyContentProvider"
android:authorities="com.example.mycontentprovider" />
如果一个Content Provider为公开的,Content Provider中存储的数据就可以被其它应用程序访问。因此,它只能用于处理非敏感信息。
-------------通过在AndroidManifest. xml文件中指定android:exported属性为false,可以设置将Content Provider设置为私有的。从API Level17及以后,Content Provider默认是私有的,除非显式指定android:exported=“true”。例如:
<provider
android:exported="false"
android:name="MyContentProvider"
android:authorities="com.example.mycontentprovider" />
-------------通过声明权限,并且定义ContentProvider其protectionLevel,限制部分应用访问
ContentReceiver访问是受限的,只有满足权限要求的才能通信
在AndroidManifest中声明provider以及定义相关访问权限
在注册ContentProvider的时候通过android:process属性设置provider运行在单独的进程里,模拟进程间通信。
<!-- student provider 访问权限声明 -->
<permission
android:name="com.android.peter.provider.READ_PERMISSION"
android:label="Student provider read permission"
android:protectionLevel="normal"
/>
<permission
android:name="com.android.peter.provider.WRITE_PERMISSION"
android:label="Student provider read permission"
android:protectionLevel="normal"
/>
<!-- 声明ContentProvider -->
<application
...
<provider
android:name=".StudentContentProvider"
android:authorities="com.android.peter.provider"
android:readPermission="com.android.peter.provider.READ_PERMISSION"
android:writePermission="com.android.peter.provider.WRITE_PERMISSION"
android:process=":provider"
android:exported="true"/>
...
</application>
为了方便起见,权限声明时protectionLevel设置的是最低风险权限(normal),关于其他等级权限和说明如下:
权限等级说明:
使用带权限的ContentProvider
假如应用B要使用应用A中带权限的ContentProvider,需要在应用B的AndroidManifest.xml中加入权限的使用;
<uses-permission android:name="com.example.demodatabase.peopleDataBaseProvider.WRITE_PERMISSION"/>
<uses-permission android:name="com.example.demodatabase.peopleDataBaseProvider.READ_PERMISSION"/>
其中,标签中设置的android:name的值,就是应用A中对外声明的那个provider的权限值。
数据监听?
1、监听步骤
要监听指定URI上的数据库变化,在监听方需要两步:
(1) 生成一个类派生自ContentObserver(一般将ContentObserver定义成一个内部类或者匿名内部类)
public class DataBaseObserver extends ContentObserver {
public DataBaseObserver(Handler handler) {
super(handler);
}
@Override
public void onChange(boolean selfChange) {
super.onChange(selfChange);
Log.d("harvic","received first database changed");
}
}
注意这里会自动生成一个onChange()方法,当有改变到来时就会跑到onChange()里,所以我们在这里面打一个LOG,注意LOG内容:“received first database changed”,很明显这里我只用DataBaseObserver来监听"content://com.harvic.provider.PeopleContentProvider/frist "所对应的数据库进行监听,至于如何指定URI往下看
(2) 注册和反注册监听
-------注册:
在使用URI来操作第三方数据之前,先进行ContentObserver注册,这里我们在MainActivity的OnCreate()函数里注册:
private DataBaseObserver observer;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
observer = new DataBaseObserver(new Handler());
//注册指定URI 变动监听
getContentResolver().registerContentObserver(CONTENT_URI_FIRST,true,observer);
}
这里先完整说完流程,等下再讲registerContentObserver()的参数
--------在OnDestory()时,反注册:
@Override
protected void onDestroy() {
super.onDestroy();
//取消监听
getContentResolver().unregisterContentObserver(observer);
}
(3) 在ContentProvider中数据库变动通知
在ContentProvider中指定URI上有数据库数据变动时,框架层会及时回调notifyChange(uri, null),notifyChange会通知所有注册了该Uri的ContentObserver;
也可以:
在ContentProvider中指定URI上有数据库数据变动时,及时利用下面的函数来通知,在子类继承ContentProvider中的query()方法中调用:
getContext().getContentResolver().notifyChange(uri, null);
这个函数就是在第二篇中,我们提到的通知函数;
好了,到这数据库变动通知和监听的过程就讲完了,下面看看registerContentObserver()注册监听函数的用法:
public final void registerContentObserver(Uri uri, boolean notifyForDescendents, ContentObserver observer)
功能:为指定的Uri注册一个ContentObserver派生类实例,当给定的Uri发生改变时,回调该实例对象去处理。
参数:
-------uri:需要观察的Uri
-------notifyForDescendents: 为false 表示精确匹配,即只匹配该Uri; 为true 表示可以同时匹配其派生的Uri
举例如下:假设UriMatcher 里注册的Uri共有一下类型:
1、content://com.qin.cb/student (学生)
2、content://com.qin.cb/student/#
3、content://com.qin.cb/student/schoolchild(小学生,派生的Uri)
假设我们当前需要观察的Uri为content://com.qin.cb/student,如果发生数据变化的 Uri 为 content://com.qin.cb/student/schoolchild;那么当notifyForDescendents为 false,那么该ContentObserver会监听不到, 但是当notifyForDescendents 为ture,能捕捉该Uri的数据库变化。
----------observer: ContentObserver的派生类实例
增删改查注意点?多个进程通过ContentResolver对同一个Uri的ContentProvider进行增删改查的时候,是不是线程安全、原子的?如果不是,有什么方法可以避免?
-
首先不管Provider使用方是同一个进程的不同线程,还是不同的进程,Provider方实际上是同一个Provider对象实例;
-
并发访问时,Provider方query()方法运行在不同的线程,实际上是运行在Provider方的进程的Binder线程池中;
-
Provider方query()(增删改查等方法)方法不是原子操作。Android框架层并没有做什么以使数据操作原子化;
那么如何进行避免?
可以在子类继承ContentProvider重写增、删、改、查方法时,对增、删、改、查方法加锁;