首页 > 其他分享 >Android学习笔记(四八):提供自己的Content Provider

Android学习笔记(四八):提供自己的Content Provider

时间:2023-04-09 12:07:22浏览次数:39  
标签:return String Uri private uri Content Provider 四八 Constants


在上一次的学习中,采用了原生的内容提供者Contact,Contact有多层映射关系,比较复杂,并非作为小例子的好选择,在本次学习中,我们将学习如何建立Content Provider,并通过Uri进行增删改查。如果应用的数据只需自己使用,并不需要content provider,相反避免这样做,可直接访问数据;但是若希望数据可以被其他应用访问,创建content provider就是常规手段

再谈Content Provider的Uri

在上一次学习中,我们谈到了Uri的格式。现在已content://com.wei.android.myproject/card/pin/17为例子,具体解构。

1、scheme部分:content://,表明这是个content的Uri,而不是一个http://的网络Uri;

2、authority(com.wei.andriod.myproject)紧跟scheme部分,是唯一的标识,通常我们用类的namespace的命名方法,表明归属。

3、在authority(com.wei.android.myproject)后面,在instance identifier(17)之前,用于表明路径,表示内容的类型,即例子中的“card/pin”,这部分可以为空。

4、最后是instance identifier,是整数,表明内容中数据的具体位置。一个内容的Uri如果没有instance identifier,则表示内容的数据集(collection)。

内容中Uri指向的数据,有对应的MIME type pair,一个用于collection,一个用于instance。对于collection,MIME类型应是vnd.X.cursor.dir/Y,X是我们公司、组织或者项目的名字,而Y是用“.”分割。例如vnd.wei.cursor.dir/com.wei.android.myproject.card.pin。对于instance而言,MIME类型应为vnd.X.cursor.item/Y,其中X和Y与collection的一致。

创建内容提供者:创建自己的Provider类

通过继承ContentProvider提供我们自己的子类。我们将采用SQLite的方式,在Android学习笔记(四一):SQLite的使用中学习过,很多代码都可以重用。我们将通过Content Provider的方式封装SQLite的数据访问接口,允许其他应用通过Uri访问数据。

步骤1:创建自己的Provider类,需要重写6个抽象方法onCreate( ), query( ), insert( ), update( ), delete( )和返回MIME类型的getType()。由于一个内容提供者,允许多个客户端同时访问,客户端A修改数据(insert,update,delete),需通知其他查询的客户端(query),以便其他客户端可以进行数据同步。见例子蓝色部分。对于SQLite,有些模板可以套用,见绿色部分。

步骤2:设置Uri以及Provider的属性,见例子暗红字部分。

//步骤1:通过继承ContentProvider创建自己的Provider类,需要重写6个抽象方法,以实现创建,读取、更新和删除
public class GravityProvider{
    private String DB_NAME = "gravity.db"; 
     public static final String TABLE="constants"; 
    private SQLiteDatabase db = null;  
     /* 步骤2:定义Uri以及Provider的属性*/  
     /* 步骤2.1 通过继承BaseColumns的静态公共类俩描述,申明Uri,以及基本属性以便客户端是以哦那个。如果信息存储采用SQLite的方式,这些属性通才就是表格的column,这样我们可以很方便地在update,insert中传递信息。 属性的类型没有特定要求,只要cursor能够支持。在BaseColummns中已经含有ID=_id */ 
    public static final class Constants implements BaseColumns { 
         /* 步骤2.2 定义Uri,提供content provider的每个collection的Uri,为了避免Uri的冲突,可以是使用Java类的namespace */          public static final Uri CONTENT_URI = Uri.parse("content://" + AUTHORITY + "/gravity");
        /* 步骤2.3 定义一些基本属性,对于SQLite是column的名字 */
         public static final String TITLE="title"; 
         public static final String VALUE="value"; 
     } 
     public static final String AUTHORITY = "com.wei.android.learning.provider";  

    //【通用模板类】建立Uri的树状层次结构,以便于检查Uri 的匹配情况:isCollectUri( )    private static final UriMatcher sURIMatcher = new UriMatcher(UriMatcher.NO_MATCH);
     private static final int CONSTANTS = 1; 
     private static final int CONSTANTS_ID = 2; 
    static { 
        //对于具体的instance,实际是在collection后面加上/<num>,例如……/1,用#表示数字        sURIMatcher.addURI(AUTHORITY, "gravity", CONSTANTS); 
         sURIMatcher.addURI(AUTHORITY, "gravity/#", CONSTANTS_ID);  
     } 

     // 【通用模板类】设置query映射的map,对于SQLite而言,通过query传递的column名字,影射到具体数据库column名字,这种方式对重命名column很有帮助。     private static HashMap<String,String> CONSTANTS_LIST_PROJECT = new HashMap<String, String>();
    static{ 
         CONSTANTS_LIST_PROJECT.put(GravityProvider.Constants._ID, GravityProvider.Constants._ID);
         CONSTANTS_LIST_PROJECT.put(GravityProvider.Constants.TITLE, GravityProvider.Constants.TITLE);
         CONSTANTS_LIST_PROJECT.put(GravityProvider.Constants.VALUE, GravityProvider.Constants.VALUE);
     } 


    /* 步骤1.1:onCreate( ) 是ContentProvider的入口,用于初始化处理。如果数据是文件格式,将检测文件路径以及文件是否存在,如果不存在则创建之,对于SQLite,则建立SQLite的关联。 如果更新了ContentProvider中的数据结构,应检测新旧数据结构是否兼容。onCreate() 是Content Provider普通开始的入口,或者是释放更新的入口 */
    public boolean onCreate() {   
        db=(new GravityDbHelper(getContext())).getWritableDatabase(); 
         return (db == null) ? false : true; 
     }   


    /* 步骤1.2 : 处理查询,即对应处理客户端的manageQuery查询,并返回Cursor。这些参数特别适合采用SQLite方式存储数据,我们可以忽略某些参数,例如只根据Uri返回数据,这种情况,应在doc对方法的描述中进行说明。*/
     public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder){  
        //对于SQLite,使用SQLiteQueryBuilder将参数放入一个SQL语句中,如下:       SQLiteQueryBuilder qb = new SQLiteQueryBuilder();
         qb.setTables(getTableName());  

         if(isCollectionUri(uri)){ 
            //如果是Collection的Uri,本例为content://com.wei.android.learning/constants,设置返回的的内容,用HashMap的结构,映射到Content Provider的封装中。
            qb.setProjectionMap(CONSTANTS_LIST_PROJECT);         }else{ 
            //如果是instance的Id,本例为content://com.wei.android.learning/constants/#,即需要查询特定_ID的用户,因此增加Where的条件说明 
            qb.appendWhere(GravityProvider.Constants._ID + "=" + uri.getPathSegments().get(1));
         } 
        //获取排序方式,或给出缺省的排序方式         String orderBy = null;
         if( TextUtils.isEmpty(sortOrder)){ 
             orderBy= getDefaultSortOrder(); 
         }else{ 
             orderBy  = sortOrder; 
         } 
        //查找数据库,获取游标         Cursor c=qb.query(db, projection, selection, selectionArgs, null, null, orderBy);
        //【注册通知】注册该uri对应的数据发生变化时,向客户端发出通知。内容提供者可以有多个应用(客户端)来访问,提供同步数据的通知。 
        c.setNotificationUri(getContext().getContentResolver(), uri);     
         return c; 
     }  

    /* 步骤1.3 insert()处理增加数据,其中参数1:表示collection的Uri,参数2:是instance的内容,并返回一个instance的Uri */
     public Uri insert(Uri uri, ContentValues values) {  
        long rowId; 
         ContentValues cv = null; 
         if(values != null){ 
             cv = new ContentValues(values); 
         }else{ 
             cv  = new ContentValues(); //可能允许空置的插入,所以null的情况不一定异常 
         } 
        //检测Uri是否有效:若是instance的Uri,不是collection Uri,即不能增加Uri,抛出异常 
         if(!isCollectionUri(uri)){   
             throw new IllegalArgumentException("Unknown Uri " + uri); 
         } 
        //检测提供的数据是否完毕:必有的Columns是否存在,否则抛出异常
        for(String colName : getRequiredColumns()){ 
             if( !cv.containsKey(colName) ){ 
                 throw new IllegalArgumentException("Miss column : " + colName); 
             } 
         } 
         //对空缺的非必要列进行缺省值填入 
         popularDefaultValues(cv); 
         rowId = db.insert(getTableName(), getNullColumnHack(), cv);
         if(rowId > 0){ 
             Uri instanceUri = ContentUris.withAppendedId(getContentUri(), rowId);
             //【通知注册客户端数据出现变更】出现Uri指向数据变更,通过notifyChange( )通知所有注册者发生了更新。本例第二参数为null,如果非null,表明若变更由该oberver引起,则无需通知该observer 
            getContext().getContentResolver().notifyChange(instanceUri, null);            return  instanceUri; 
         } 
         return null; 
     }   

     /* 步骤1.4: 用于修改一个或者多个instance的值,通常只用于SQLite,否者一般忽略。*/
    public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs) {         int count = 0; 
         if(isCollectionUri(uri)){  
             count = db.update(getTableName(), values, selection, selectionArgs);
         }else{ //如果是instance,在Where处制定ID 
            String segment = uri.getPathSegments().get(1); 
             count = db.update(getTableName(), values,  
                     getIdColumnName() + "=" + segment + (TextUtils.isEmpty(selection) ? "" : " AND (" + selection + ")" ),
                     selectionArgs); 
         } 
         getContext().getContentResolver().notifyChange(uri, null);//【通知注册客户端数据出现变更】 
         return count; 
     }   

     /* 步骤1.5:处理删除 */     public int delete(Uri uri, String selection, String[] selectionArgs) {         int count = 0; 
         if(isCollectionUri(uri)){ 
             count = db.delete(getTableName(), selection, selectionArgs);
         }else{ 
             String segment = uri.getPathSegments().get(1); 
             count = db.delete(getTableName(), 
                     getIdColumnName() + "=" +segment +(TextUtils.isEmpty(selection) ? "" : " AND (" + selection + ")"),
                     selectionArgs); 
         } 
         getContext().getContentResolver().notifyChange(uri, null); //【通知注册客户端数据出现变更】        return count; 
     } 

     /* 步骤1.6 返回collection或者instance的MIME Type */    public String getType(Uri uri) {          if( isCollectionUri(uri)){ 
             return getCollectionType(); 
         }else{ 
             return getInstanceType(); 
         } 
     } 

     //【通用模板类】对于SQLite的内容提供者的处理,已经有套路,下面的均可作为模板化处理    private String[] getRequiredColumns(){
         return (new String[]{Constants.TITLE}); 
     } 

     private void popularDefaultValues(ContentValues values){ 
         if(!values.containsKey(Constants.VALUE)) 
             values.put(Constants.VALUE, 0.0f); 
     } 
   
     private Uri getContentUri(){ 
         return Constants.CONTENT_URI; 
     } 

    private String getIdColumnName(){
        return Constants._ID; 
     } 

     private String getTableName(){ 
         return GravityProvider.TABLE; 
     } 

     private boolean isCollectionUri(Uri uri){ 
         return sURIMatcher.match(uri) == CONSTANTS; 
     } 

     private String getDefaultSortOrder(){ 
         return GravityProvider.Constants.TITLE; 
     } 

     private String getNullColumnHack(){ 
         return GravityProvider.Constants.TITLE; 
     } 

     //返回collection的MIME类型 
     private String getCollectionType(){ 
         return "vnd.wei.cursor.dir/com.wei.android.learning.gravity"; 
     } 

     //返回instance的MIME类型 
     private String getInstanceType(){ 
         return "vnd.wei.cursor.item/com.wei.android.learning.gravity"; 
     }  

   /* 下面这部分是创建SQLite,我们直接采用Andriod学习笔记(四一)的例子,代码不在重复*/    private class GravityDbHelper extends SQLiteOpenHelper {
         …… …… 
     } 

 }

步骤三:在AndroidManifest.xml中声明provider,允许在该xml中定义的Application使用该content Provider的数据访问接口。如下。如果有多个authortity,并全部列出。

<provider android:name=".GravityProvider" android:authorities="com.wei.android.learning.provider" />



标签:return,String,Uri,private,uri,Content,Provider,四八,Constants
From: https://blog.51cto.com/u_9877302/6178723

相关文章

  • django-content-type简单示例
    fromdjango.contrib.contenttypes.fieldsimportGenericForeignKeyfromdjango.contrib.contenttypes.modelsimportContentTypefromdjango.dbimportmodelsclassComment(models.Model):content_type=models.ForeignKey(ContentType,on_delete=models.CASCA......
  • Vue中axios请求后res返回204,no content,res.data为undefind
    一直以为后台只要返回200-299之间的状态码就没问题,就是这个错误认知,花了三个小时终于想到解决办法附上axios 地址  axios中文文档|axios中文网|axios(axios-js.com)前面一直想着后台能接收处理请求,肯定不是后台问题,一直花时间配置前端请求指令,以及设置响应拦截器查看情况......
  • 常用Content-Type汇总
    MIMEtype定义媒体类型(通常称为MultipurposeInternetMailExtensions或MIME类型)是一种标准,用来表示文档、文件或字节流的性质和格式。语法MIME的组成结构非常简单;由类型与子类型两个字符串中间用'/'分隔而组成。不允许空格存在。type表示可以被分多个子类的独......
  • 被ST-Link【The content of ST-Link is corrupt】【Communication error with ST-Link
    直接跳转【4】看解决方法,祝大家都顺利解决【1】我的尝试   【2】我的错误情况【3】我无用的努力【尝试1:点击setting之后的第一个debug页面里面的port要改成sw,不然下载不成功】,其实这样只是比较节约端口而已,当然一般还是都选择【SW】  【尝试2:output里记得把crea......
  • ContentProvider的启动
    ContentProvider的工作离不开AMS(ActivityManagerService),事实上,四大组件的工作流程都离不开AMS。我们在创建一个ContentProvider的时候,除了新建一个类继承并重写方法,还需要在AndroidManifest中进行注册,而AndroidManifest就是AMS进行处理的。AndroidManifest会在当前应用被创建时进......
  • 解决Mixed Content: The page at https://* was loaded over HTTPS, but requested an
    问题:前端页面调用后端接口加载不出来 原因分析:通过查看浏览器调试console日志,得到报错如下 原文:MixedContent:Thepageathttps://*wasloadedoverHTTPS,butrequestedaninsecureXMLHttpRequestendpointhttp://*.Thisrequesthasbeenblocked;thecont......
  • 优维低代码:定制 Providers
    优维低代码技术专栏,是一个全新的、技术为主的专栏,由优维技术委员会成员执笔,基于优维7年低代码技术研发及运维成果,主要介绍低代码相关的技术原理及架构逻辑,目的是给广大运维人提供一个技术交流与学习的平台。连载第四十三期《现场定制:定制Providers》▽# 何为provider?provider也......
  • SB-RocketMQ-Provider-Consumer20230331
     一、生产者1、pom.xml<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId></dependency><dependency><groupId>org.apache.rocketmq</groupId>......
  • Angular 依赖注入错误消息:ERROR Error NullInjectorError No provider for XX
    错误消息ERRORError:NullInjectorError:NoproviderforCustomI18nConfigInitializer!应该如何处理?我已经在AppModule里编写了如下代码啊:{provide:CONF......
  • EF6链接MySql报The underlying provider failed on open错误
    这个问题困惑了好久,在自己电脑是数据库连接都可以用(VS2017),但是在公司的电脑(VS2015)上死活报:Theunderlyingproviderfailedonopen这个错误。网上查找了很多资料都没有得......