ContentProvider的使用

ContentProvider的意义

ContentProvider的类注释如下:

Content providers are one of the primary building blocks of Android applications, providing content to applications. They encapsulate data and provide it to applications through the single ContentResolver interface. A content provider is only required if you need to share data between multiple applications. For example, the contacts data is used by multiple applications and must be stored in a content provider. If you don't need to share data amongst multiple applications you can use a database directly via android.database.sqlite.SQLiteDatabase

内容提供者是Android应用程序的主要构建块之一,为应用程序提供内容。它们封装数据并通过单一的ContentResolver接口提供给应用程序。如果需要在多个应用程序之间共享数据,则只需要内容提供程序。例如,联系人数据由多个应用程序使用,必须存储在内容提供者中。如果您不需要在多个应用程序之间共享数据,您可以直接通过数据库使用数据库。

由此可见,ContentProvider适合进程间的数据共享,你的应用可以通过ContentProvider将需要共享的部分数据提供给其他应用。你也可以通过ContentProvider获取手机的图片、音视频、通讯录等数据

ContentProvider实现

需要重写以下的几个主要方法

  • onCreate 初始化创建ContentProvider
  • query 返回查询数据
  • insert 插入数据
  • update 更新数据
  • delete 删除数据
  • getType

getType方法很特别,基本用不上,但是它具体的作用又是什么呢,来看看它的注释

Implement this to handle requests for the MIME type of the data at the
given URI. The returned MIME type should start with vnd.android.cursor.item for a single record, or vnd.android.cursor.dir/ for multiple items.
根据访问的URI,返回对应的MIME类型的字符串,这个字符串需要以vnd.android.cursor.item(返回单条数据记录)或者vnd.android.cursor.dir/(返回多条数据记录) 开头

看到这些方法是不是很熟悉?跟数据库操作一毛一样啊,其实在这个时候我们可以将ContentProvider理解成一个共享数据库,我们只要在ContentProvider初始化的时候,实例化一个可读写的数据库,在以上的方法中对这个数据库进行增删改查的操作就可以了。当你的数据库有多个表的时候,这些增删改查又是如何区分不同的表呢?其实这些方法都有一个Uri的参数回调,我们可以通过实例UriMatcher来区分用户操作的到底是哪个表。

示例

创建一个DBOpenHelper


public static class TestDBOpenHelper extends SQLiteOpenHelper{
    public static final String DB_NAME = "test.db";
    public static final int VERSION=1;
    public static final String TABLE_NAME = "person";
    public static final String TABLE_NAME2 = "class";
    public static final String CREATE_TABLE = "create table if not exists "+TABLE_NAME+"(_id integer, name TEXT, desc TEXT)";
    public static final String CREATE_TABLE2= "create table if not exists "+TABLE_NAME2+"(id integer, className TEXT, desc TEXT)";

    public TestDBOpenHelper(Context context){

        super(context,DB_NAME,null,VERSION);
    }

    @Override
    public void onCreate(SQLiteDatabase db) {

        db.execSQL(CREATE_TABLE);
        db.execSQL(CREATE_TABLE2);
    }

    @Override
    public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {

    }
}

ContentProvider


public class TestContentProvider extends ContentProvider {

        private static final UriMatcher mUriMatcher = new UriMatcher(UriMatcher.NO_MATCH);
        public static final String AUTHORITY = "com.gzkit.dailysample.TestContentProvider";  //授权
        public static final Uri PERSON_CONTENT_URI = Uri.parse("content://" + AUTHORITY + "/person");
        public static final Uri CLASS_CONTENT_URI = Uri.parse("content://" + AUTHORITY + "/class");

        private static final int TABLE_CODE_PERSON = 2;
        private static final int TABLE_CODE_CLASS = 3;

        static {
            //关联不同的 URI 和 code,便于后续 getType
            mUriMatcher.addURI(AUTHORITY, "person", TABLE_CODE_PERSON);
            mUriMatcher.addURI(AUTHORITY, "class", TABLE_CODE_CLASS);
        }

            //数据库
        private SQLiteDatabase mDatabase;
        private Context mContext;
        private String mTable;

        private void initProvider(){

            mContext = getContext();
            mTable = TestDBOpenHelper.TABLE_NAME;
            mDatabase = new TestDBOpenHelper(mContext).getWritableDatabase();
                //插入一条初始数据
            mDatabase.execSQL("insert into "+mTable+" values(1,'test','good boy') ");


        }


        @Override
        public boolean onCreate() {

            initProvider();
            return false;
        }

        @Nullable
        @Override
        public Cursor query(@NonNull Uri uri, @Nullable String[] projection, @Nullable String selection, @Nullable String[] selectionArgs, @Nullable String sortOrder) {

            String tableName = getTableName(uri);


            return mDatabase.query(tableName,projection,selection,selectionArgs,null,sortOrder,null);
        }

        @Nullable
        @Override
        public String getType(@NonNull Uri uri) {
            int match = mUriMatcher.match(uri);
            String MIME = "";
            switch (match) {
                case TABLE_CODE_PERSON:

                    MIME = "vnd.android.cursor.dir/multi";
                    break;
                case TABLE_CODE_CLASS:
                    MIME = "vnd.android.cursor.item/single";

                    break;
            }

            return MIME;
        }

        @Nullable
        @Override
        public Uri insert(@NonNull Uri uri, @Nullable ContentValues values) {
            String tableName = getTableName(uri);

            mDatabase.insert(tableName,null,values);
            //发送数据变动的通知
            mContext.getContentResolver().notifyChange(uri,null);
            return null;
        }

        @Override
        public int delete(@NonNull Uri uri, @Nullable String selection, @Nullable String[] selectionArgs) {
            return 0;
        }

        @Override
        public int update(@NonNull Uri uri, @Nullable ContentValues values, @Nullable String selection, @Nullable String[] selectionArgs) {
            return 0;
        }

        /**
         * CRUD 的参数是 Uri,根据 Uri 获取对应的表名
         *
         * @param uri
         * @return
         */
        private String getTableName(final Uri uri) {
            String tableName = "";
            int match = mUriMatcher.match(uri);
            switch (match){
                case TABLE_CODE_PERSON:
                    tableName = TestDBOpenHelper.TABLE_NAME;

                    break;

                case TABLE_CODE_CLASS:
                    tableName = TestDBOpenHelper.TABLE_NAME2;

                    break;
            }
            return tableName;
        }

在AndroidManifest中注册ContentProvider

提供ContentProvider的APP按实际需要加入以下权限

<permission android:name="com.gzkit.dailysample.permission.READ_CONTENT"
    android:protectionLevel="normal"
    />
<permission android:name="com.gzkit.dailysample.permission.WRITE_CONTENT"
    android:protectionLevel="normal"
    />
<permission android:name="com.gzkit.dailysample.provider.permission"
    android:protectionLevel="normal"
    />

如果provider不存在这些权限的属性,声明这些权限则不是必要的

application节点下


<provider
   android:name="com.gzkit.dailysample.bean.TestContentProvider"
    android:authorities="com.gzkit.dailysample.TestContentProvider"
    android:exported="true"
    android:readPermission="com.gzkit.dailysample.permission.READ_CONTENT"
    android:writePermission="com.gzkit.dailysample.permission.WRITE_CONTENT"
    android:permission="com.gzkit.dailysample.provider.permission"
    android:process=":provider"
/>

  • name 自定义的类
  • authorities 定义授权的名称,在自定义的ContentProvider中要用到
  • exported 外部是否能调用
  • process 进程名
  • readPermission 读取的权限名,必须和前面的permission节点名一致
  • writePermission 写入的权限名,必须和前面的permission节点名一致
  • permission 读写的权限名,必须和前面的permission节点名一致

值得注意的是,readPermission和writePermission的优先级比permission高。

有以下几种情况

  • 所有权限都不声明,其他应用无须声明权限就可以自由读写
  • 只声明了permission,则其他应用需要声明对应的权限,即可读写
  • 只声明readPermission,则其他应用需要声明此权限,才可以读取数据,但仍然可以写入数据
  • 只声明writePermission,和上面相反
  • 声明readPermission或writePermission,同时声明了permission,则其他应用需要声明对应的权限,可以读写。

ContentProvider声明了哪种权限,调用的那方就必须声明对应的权限

调用方需要声明对应的权限

<!--ContentProvider需要的权限-->
<uses-permission android:name="com.gzkit.dailysample.permission.READ_CONTENT"/>
<uses-permission android:name="com.gzkit.dailysample.permission.WRITE_CONTENT"/>
<uses-permission android:name="com.gzkit.dailysample.provider.permission"/>

使用ContentProvider

在activity或service中通过getContentResolver()获取resolver接口进行数据的增删查改


public static final String AUTHORITY = "com.gzkit.dailysample.TestContentProvider";  //授权
public static final Uri PERSON_CONTENT_URI = Uri.parse("content://" + AUTHORITY + "/person");
public static final Uri CLASS_CONTENT_URI = Uri.parse("content://" + AUTHORITY + "/class");


public void insert(){
        ContentValues contentValues = new ContentValues();
        contentValues.put("_id",1);
        contentValues.put("name","test here");
        contentValues.put("desc","here is desc");
        getContentResolver().insert(PERSON_CONTENT_URI,contentValues);
}

public void query(){
        Cursor cursor =  getContentResolver().query(CLASS_CONTENT_URI,null,null,null,null);
        Toast.makeText(mContext, "查询结果数:"+cursor.getCount(), Toast.LENGTH_SHORT).show();
        cursor.close();
}

下一篇,将会讲到如何利用ContentProvider读取手机的多媒体资料

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 199,636评论 5 468
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 83,890评论 2 376
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 146,680评论 0 330
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 53,766评论 1 271
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 62,665评论 5 359
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,045评论 1 276
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,515评论 3 390
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,182评论 0 254
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,334评论 1 294
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,274评论 2 317
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,319评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,002评论 3 315
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,599评论 3 303
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,675评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,917评论 1 255
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,309评论 2 345
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 41,885评论 2 341

推荐阅读更多精彩内容

  • Android程序往往都需要在androidmanifest.xml文件中来声明许多的相关权限请求, 而权限请求也...
    Yinll阅读 8,252评论 0 11
  • Android 自定义View的各种姿势1 Activity的显示之ViewRootImpl详解 Activity...
    passiontim阅读 171,263评论 25 707
  • 参考Content Providers 对于ContentProvider, 可以把它看做为一个数据库, 数据库中...
    AssIstne阅读 1,986评论 1 3
  • 获取系统ContentProvider的例子(获取联系人及手机号) 自己创建的contentprovider,代码...
    名字_都被占了阅读 117评论 0 0
  • 一次过敏,一次强效的注射,一天昏沉的睡眠。使我感觉,仿佛大病初愈对这人世间的一切又多了几分的眷恋。 ...
    棕色小熊阅读 208评论 1 0