Android四大组件之ContentProvider

2018-01-01  本文已影响12人  AndroidMaster

一、ContentProvider

ContentProvider为存储和获取数据提供统一的接口,可以在不同的应用程序之间共享数据。如果你不需要在多个应用之间共享数据,你可以直接通过SQLite来操作数据库。

使用ContentProvider,主要有以下几个理由:

onCreate() boolean //当第一次访问ContentProvider,创建完对象之后调用,唯一的生命周期方法
insert(Uri uri, ContentValues values) Uri  //根据Uri插入values对应的数据
delete(Uri uri, String selection, String[] selectionArgs) int    //根据Uri删除select条件所匹配的全部记录
update(Uri uri, ContentValues values, String selection, String[] selectionArgs)    int //根据Uri修改select条件所匹配的全部记录
query(Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder) Cursor    //查询,projection:选择的指定的列
getType(Uri uri) String    //返回当前Uri所代表的MIME类型。

二、ContentResolver

Android为我们提供了ContentResolver来统一管理与不同ContentProvider间的操作,通过URI来区别不同的ContentProvider
ContentResolver 类也提供了与ContentProvider类相对应的四个方法:

insert(Uri uri, ContentValues values) Uri    
delete(Uri uri, String selection, String[] selectionArgs) int   
update(Uri uri, ContentValues values, String selection, String[] selectionArgs)  int   
query(Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder) Cursor 
ContentResolver角色

ContentProvider中的URI有固定格式,如下图:

Scheme:URI的命名空间标识
Authority:授权信息,用以区别不同的ContentProvider
Path:表名,用以区分ContentProvider中不同的数据表
Id:Id号,用以区别表中的不同数据

三、URI

1、URI

URI:通用资源标志符 —— Uniform Resource Identifier
URI类:java.net.URI,是Java提供的一个类,代表了URI的一个实例
Uri类:android.net.Uri,扩展了JAVA中URI的一些功能来适用于Android开发

2、URI的结构

2.1、基本结构
[scheme:]scheme-specific-part[#fragment]  
[scheme:][//authority][path][?query][#fragment]  
[scheme:][//host:port][path][?query][#fragment]
http://www.java2s.com:8080/yourpath/fileName.htm?stove=10&path=32&id=4#harvic

在android中,除了scheme、authority是必须要有的,其他都可以不要,Android中可用的每种资源( 图像、视频片段等)都可以用URI来表示

2.2、代码中提取
Uri.parse(String uriString)    //返回一个Uri对象
getScheme()     //获取Uri中的scheme字符串部分,在这里即,http
getSchemeSpecificPart()   //获取Uri中的scheme-specific-part:部分,这里是://www.java2s.com:8080/yourpath/fileName.htm?
getFragment()   //获取Uri中的Fragment部分,即harvic
getAuthority()   //获取Uri中Authority部分,即www.java2s.com:8080
getPath()   //获取Uri中path部分,即/yourpath/fileName.htm
getQuery()   //获取Uri中的query部分,即stove=10&path=32&id=4
getHost()   //获取Authority中的Host字符串,即www.java2s.com
getPost()   //获取Authority中的Port字符串,即8080
getPathSegments() List<String>    //依次提取出Path的各个部分的字符串,以字符串数组的形式输出
getQueryParameter(String key)   //根据传进去path中某个Key的字符串,返回对应的值
2.3、绝对URI和相对URI

绝对URI:以scheme组件起始的完整格式,如http://fsjohnhuang.cnblogs.com。表示以对标识出现的环境无依赖的方式引用资源
相对URI:不以scheme组件起始的非完整格式,如fsjohnhuang.cnblogs.com。表示以对标识出现的环境有依赖的方式引用资源

2.4、不透明URI和分层URI

不透明URI:scheme-specific-part组件不是以正斜杠(/)起始的,如mailto:fsjohnhuang@xxx.com
分层URI:scheme-specific-part组件是以正斜杠(/)起始的,如http://fsjohnhuang.com

2.5、UriMatcher工具类

因为Uri代表了要操作的数据,所以我们很经常需要解析Uri,并从Uri中获取数据。Android系统提供了两个用于操作Uri的工具类,分别为UriMatcher和ContentUris。掌握它们的使用,会便于我们的开发工作。

UriMatcher

UriMatcher本质上是一个文本过滤器,用在contentProvider中帮助我们过滤,分辨出查询者想要查询哪个数据表

UriMatcher(int code)   //code:匹配未成功时返回的标志码,一般置为:UriMatcher.NO_MATCH(值为-1)
//向UriMatcher中注册Uri,其中authority和path组合成一个Uri,code标识该Uri对应的标识码,path中*表示可匹配任意文本,#表示只能匹配数字
addURI(String authority, String path, int code) void    
match(Uri uri) int   //该Uri是否匹配,若匹配返回注册时候的标志码,否则返回-1

matcher.addURI("com.xx","person/id/#", 2) 匹配content://com.xx/person/id/1any://com.xx/person/id/12

ContentUris工具类

其实就是在末尾加上一个id

ContentUris.withAppendedId(Uri contentUri, long id) //为路径加上ID部分
ContentUris.parseId(Uri contentUri) //解析Uri中的ID值

3、URL

URL = URI(scheme组件为部分已知的网络协议) + 与scheme组件标识的网络协议匹配的协议处理器(URL Protocol Handler),是URI子集

四、ContentObserver

1、基本认知

内容观察者,观察指定的Uri引起数据库变化后通知主线程,然后根据需求做处理。首先在需要监测ContentProvider的应用中进行注册(ContentResolver调用方法的地方),在ContentProvider中要做的就是当数据变化时进行通知。

ContentResolver相关方法:

//传递一个ContentObserver的子类对象进去,会回调其onChange(boolean selfChange)
registerContentObserver(Uri uri, boolean notifyForDescendents, ContentObserver observer)
//取消对注册的那个Uri的观察,这里传进去的就是在registerContentObserver中传递进去的ContentObserver对象。
unregisterContentObserver(ContentObserver observer) 
notifyChange(Uri uri, ContentObserver observer)//在Provider中调用,通知观察uri的观察者,observer可以传null

registerContentObserver方法是注册一个观察者实例,当指定的Uri发生改变时,这个实例会回调实例对象做相应处理。uri:需要观察的Uri,notifyForDescendents:如果为true表示以这个Uri为开头的所有Uri都会被匹配到,如果为false表示精确匹配,即只会匹配这个给定的Uri。
举个例子,假如有这么几个Uri:
content://com.example.studentProvider/student
content://com.example.studentProvider/student/#
content://com.example.studentProvider/student/10
content://com.example.studentProvider/student/teacher
假如观察的Uri为content://com.example.studentProvider/student,当notifyForDescendents为true时则以这个Uri开头的Uri的数据变化时都会被捕捉到,在这里也就是①②③④的Uri的数据的变化都能被捕捉到,当notifyForDescendents为false时则只有①中Uri变化时才能被捕捉到。

2、实现一个ContentObserver

直接创建一个类继承ContentObserver,实现其onChange(boolean selfChange)方法,当指定的Uri的数据发生变化时会回调这个方法。在此方法中,调用构造函数ContentObserver(Handlerhandler)中传入的 Handler对象发送消息到Handler中,做相应的处理。

五、数据共享

如何让其他应用也可以访问此应用中的数据?

1、android:sharedUserId

向此应用设置一个android:sharedUserId,然后需要访问此数据的应用也设置同一个sharedUserId,具有同样的sharedUserId的应用间可以共享数据。

不足:
1)不够安全
2)无法做到对不同数据设置不同读写权限的管理

2、android:exported

实例:
1)声明一个权限
<permission android:name="me.pengtao.READ" android:protectionLevel="normal"/>
2)配置

<provider
android:authorities="me.pengtao.contentprovidertest"
android:name=".provider.TestProvider"
android:readPermission="me.pengtao.READ"
android:exported="true">
</provider>

3)其他应用中可以使用以下权限来对TestProvider进行访问<uses-permission android:name="me.pengtao.READ"/>

对不同的数据表有不同的权限操作,要如何做呢?Android为这种场景提供了provider的子标签<path-permission>,path-permission包括了以下几个标签。

<path-permission android:path="string"
android:pathPrefix="string"
android:pathPattern="string"
android:permission="string"
android:readPermission="string"
android:writePermission="string" />

六、开发ContentProvider步骤

1、步骤一:暴露数据

  1. 开发一个ContentProvider的子类,默认需要实现上面的6个方法。
    数据访问的方法(如:insert和update)可能被多个线程同时调用,此时必须是线程安全的。其他方法(如: onCreate())只能被应用的主线程调用,它应当避免冗长的操作。ContentResolver(内容解析者)请求被自动转发到合适的内容提供者实例,所以子类不需要担心跨进程调用的细节。

实例代码
实现的ContentProvider子类中:

private final static int TEST = 100;

static UriMatcher buildUriMatcher() {
    final UriMatcher matcher = new UriMatcher(UriMatcher.NO_MATCH);
    final String authority = TestContract.CONTENT_AUTHORITY;//必须和清单文件中配置的一致
    matcher.addURI(authority, TestContract.PATH_TEST, TEST);
    return matcher;
}

@Nullable
@Override
public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder) {
    final SQLiteDatabase db = mOpenHelper.getReadableDatabase();
    Cursor cursor = null;
    switch (buildUriMatcher().match(uri)) {
        case TEST://匹配不同的表
            cursor = db.query(TestContract.TestEntry.TABLE_NAME, projection, selection, selectionArgs, sortOrder, null, null);
            break;
    }
    return cursor;
}

@Nullable
@Override
public Uri insert(Uri uri, ContentValues values) {
    final SQLiteDatabase db = mOpenHelper.getWritableDatabase();
    Uri returnUri;
    long _id;
    switch ( buildUriMatcher().match(uri)) {
        case TEST:
            _id = db.insert(TestContract.TestEntry.TABLE_NAME, null, values);
            if ( _id > 0 )
                returnUri = TestContract.TestEntry.buildUri(_id);
            else
                throw new android.database.SQLException("Failed to insert row into " + uri);
            break;
        default:
            throw new android.database.SQLException("Unknown uri: " + uri);
    }
    return returnUri;
}

  1. 在清单文件中配置
<provider    
    android:authorities="me.pengtao.contentprovidertest" //一般为包名.含义 
    android:name=".provider.TestProvider"
    android:exprorted="true"/>//是否允许其他应用调用

2、步骤二:获取ContentResolver对象,并使用

  1. 获取ContentResolver对象。Context的方法:getContentResolver(),因此Activity,Service都能获得该对象
  2. 调用ContentResolver的insert,delete,update,query方法
ContentValues contentValues = new ContentValues();
contentValues.put(TestContract.TestEntry.COLUMN_NAME, "peng");
contentValues.put(TestContract.TestEntry._ID, System.currentTimeMillis());
getContentResolver().insert(TestContract.TestEntry.CONTENT_URI, contentValues);
Cursor cursor = getContentResolver().query(TestContract.TestEntry.CONTENT_URI, null, null, null, null);
try {
    Log.e("ContentProviderTest", "total data number = " + cursor.getCount());
    cursor.moveToFirst();
    Log.e("ContentProviderTest", "total data number = " + cursor.getString(1));
} finally {
    cursor.close();
}

参考文献

ContentProvider从入门到精通
Uri详解之——Uri结构与代码提取
Android开发之内容提供者——创建自己的ContentProvider(详解)

上一篇下一篇

猜你喜欢

热点阅读