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

在上一次的学习中,采用了原生的内容提供者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 extends ContentProvider{
    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" />

时间: 2024-10-13 21:33:05

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

【转】Pro Android学习笔记(八):了解Content Provider(下中)

在之前提供了小例子BookProvider,我们回过头看看如何将通过该Content Provider进行数据的读取. (1)增加 private void addBook(String name ,String isbn,String author){    /* 从ContentProvider的insert()方法的参数可以看到,通过ContentValues来进行数据的传递.ContentValues是key/values对,可以存储多个组,非常适合传递信息 */    ContentV

【转】Pro Android学习笔记(五):了解Content Provider(上)

Content Provider是抽象数据封装和数据访问机制,例如SQLite是Android设备带有的数据源,可以封装到一个content provider中.要通过content provider进行读写,需要使用URI.推荐阅读Android学习笔记(四七):Content Provider初谈和Android联系人信息.Android学习笔记(四八):提供自己的Content Provider和Android学习笔记(四九):通过Content Provider访问数据.Content

Android学习笔记(四九):通过Content Provider访问数据

在上次笔记中,我们编写了自己的Provider,这次笔记,我们将通过Content Provider的Uri接口对数据进行访问,重写Android学习笔记(四二)中例子.在这里我们不在充分描述相关UI如何编写,可以到笔记(四二)中详细查看,重点讲述如何实现数据的访问. 读取信息 读取信息方式,在笔记(四七)中已经介绍,代码如下 private voidread(){     /* 通过managedQuery读取,第1参数表示URI, 第2参数表示所需读取的信息,第3个参数是限制条件,类似SQL

【转】Pro Android学习笔记(七):了解Content Provider(下上)

我们通过一个Content Provider小例子进行详细说明.数据源是一个SQLite数据库,名字为books.db,该数据库只含有一个表格,名字为books.表格中含有name,isbn,author,created_date和modified_date几列.我们通过一个名为BookProvider的内容提供者将数据源运行封装,并对外提供增删改查的接口. 首先:定义Content Provider的结构 创建一个Provider,我们首先需要定义好这个provider的结构.通过consta

Android学习笔记四:添加Source

问题描述 Source not foundThe JAR file D:\.....\sdk\platforms\android-20\android.jar has no source attachment. 问题原因及解决办法 1. 使用SDK Manager下载最新版本的Sources for Android SDK 一般文件下载目录默认在SDK下的sources文件中即 \adt-bundle-windows-x86_64-20130522\sdk\sources\android-20

【转】 Pro Android学习笔记(八二):了解Package(1):包和进程

文章转载只能用于非商业性质,且不能带有虚拟货币.积分.注册等附加条件.转载须注明出处:http://blog.csdn.net/flowingflying/ 在之前,我们已经学习了如何签发apk,见Pro Android学习笔记(六四):安全和权限(1):签发apk,我们将对package做进一步了解. 每个apk都有一个唯一的根包名,在AndroidManifest.xml中定义,如下.开发者为包进行签发,前面和包名绑定,其他开发者不能对这个包进行更新. <?xml version="1

【转】 Pro Android学习笔记(八十):服务(5):访问远程服务

目录(?)[-] Client的AIDL文件 Client的代码 建立连接 请求服务 断开连接 文章转载只能用于非商业性质,且不能带有虚拟货币.积分.注册等附加条件.转载须注明出处:http://blog.csdn.net/flowingflying/ 和Local service不同,remote service可以被其他进程,即其他应用所调用. Client的AIDL文件在onBind()中将stub对象返回给client,client对stub对象的操作,就如同操作service的对外接口

Android学习笔记四之Activity

Android四大组件之Activity 1.什么是Activity Activity是Android四大组件之一,用于显示View.Activity是一个应用程序组件,提供一个用户交互的接口,其本身是没有界面的,Activity类创建一个窗口,在上面可以绘制用户接口.窗口通常充满屏幕,也可以小于屏幕而悬浮于其他窗口之上. 开发者可以通过Activity类提供的setContentView(View)接口将View放到Activity创建的窗口上.一个程序一般由多个Activity组成,他们通常

Android 学习笔记四:创建工具栏按钮

原文:http://blog.csdn.net/lihongxun945/article/details/48951199 前面我们已经可以在一个Activity中添加一些按钮之类的组件.由于手机的屏幕很小,所以很多时候我们会需要用到工具栏,通过下拉菜单之类的方式来节省空间. Android 提供了对工具栏按钮的强大支持. 增加一个工具栏按钮 我们现在给 MainActivity 增加一个搜索按钮.增加一个按钮需要做这三件事 一,在 res/menu/activity_main.xml 中增加一

【转】 Pro Android学习笔记(八三):了解Package(2):包签名过程

目录(?)[-] 类比例子 数字签名 文章转载只能用于非商业性质,且不能带有虚拟货币.积分.注册等附加条件.转载须注明出处:http://blog.csdn.net/flowingflying/ 在Windows等操作系统中安装应用并不需要授权,为何Android需要?在设备安装的包都有一个唯一的包名,如果你试图安装一个已有包名的应用,是不会允许的,除非将之前的包删除.为了允许包升级,你必须确保是相同应用发布者,这需要数字签名. 类比例子 葡萄酒收集家发现每一种葡萄酒都有独一无二的色泽,如果色泽