Android细笔记--ContentProvider
Provider的不常见访问方式
- Batch access:访问ContentProvider的一中模式,使用该模式可以同时对provider进行多个操作,且支持同时操作多个表。使用时首先构建一个ContentProviderOperation序列,然后使用ContentResolver.applyBatch方法把这些操作分发到provdier中,调用applyBatch参数时需要传入provider的authority而不是某个表的Uri,这样就可以使得不同的operation查询不同的表。
- 当你的应用A不具有访问某个provider的权限时,你可以通过Intent的方法来访问。典型的比如选择一个联系人,在A中可以发送一个Intent,设置相应的action和MIME类型,然后就会启动比如联系人应用来供用户选择一个联系人,带选择完后,应用A的onActivityResult中就会收到返回的Intent,通过getData方法得到其中的Uri,这个Uri就指向了用户选择的联系人,同时,在联系人应用通过对返回的intent设置了
FLAG_GRANT_READ_URI_PERMISSION以及
FLAG_GRANT_WRITE_URI_PERMISSION两个分别给接收该intent的Activity赋予临时读/写该Uri指向的数据的权限,该权限直到该Activity finish之后就失效。另外一中方法就是通过发送Intent,并把需要的额外数据放入Intent中,然后交由另外一个具有该权限的应用去完成你想对该provider的操作。
用户自定义MIME
- 对于用户自定义的,也称作vendor-specific,的MIME类型,MIME类型的固定格式为type/subtype,对于自定的的MIME,其type固定为:对于多行:vnd.android.cursor.dir,对于单行为:vnd.android.cursor.item,而对于subtype类型则在遵守“vnd.<name>.<type>”形式的范围内由用户自定义。name必须全球唯一,一般为公司名或者报名,type则应该与URI类型有一对一的关系。例如对于一个authority为com.willhua.trains的provider,对应的表分别为list1和list2,那么对于uri,content://com.willhua.trains/list1,其对应的MIME类型可以设定为vnd.android.cursor.dir/vnd.willhua.list1,对于uri,content://com.willhua.trains/list2/5,其对应的MIME类型可以设定为vnd.android.cursor.dir/vnd.willhua.list2。
设计数据结构tips
- 最好有一个名为_ID的int列来作为primary key,这样的话即可以把它当做foreign key来映射其他表中的数据,而且如果要把provider的查询结果链入listview的,那么就必须有_ID列(BaseColumns._ID;
- 使用BOLB(binary large object)类型来存储数据体积变化大或者数据结构不同的数据,比如protocol buffer或者JSON数据
- 同样可以使用BLOB来做一个schema-independent的表。一般来说,定一个int的primarykey,一个表示MIME类型的列,一个或者多个BLOB列,BLOB中的数据则可以根据MIME类型来解释,这样的话就实现了把不同shchema类型的数据存储在了同一个表中,ConatactsContract.Data就是这样一个例子的表。
URI
- content Uri分成几个部分,首先是开始的schema部分,即content://,这个都是一样的;然后是provider的authority,ContentRsolver就是根据Uri这个部分匹配系统中已知的provider表而确定(resolver)需要使用哪个provider,从而调用相应provider的对于查询方法;然后就是路径或者表;最后就是若是表示单行的Uri则加上行ID。
- 需要注意的是,路径或者表部分没有限定说只能有一个片段或者层级,比如content://com.willhua.blog/tablea/dataa/8或者content://com.willhua.blog/tablea/datab,这样的Uri也是可以的,其中tablea/dataa和tablea/datab即表示路径部分,且表示的表分别为dataa和datab,这样的做法可以更好的显示表的层级关系。
- 在使用UriMatcher使用通配符判断Uri类型时,符号‘*’表示任意长度的有效字符,‘#’表示任意长度的数字字符
ContentProvider的实现
- query():对于查询结果没有任何匹配的项的时候应该返回getCount==0的cursor,而不是null。只有当查询过程中发生错误才返回null。如果provider使用的数据存储不是SQLite,那么可以使用cursor的一些子类,比如MatrixCursor作为返回值。
- insert():返回的值记得以该表的Uri然后append新插入行的ID.
- delete():有时候可以考虑使用一个比如delete列来表示该行是否被删除来代替真正的从表中删除
- update()
- onCreate():应该尽可能的降低此函数的执行时间以提高响应速度。如果是使用SQLite作为数据存储,那么最好使用SQLiteOpenHelper,它将把创建SQL表的时候延迟到第一次打开database的时候。当第一次调用getWritableDatabase的时候,将调用Helper的onCreate函数。
- getType():用于返回一个MIME格式的的字符串来描述根据content URI参数返回的数据类型。
- getStreamTypes():如果provider提供files,那么应该实现此函数。用于返回与content URI对应的MIME类型序列。例如某provider提供.jpg, .png两种图片,如果另一个应用调用getStreamTypes传入的filter参数为“image/*”,那么getStreamTypes应该返回{“image/jpg”, "image/png"}。如果没有任何匹配该filter的,那么返回null。
Provider权限控制
- 对于放置在内部存储器(internal)上的数据,默认是只能由本应用访问的。但是如果把这些数据作为一个provider的仓库的话,且在没有对provider做额外的权限控制的条件下,那么别的应用程序就可以通过provider完全访问这些数据,也就是相当于把一个私有的数据完全开放了。这并不合理。所以,我们应该根据需要对provider进行一些访问权限控制。
- 对provider的权限设置可以分为三个级别:整个provider的完全权限,使用provider的android:permission控制,这个权限表示对整个provider的完全读写权限;单独的读或者写权限,使用provider的android:readPermission和android:writePermission分别控制对整个provider的读写权限控制;路径级别的权限控制,使用provider的子元素<path-permission>控制,可以控制针对某个具体的URI的读写,读,写或者三者的权限;
- 这三个权限的优先级逐级增大,即假如某个数据同时被设定了整个provider的权限和path级别的权限,那么外部应用就得必须有path级别的权限才能访问这个数据。
- 临时权限,使用provider的android:grantUriPermission属性(默认false)和子元素<grant-uri-permission>来分别控制对整个provider或者某个特定uri的临时权限。具有临时权限的访问忽视前面说的三级权限要求。可以通过Content.revokeUriPermission()来收回针对某个uri的临时权限。在Intent通过FLAG_GRANT_READ_URI_PERMISSION和FLAG_GRANT_WRITE_URI_PERMISSION来给别的应用分别赋予临时读写权限。
- 如果provider没有指明所需的权限,那么别的应用就无法访问到这个provider。但是在provider所在应用中的其他组件是一直都是忽视所需权限要求的,有对该provider完整的读写权限。
/************************* Stay hungry, Stay foolish. @willhua ************************/