ContentProvider(外共享数据)
ContentProvider在Android中的作用是对外共享数据,也就是说通过ContentProvider可以吧应用中的数据共享给其他应用访问,其他的应用可以通过ContentProvider对应用中的数据进行增、删、改、查的操作。使用ContentProvider 的好处是统一了数据访问方式。它实际上是对SqliteOpenHelper的进一步封装,通过Uri映射来判断选择需要操作数据库中的哪个表并且进行想要的处理。
- Uri
首先我们了解下Uri,Uri代表操作数据表的绝对路径,它主要包含两部分信息。一个是需要操作的ContentProvider,二是对ContentProvider中的哪个表进行操作。对于ContentProvider来说,一个Uri由以下几部分组成。
- Scheme:是系统定好的 content://
- Authority:是ContentProvider的唯一标识
- Path:是该ContentProvider下要操作的数据库表
- Id: 是指表中的数据项ID
好的既然我们理解了其中的概念那么我们来带大家实践下!
创建两个项目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系统问题需要配置文件存储路径:
<?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