Android Contentprovider 经验总结

来源:互联网 发布:云播软件 编辑:程序博客网 时间:2024/06/01 07:51

Android提供了5种方式存储数据。

(1)SQLite数据库存储数据

(2)文件存储数据

(3)网络存储数据

(4)SharedPreferences存储数据

(5)ContentProvider存储数据

ContentProvider

1、适用场景

(1)ContentProvider为存储和读取数据提供了统一的接口

(2)使用ContentProvider,应用程序可以实现数据共享

(3)android内置的许多数据都是使用ContentProvider形式,供开发者调用的(如视频,音频,图片,通讯录等)

2、ContentProvider介绍

(1)ContentProvider简介 App中新建一个类继承ContentProvider类,并重写该类用于提供数据和存储数据的方法,就可以向其他应用共享其数据。虽然使用其他方法也可以对外共享数据,但数据访问方式会因数据存储的方式而不同,如:采用文件方式对外共享数据,需要进行文件操作读写数据;采用sharedpreferences共享数据,需要使用sharedpreferences API读写数据。而使用ContentProvider共享数据的好处是统一了数据访问方式。

(2)Uri 通用资源标志符(Universal Resource Identifier, 简称"URI")简介 Uri uri = Uri.parse("content://com.changcheng.provider.contactprovider/contact") 在Content Provider中使用的查询字符串有别于标准的SQL查询。很多诸如select, add, delete, modify等操作我们都使用一种特殊的URI来进行,这种URI由3个部分组成, “content://”, 代表数据的路径,和一个可选的标识数据的ID。 

举个实际的例子:

content://com.example.project:200/folder/subfolder/etc
\---------/  \---------------------------/ \---/ \--------------------------/
scheme                 host               port        path
                \--------------------------------/
                          authority   

1.scheme:ContentProvider(内容提供者)的scheme已经由Android所规定为:content://。

2.主机名(或Authority):用于唯一标识这个ContentProvider,外部调用者可以根据这个标识来找到它。

3.路径(path):可以用来表示我们要操作的数据,路径的构建应根据业务而定,如下:

 content://media/internal/images  这个URI将返回设备上存储的所有图片
 content://contacts/people/  这个URI将返回设备上的所有联系人信息
 content://contacts/people/45 这个URI返回单个结果(联系人信息中ID为45的联系人记录)

尽管这种查询字符串格式很常见,但是它看起来还是有点令人迷惑。为此,Android提供一系列的帮助类(在android.provider包下),里面包含了很多以类变量形式给出的查询字符串,这种方式更容易让我们理解一点,因此,如上面content://contacts/people/45这个URI就可以写成如下形式:
Uri person = ContentUris.withAppendedId(People.CONTENT_URI,  45);
然后执行数据查询:
Cursor cur = managedQuery(person, null, null, null);
这个查询返回一个包含所有数据字段的游标,可以通过迭代这个游标来获取所有的数据:

 cursor = contentResolver.query(IPersonProvider.CONTENT_URI, new String[] { IPersonProvider.PERSON_ID,IPersonProvider.PERSON_NAME, IPersonProvider.PERSON_AGE },                null, null, "_id");        if (cursor != null && cursor.moveToFirst()) {            Person p = new Person();            do {                p.setName(cursor.getString(cursor.getColumnIndexOrThrow(IPersonProvider.PERSON_NAME)));                p.setId(cursor.getString(cursor.getColumnIndexOrThrow(IPersonProvider.PERSON_ID)));                p.setAge(cursor.getInt(cursor.getColumnIndexOrThrow(IPersonProvider.PERSON_AGE)));                str.append("  person.id="+p.getId()+"  person.name="+p.getName()+"    person.age="+p.getAge()+ "\n");                textView.setText(str.toString());            }while (cursor.moveToNext());        } else {            Log.i("TAG", "query failure!");        }

使用ContentResolver.update()方法来修改数据,写一个修改数据的方法,修改指定id的行的人的姓名。updateRecord(1,"ssss")修改第1条记录的人姓名为ssss:

    private void updateRecord(int recNo, String name) {        Uri uri = ContentUris.withAppendedId(IPersonProvider.CONTENT_URI, recNo);        ContentValues values = new ContentValues();        values.put(IPersonProvider.PERSON_NAME, name);        getContentResolver().update(uri, values, null, null);    }

使用ContentResolver.insert()方法来插入数据,该方法接受一个要增加的记录的目标URI,以及一个包含了新记录值的Map对象,调用后的返回值是新记录的URI,包含记录号。

    private int insertData(Person person) {        ContentValues values = new ContentValues();        values.put(IPersonProvider.PERSON_NAME,person.getName());        values.put(IPersonProvider.PERSON_AGE,person.getAge());        Uri uri = getContentResolver().insert(IPersonProvider.CONTENT_URI,values);        String lastPath = uri.getLastPathSegment();        if (TextUtils.isEmpty(lastPath)) {            Log.i(TAG, "insert failure!");        } else {            Log.i(TAG, "insert success! the id is " + lastPath);        }        return Integer.parseInt(lastPath);    }
使用ContentResolver.insert()方法来删除数据:

删除该表所有信息

    private void deleteRecords() {        Uri uri =IPersonProvider.CONTENT_URI;        getContentResolver().delete(uri, null, null);    }
删除指定姓名的行

    private void deleteRecords(String name) {        Uri uri =IPersonProvider.CONTENT_URI;        getContentResolver().delete(uri, IPersonProvider.PERSON_NAME + " = 'Michelle'",null );    }

3、创建ContentProvider

当应用需要通过ContentProvider对外共享数据时,第一步需要继承ContentProvider并重写下面方法:

public class PersonContentProvider extends ContentProvider{   public boolean onCreate()   public Uri insert(Uri uri, ContentValues values)   public int delete(Uri uri, String selection, String[] selectionArgs)   public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs)   public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder)   public String getType(Uri uri)}

并定义需要用到常量数据

public interface IPersonProvider extends BaseColumns {    public static final String AUTHORITY = "com.caidongdong.contentprovider.person";    public static final String CONTENT_TYPE = "vnd.android.cursor.dir/person";    public static final String CONTENT_ITEM_TYPE = "vnd.android.cursor.item/person";    public static final String PERSON_NAME = "name";    public static final String PERSON_AGE = "age";    public static final Uri CONTENT_URI = Uri.parse("content://"+ AUTHORITY +"/persons");    public static final String TABLE_NAME = "person";    public static final String DEFAULT_SORT_ORDER = "age desc";}

第二步需要在AndroidManifest.xml使用对该ContentProvider进行配置,为了能让其他应用找到该ContentProvider ,ContentProvider采用了authorities(主机名/域名)对它进行唯一标识,你可以把ContentProvider看作是一个网 站(想想,网站也是提供数据者),authorities 就是他的域名:

    <!-- 注意这个地方的位置,是在application标签里面;android:authorities对应Provider.AUTHORITY -->        <provider            android:name=".provider.PersonProvider"            android:authorities="com.caidongdong.contentprovider.person"            android:multiprocess="true"            android:exported="true">        </provider>
 创建你的数据存储系统。大多数Content Provider使用Android文件系统或SQLite数据库来保持数据,但是你也可以以任何你想要的方式来存储。如果你要存储字节型数据,比如位图文件等,数据列其实是一个表示实际保存文件的URI字符串,通过它来读取对应的文件数据。处理这种数据类型的Content Provider需要实现一个名为_data的字段,_data字段列出了该文件在Android文件系统上的精确路径。这个字段不仅是供客户端使用,而且也可以供ContentResolver使用。客户端可以调用ContentResolver.openOutputStream()方法来处理该URI指向的文件资源;如果是ContentResolver本身的话,由于其持有的权限比客户端要高,所以它能直接访问该数据文件。

4、监听ContentProvider数据变化

如果ContentProvider的访问者需要知道ContentProvider中的数据发生变化,可以在ContentProvider发生数据变化时调用getContentResolver().notifyChange(uri, null)来通知注册在此URI上的访问者,例子如下:

public class PersonContentProvider extends ContentProvider {   public Uri insert(Uri uri, ContentValues values) {      db.insert("person", "personid", values);      getContext().getContentResolver().notifyChange(uri, null);   }}
如果ContentProvider的访问者需要得到数据变化通知,必须使用ContentObserver对数据(数据采用uri描述)进行监听,当监听到数据变化通知时,系统就会调用ContentObserver的onChange()方法:

getContentResolver().registerContentObserver(Uri.parse("content://com.ljq.providers.personprovider/person"),       true, new PersonObserver(new Handler()));public class PersonObserver extends ContentObserver{   public PersonObserver(Handler handler) {      super(handler);   }   public void onChange(boolean selfChange) {      //此处可以进行相应的业务处理   }}

5、权限设置与线程同步

(1)可以在代码中通过setReadPermission()和setWritePermission()两个方法来设置ContentProvider的操作权限,也可以在配置文件中通过android:readPermission和android:writePermission属性来控制。
(2)因为ContentProvider可能被不同的进程和线程调用,所以里面的方法必须是线程安全的。

自定义ContentProvider读写权限

  <permission        android:name="com.caidongdong.contentprovider.person.read"        android:label="provider pomission"        android:protectionLevel="normal"/>    <permission        android:name="com.caidongdong.contentprovider.person.write"        android:label="provider write pomission"        android:protectionLevel="normal" />
在provider标签中也需要设置改权限才有效

 <provider            android:name=".provider.PersonProvider"            android:authorities="com.caidongdong.contentprovider.person"            android:readPermission="com.caidongdong.contentprovider.person.read"            android:writePermission="com.caidongdong.contentprovider.person.write"            android:multiprocess="true"            android:exported="true">
这里对读写都加了权限,若第三方app想要获得数据,就必须要在Manifest.xml中获得读和写的权限

 <uses-permission android:name="com.caidongdong.contentprovider.person.read" />
如果这里不申请写权限,那么是没有办法向提供数据的app写入数据的。

提一下这个android:multiprocess="true",数据可能被不同的第三方app同时访问,要保持数据的同步线程安全就需要配置进程之间是否是单例,这句代码就是使用单例模式的意思。

如果你对下面几个问题感兴趣,那么就点击这个链接

1. 在应用程序A里面怎么跨进程拿到ContentProvider的对象呢?
2. ContentProvider实例对象是保存在哪里呢?
3. ContentProvider的方法实现要注意线程安全吗?


6、效果展示

附上自己做的demo图,一个提供数据源的app,一个第三方访问ContentProvider的app。




访问ContentProvider的app



需要源码的请留下邮箱














0 0