Android《第五章:ContentProvider》

ContentProvider(外共享数据)

ContentProvider在Android中的作用是对外共享数据,也就是说通过ContentProvider可以吧应用中的数据共享给其他应用访问,其他的应用可以通过ContentProvider对应用中的数据进行增、删、改、查的操作。使用ContentProvider 的好处是统一了数据访问方式。它实际上是对SqliteOpenHelper的进一步封装,通过Uri映射来判断选择需要操作数据库中的哪个表并且进行想要的处理。

  • Uri
    首先我们了解下Uri,Uri代表操作数据表的绝对路径,它主要包含两部分信息。一个是需要操作的ContentProvider,二是对ContentProvider中的哪个表进行操作。对于ContentProvider来说,一个Uri由以下几部分组成。
image.png
  • Scheme:是系统定好的 content://
  • Authority:是ContentProvider的唯一标识
  • Path:是该ContentProvider下要操作的数据库表
  • Id: 是指表中的数据项ID

举例:"content://com.bsoft.hs/userinfo/100"

好的既然我们理解了其中的概念那么我们来带大家实践下!
创建两个项目FactoryProvider:是用来共享商品数据。ProviderTest:用来对FactoryProvider所提供的数据进行修改操作。

  • FactoryProvider(工厂项目)

首先我们来设计数据结构,假设我们工厂用来提供商品,那么我们需要创建商品数据库(Factory),其次我们需要创建一个水果类型的表用来提供水果类型的商品(Goods)表设计如下。

Goods
_ID _Name _Desc _Url
0 苹果 花牛苹果(正品秦安) http://shop.bytravel.cn/produce/82B1725B82F9679C/

好的我们数据表建立完成,我们都知道Android 内部提供了SQlite数据库,是一种关系型轻量级的数据库,那么我们就用它了。

/**
 * Created by 泅渡者
 * Created on 2017/9/28.
 *
 */

public class Goods {
    /**
     * 数据字段
     **/
    public static final String ID = "_id";
    public static final String NAME = "_name";
    public static final String DESC = "_desc";
    public static final String URL = "_url";
    /**
     * 数据默认排序
     **/
    public static final String DEFAULT_SORT_ORDER = "_id asc";
    /**
     * ContentProvider的唯一标识
     **/
    public static final String AUTHORITY = "com.bsoft.factoryprovider";

    /**
     * METHOD_GET_ITEM_COUNT和KEY_ITEM_COUNT两个常量是调用ContentProvider接口的一个未公开函数call来查询数据时用的,
     * 使用这个call函数时,传入参数METHOD_GET_ITEM_COUNT表示我们要调用我们自定义的ContentProvider子类中的
     * getItemCount函数来获取数据库中的文章信息条目的数量,
     * 结果放在一个Bundle中以KEY_ITEM_COUNT为关键字的域中。
     **/
    public static final String METHOD_GET_ITEM_COUNT = "METHOD_GET_ITEM_COUNT";
    public static final String KEY_ITEM_COUNT = "KEY_ITEM_COUNT";

    /**
     * CONTENT_URI:表示通过ID来访问数据
     * CONTENT_POS_URI:表示是通过位置来访问数据(这里的位置并比一定和Id 相同)
     **/
    public static final Uri CONTENT_URI = Uri.parse("content://" + AUTHORITY + "/item");
    public static final Uri CONTENT_POS_URI = Uri.parse("content://" + AUTHORITY + "/item");

    /**
     * MIME类型
     **/
    public static final String CONTENT_TYPE = "vnd.android.cursor.dir/com.bsoft.factoryprovider.goods";
    public static final String CONTENT_ITEM_TYPE = "vnd.android.cursor.item/com.bsoft.factoryprovider.goods";

    /**
     * URI匹配规则的匹配码
     **/
    public static final int ITEM = 1;
    public static final int ITEM_ID = 2;
    public static final int ITEM_POS = 3;

}

ID、NAME、DESC 、URL :是我们的数据表字段
DEFAULT_SORT_ORDER :是数据的排序方式
AUTHORITY:我们前面提过事唯一标识一般情况下用包名
MIME: 每个MIME类型由两部分组成,前面是数据的大类别,后面定义具体的种类。
在Content Provider中,URI所对应的资源的MIME类型的大类别根据同时访问的资源的数量分为两种,
对于访问单个资源的URI,它的大类别就为vnd.android.cursor.item,而对于同时访问多个资源的URI,
它的大类别就为vnd.android.cursor.dir。
Content Provider的URI所对应的资源的MIME类型的具体类别就需要由Content Provider的提供者来设置了,
它的格式一般为vnd.[company name].[resource type]的形式。

接下来我们需要创建一个数据库助手类:


/**
 * Created by 泅渡者
 * Created on 2017/9/28.
 */

public class DBHelper extends SQLiteOpenHelper {
    public static final String DB_NAME = "Factory.db";
    public static final String DB_TABLE = "Goods";
    public static final int DB_VERSION = 1;
    /**
     * 创建数据库SQL
     **/
    private static final String DB_CREATE = "CREATE TABLE " + DB_TABLE + "("
            + Goods.ID + " INTEGER PRIMARY KEY, "
            + Goods.NAME + " text not null, "
            + Goods.DESC + " text not null, "
            + Goods.URL + " text not null "
            + ")";

    public DBHelper(Context context) {
        super(context, DB_NAME, null, DB_VERSION);
    }

    @Override
    public void onCreate(SQLiteDatabase db) {
        db.execSQL(DB_CREATE);
    }

    @Override
    public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
        db.execSQL("DROP TABLE IF EXISTS " + DB_TABLE);
        onCreate(db);
    }
}

这里有个地方需要注意下 Goods.ID + " INTEGER PRIMARY KEY, "这句话别用 "integer PRIMARY KEY autoincrement"会出现报错具体的原因网上很多。

这些都创建完成后我们来创建我们的利器 GoodsProvider:

/**
 * Created by 泅渡者
 * Created on 2017/9/28.
 */

public class GoodsProvider extends ContentProvider {

    /**
     * 定义Uri匹配规则,如果不符合则会返回 (UriMatcher.NO_MATCH)
     **/
    private static final UriMatcher uriMatcher;

    static {
        uriMatcher = new UriMatcher(UriMatcher.NO_MATCH);
        uriMatcher.addURI(Goods.AUTHORITY, "item", Goods.ITEM);
        uriMatcher.addURI(Goods.AUTHORITY, "item/#", Goods.ITEM_ID);
        uriMatcher.addURI(Goods.AUTHORITY, "pos/#", Goods.ITEM_POS);
    }

    /**
     * 这里的主要作用是隐藏数据库表结构,防止将数据库中的表字段暴露出来
     **/
    private static final HashMap<String, String> goodsProjectionMap;

    static {
        goodsProjectionMap = new HashMap<String, String>();
        goodsProjectionMap.put(Goods.ID, Goods.ID);
        goodsProjectionMap.put(Goods.NAME, Goods.NAME);
        goodsProjectionMap.put(Goods.DESC, Goods.DESC);
        goodsProjectionMap.put(Goods.URL, Goods.URL);
    }

    private DBHelper dbHelper = null;
    private ContentResolver resolver = null;

    @Override
    public boolean onCreate() {
        Context context = getContext();
        resolver = context.getContentResolver();
        dbHelper = new DBHelper(context);
        KLog.d("GoodsProvider onCreate()");
        return true;
    }

    @Nullable
    @Override
    public String getType(@NonNull Uri uri) {
        switch (uriMatcher.match(uri)) {
            case Goods.ITEM:
                return Goods.CONTENT_TYPE;
            case Goods.ITEM_ID:
            case Goods.ITEM_POS:
                return Goods.CONTENT_ITEM_TYPE;
            default:
                throw new IllegalArgumentException("Error Uri: " + uri);

        }
    }

    @Nullable
    @Override
    public Uri insert(@NonNull Uri uri, @Nullable ContentValues values) {
        if (uriMatcher.match(uri) != Goods.ITEM) {
            throw new IllegalArgumentException("Error Uri: " + uri);
        }
        SQLiteDatabase db = dbHelper.getWritableDatabase();

        long id = db.insert(DB_TABLE, Goods.ID, values);
        if (id < 0) {
            throw new SQLiteException("Unable to insert " + values + " for " + uri);
        }

        Uri newUri = ContentUris.withAppendedId(uri, id);
        resolver.notifyChange(newUri, null);

        return newUri;
    }

    @Override
    public int update(@NonNull Uri uri, @Nullable ContentValues values, @Nullable String selection, @Nullable String[] selectionArgs) {
        SQLiteDatabase db = dbHelper.getWritableDatabase();
        int count = 0;

        switch (uriMatcher.match(uri)) {
            case Goods.ITEM: {
                count = db.update(DB_TABLE, values, selection, selectionArgs);
                break;
            }
            case Goods.ITEM_ID: {
                String id = uri.getPathSegments().get(1);
                count = db.update(DB_TABLE, values, Goods.ID + "=" + id
                        + (!TextUtils.isEmpty(selection) ? " and (" + selection + ')' : ""), selectionArgs);
                break;
            }
            default:
                throw new IllegalArgumentException("Error Uri: " + uri);
        }

        resolver.notifyChange(uri, null);

        return count;
    }


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


        SQLiteDatabase db = dbHelper.getReadableDatabase();

        SQLiteQueryBuilder sqlBuilder = new SQLiteQueryBuilder();
        String limit = null;

        switch (uriMatcher.match(uri)) {
            case Goods.ITEM: {
                sqlBuilder.setTables(DB_TABLE);
                sqlBuilder.setProjectionMap(goodsProjectionMap);
                break;
            }
            case Goods.ITEM_ID: {
                String id = uri.getPathSegments().get(1);
                sqlBuilder.setTables(DB_TABLE);
                sqlBuilder.setProjectionMap(goodsProjectionMap);
                sqlBuilder.appendWhere(Goods.ID + "=" + id);
                break;
            }
            case Goods.ITEM_POS: {
                String pos = uri.getPathSegments().get(1);
                sqlBuilder.setTables(DB_TABLE);
                sqlBuilder.setProjectionMap(goodsProjectionMap);
                limit = pos + ", 1";
                break;
            }
            default:
                throw new IllegalArgumentException("Error Uri: " + uri);
        }

        Cursor cursor = sqlBuilder.query(db, projection, selection, selectionArgs, null, null, TextUtils.isEmpty(sortOrder) ? Goods.DEFAULT_SORT_ORDER : sortOrder, limit);
        cursor.setNotificationUri(resolver, uri);

        return cursor;
    }


    @Override
    public int delete(@NonNull Uri uri, @Nullable String selection, @Nullable String[] selectionArgs) {
        SQLiteDatabase db = dbHelper.getWritableDatabase();
        int count = 0;

        switch (uriMatcher.match(uri)) {
            case Goods.ITEM: {
                count = db.delete(DB_TABLE, selection, selectionArgs);
                break;
            }
            case Goods.ITEM_ID: {
                String id = uri.getPathSegments().get(1);
                count = db.delete(DB_TABLE, Goods.ID + "=" + id
                        + (!TextUtils.isEmpty(selection) ? " and (" + selection + ')' : ""), selectionArgs);
                break;
            }
            default:
                throw new IllegalArgumentException("Error Uri: " + uri);
        }

        resolver.notifyChange(uri, null);

        return count;
    }

    @Override
    public Bundle call(String method, String request, Bundle args) {
        KLog.i("FactoryProvider.call:" + method);

        if (method.equals(Goods.METHOD_GET_ITEM_COUNT)) {
            return getItemCount();
        }

        throw new IllegalArgumentException("Error method call: " + method);
    }

    private Bundle getItemCount() {
        KLog.i("FactoryProvider.getItemCount:");
        SQLiteDatabase db = dbHelper.getReadableDatabase();
        Cursor cursor = db.rawQuery("select count(*) from " + DB_TABLE, null);

        int count = 0;
        if (cursor.moveToFirst()) {
            count = cursor.getInt(0);
        }

        Bundle bundle = new Bundle();
        bundle.putInt(Goods.KEY_ITEM_COUNT, count);

        cursor.close();
        db.close();

        return bundle;
    }

}

主要介绍下里面的几个常量:

UriMatcher:在创建UriMatcher对象uriMatcher时,我们传给构造函数的参数为UriMatcher.NO_MATCH,
它表示当uriMatcher不能匹配指定的URI时,就返回代码UriMatcher.NO_MATCH。
符号#表示匹配任何数字。
articleProjectionMap:  第三点是SQLiteQueryBuilder的使用。在query函数中,
我们使用SQLiteQueryBuilder来辅助数据库查询操作,
使用这个类的好处是我们可以不把数据库表的字段暴露出来,
而是提供别名给第三方应用程序使用,这样就可以把数据库表内部设计隐藏起来,方便后续扩展和维护。
第一个参数表示列的别名,第二个参数表示列的真实名称。
在这个例子中,我们把列的别名和和真实名称都设置成一样的。

以上就是 我们创建好的ContentProvider,这里我们需要再进一步操作将我们所编写的Class文件进行打包,来供ProviderTest使用。
要看怎么进行打包成Jar的请移步:http://www.jianshu.com/p/5cc512156b07
最后我们需要来进行注册:

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
          package="com.bsoft.factoryprovider">

    <permission android:name="com.bsoft.factoryprovider.READ_CONTENT"/>
    <permission android:name="com.bsoft.factoryprovider.WRITE_CONTENT"/>

    <application
        android:name=".APP"
        android:allowBackup="true"
        android:icon="@mipmap/ic_launcher"
        android:label="@string/app_name"
        android:roundIcon="@mipmap/ic_launcher_round"
        android:supportsRtl="true"
        android:theme="@style/AppTheme">
        <activity android:name=".MainActivity">
            <intent-filter>
                <action android:name="android.intent.action.MAIN"/>

                <category android:name="android.intent.category.LAUNCHER"/>
            </intent-filter>
        </activity>
        <provider
            android:name=".GoodsProvider"
            android:authorities="com.bsoft.factoryprovider"
            android:exported="true"
            android:grantUriPermissions="true"
            android:label="@string/provider_label"
            android:readPermission="com.bsoft.factoryprovider.READ_CONTENT"
            android:writePermission="com.bsoft.factoryprovider.WRITE_CONTENT">
            <meta-data
                android:name="android.support.FILE_PROVIDER_PATHS"
                android:resource="@xml/filepaths"/>
        </provider>
    </application>

</manifest>

这里由于7.0系统问题需要配置文件存储路径:

image.png
<?xml version="1.0" encoding="utf-8"?>
<paths>
    <files-path
        name="MyFolder"
        path="MyFolder/" />
</paths>

  • ProviderTest
    首先我们需要将Jar包导入项目并进行依赖。其次我们来创建一个实体类:

/**
 * Created by 泅渡者
 * Created on 2017/9/28.
 */

public class Fruits {
    public Fruits(int id, String name, String desc, String url) {
        this.id = id;
        this.name = name;
        this.desc = desc;
        this.url = url;
    }

    public int id;
    public String name;
    public String desc;
    public String url;
}

FruitsHelper.class 是我们用来操作Contentprovider的“工具类”


/**
 * Created by 泅渡者
 * Created on 2017/9/28.
 * 数据库助手类
 */

public class FruitsHelper {

    private ContentResolver resolver = null;
    private GoodsProvider goodsProvider;

    public FruitsHelper(Context context) {
        resolver = context.getContentResolver();
        goodsProvider = new GoodsProvider();
    }

    /**
     * 向ContentProvider添加数据 并返回ID
     **/
    public long insertFruits(Fruits fruits) {

        ContentValues values = new ContentValues();
        values.put(Goods.NAME, fruits.name);
        values.put(Goods.DESC, fruits.desc);
        values.put(Goods.URL, fruits.url);

        Uri uri = resolver.insert(Goods.CONTENT_URI, values);
        String itemId = uri.getPathSegments().get(1);
        return Integer.valueOf(itemId).longValue();
    }

    /**
     * 对ContentProvider更新数据并 返回状态
     **/
    public boolean updateFruits(Fruits fruits) {
        Uri uri = ContentUris.withAppendedId(Goods.CONTENT_URI, fruits.id);

        ContentValues values = new ContentValues();
        values.put(Goods.NAME, fruits.name);
        values.put(Goods.DESC, fruits.desc);
        values.put(Goods.URL, fruits.url);

        int count = resolver.update(uri, values, null, null);
        return count > 0;
    }

    public boolean removeFruits(int id) {
        Uri uri = ContentUris.withAppendedId(Goods.CONTENT_URI, id);

        int count = resolver.delete(uri, null, null);

        return count > 0;
    }

    /**
     * 对Contentprovider的Fruits 表进行遍历
     **/
    public LinkedList<Fruits> getAllFruits() {
        LinkedList<Fruits> fruitses = new LinkedList<Fruits>();

        String[] projection = new String[]{
                Goods.ID,
                Goods.NAME,
                Goods.DESC,
                Goods.URL
        };

        Cursor cursor = resolver.query(Goods.CONTENT_URI, projection, null, null, Goods.DEFAULT_SORT_ORDER);
        if (cursor.moveToFirst()) {
            do {
                int id = cursor.getInt(0);
                String name = cursor.getString(1);
                String desc = cursor.getString(2);
                String url = cursor.getString(3);

                Fruits fruits = new Fruits(id, name, desc, url);
                fruitses.add(fruits);
            } while (cursor.moveToNext());
        }

        return fruitses;
    }

    public int getFruitsCount() {
        int count = 0;
        Bundle bundle = resolver.call(Goods.CONTENT_URI, Goods.METHOD_GET_ITEM_COUNT, null, null);
        count = bundle.getInt(Goods.KEY_ITEM_COUNT, 0);

        return count;
    }

    public Fruits getArticleById(int id) {
        Uri uri = ContentUris.withAppendedId(Goods.CONTENT_URI, id);

        String[] projection = new String[]{
                Goods.ID,
                Goods.NAME,
                Goods.DESC,
                Goods.URL
        };

        Cursor cursor = resolver.query(uri, projection, null, null, Goods.DEFAULT_SORT_ORDER);

        if (!cursor.moveToFirst()) {
            return null;
        }

        String title = cursor.getString(1);
        String abs = cursor.getString(2);
        String url = cursor.getString(3);

        return new Fruits(id, title, abs, url);
    }

    public Fruits getArticleByPos(int pos) {
        Uri uri = ContentUris.withAppendedId(Goods.CONTENT_POS_URI, pos);

        String[] projection = new String[]{
                Goods.ID,
                Goods.NAME,
                Goods.DESC,
                Goods.URL
        };

        Cursor cursor = resolver.query(uri, projection, null, null, Goods.DEFAULT_SORT_ORDER);
        if (!cursor.moveToFirst()) {
            return null;
        }

        int id = cursor.getInt(0);
        String title = cursor.getString(1);
        String abs = cursor.getString(2);
        String url = cursor.getString(3);

        return new Fruits(id, title, abs, url);
    }
}

我们这里只做引导入门所以测试大多是日志:

package com.bsoft.providertest;

import android.content.ContentResolver;
import android.os.Bundle;
import android.support.v7.app.AppCompatActivity;

import com.socks.library.KLog;

public class MainActivity extends AppCompatActivity {
    private ContentResolver resolver = null;
    private FruitsHelper fruitsHelper = null;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        fruitsHelper = new FruitsHelper(this);

        /**增加数据**/
        Fruits fruits = new Fruits(4, "大苹果", "巨大", "wwww.baidu.com");
        long flag = fruitsHelper.insertFruits(fruits);
        KLog.i(flag);

        /**删除数据**/


        /**修改数据**/


        /**查找数据**/


    }

}
    <uses-permission android:name="com.bsoft.factoryprovider.WRITE_CONTENT"/>
    <uses-permission android:name="com.bsoft.factoryprovider.READ_CONTENT"/>

我们看下我的运行结果:

MainActivity.java: [ (MainActivity.java:23)#onCreate ] 5

这里我只做了个插入操作,剩余的删改,查。大家可以亲自试试。
代码地址:https://gitee.com/13102169005/Android_Projects.git

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

推荐阅读更多精彩内容