您的位置:首页 > 编程语言 > PHP开发

ContentProvider

2016-04-25 11:06 423 查看

#

适用场景和概念介绍

摘自

/article/4783911.html(不得不说,这是一篇很好的文章)

适用场景

1) ContentProvider为存储和读取数据提供了统一的接口

2) 使用ContentProvider,应用程序可以实现数据共享

3) android内置的许多数据都是使用ContentProvider形式,供开发者调用的(如视频,音频,图片,通讯录等)

相关概念介绍

1)ContentProvider简介

当应用继承ContentProvider类,并重写该类用于提供数据和存储数据的方法,就可以向其他应用共享其数据。虽然使用其他方法也可以对外共享数据,但数据访问方式会因数据存储的方式而不同,如:采用文件方式对外共享数据,需要进行文件操作读写数据;采用sharedpreferences共享数据,需要使用sharedpreferences API读写数据。而使用ContentProvider共享数据的好处是统一了数据访问方式。

2)Uri类简介

Uri uri = Uri.parse("content://com.changcheng.provider.contactprovider/contact")


在Content Provider中使用的查询字符串有别于标准的SQL查询。很多诸如select, add, delete, modify等操作我们都使用一种特殊的URI来进行,这种URI由3个部分组成, “content://”, 代表数据的路径,和一个可选的标识数据的ID。以下是一些示例URI:

content://media/internal/images  这个URI将返回设备上存储的所有图片
content://contacts/people/  这个URI将返回设备上的所有联系人信息
content://contacts/people/45 这个URI返回单个结果(联系人信息中ID为45的联系人记录)


尽管这种查询字符串格式很常见,但是它看起来还是有点令人迷惑。为此,Android提供一系列的帮助类(在android.provider包下),里面包含了很多以类变量形式给出的查询字符串,这种方式更容易让我们理解一点,因此,如上面content://contacts/people/45这个URI就可以写成如下形式:

Uri person = ContentUris.withAppendedId(People.CONTENT_URI,  45);


要创建我们自己的Content Provider的话,我们需要遵循以下几步:

a. 创建一个继承了ContentProvider父类的类

b. 定义一个名为CONTENT_URI,并且是public static final的Uri类型的类变量,你必须为其指定一个唯一的字符串值,最好的方案是以类的全名称, 如:

public static final Uri CONTENT_URI = Uri.parse( “content://com.google.android.MyContentProvider”);


c. 定义你要返回给客户端的数据列名。如果你正在使用Android数据库,必须为其定义一个叫_id的列,它用来表示每条记录的唯一性。

d. 创建你的数据存储系统。大多数Content Provider使用Android文件系统或SQLite数据库来保持数据,但是你也可以以任何你想要的方式来存储。

e. 如果你要存储字节型数据,比如位图文件等,数据列其实是一个表示实际保存文件的URI字符串,通过它来读取对应的文件数据。处理这种数据类型的Content Provider需要实现一个名为_data的字段,_data字段列出了该文件在Android文件系统上的精确路径。这个字段不仅是供客户端使用,而且也可以供ContentResolver使用。客户端可以调用ContentResolver.openOutputStream()方法来处理该URI指向的文件资源;如果是ContentResolver本身的话,由于其持有的权限比客户端要高,所以它能直接访问该数据文件。

f. 声明public static String型的变量,用于指定要从游标处返回的数据列。

g. 查询返回一个Cursor类型的对象。所有执行写操作的方法如insert(), update() 以及delete()都将被监听。我们可以通过使用ContentResover().notifyChange()方法来通知监听器关于数据更新的信息。

h. 在AndroidMenifest.xml中使用
<provider>
标签来设置Content Provider。

i. 如果你要处理的数据类型是一种比较新的类型,你就必须先定义一个新的MIME类型,以供ContentProvider.geType(url)来返回。MIME类型有两种形式:一种是为指定的单个记录的,还有一种是为多条记录的。这里给出一种常用的格式:

vnd.android.cursor.item/vnd.yourcompanyname.contenttype (单个记录的MIME类型)


比如, 一个请求列车信息的URI如

content://com.example.transportationprovider/trains/122


可能就会返回typevnd.android.cursor.item/vnd.example.rail这样一个MIME类型。

vnd.android.cursor.dir/vnd.yourcompanyname.contenttype (多个记录的MIME类型)


比如, 一个请求所有列车信息的URI如

content://com.example.transportationprovider/trains


可能就会返回vnd.android.cursor.dir/vnd.example.rail这样一个MIME 类型。

  

栗子

讲解Provider之前,我们把例子的准备工作做好。

一个是获取数据的API

一个是数据库中的表格

一个是访问数据库的DbHelper

最后才是Provider。

1. API

使用openweathermap的API获取天气
 http://api.openweathermap.org/data/2.5/forecast/daily?q=xxx&mode=xxx&units=xxx&cnt=xxx&APPID=xxxxxxxxxxx";


一个例子中的几个类:

WeatherDbHelper:extends SQLiteOpenHelper  数据库操作
WeatherContract:类中对应的表及一些操作
WeatherProvider:extends ContentProvider


2. SQLite

存储,表


table: location

Name类型对应LocationEntry类中的String值
location_settingCOLUMN_LOCATION_SETTING
coord_latCOLUMN_COORD_LAT
coord_longCOLUMN_COORD_LONG
city_nameCOLUMN_CITY_NAME
主要记录 城市名、经纬度、setting

table:weather

Name类型备注
location_idlocation表与weather表的连接处 ,Foreign Key
datelong存储格式是毫秒级(后期需要处理)
weather_condition_idint由API返回的,主要用于确定用什么天气图标
short_descvarchar简短描述天气状况,API返回的有短有长,eg: “clear” VS “sky is clear”
minfloat最低温
maxfloat最高温
humidityfloat湿度
pressurefloat气压
windfloat风力
degreesfloat风向
WeatherContract

这个类中有WeatherEntry和LocationEntry俩类,对应表中各项。

BASE

//impaments BaseColumns,这个类里定义了 _ID = "_id";
public static final class LocationEntry implements BaseColumns {

public static final String TABLE_NAME = "location" ;

public static final String COLUMN_LOCATION_SETTING = "location_setting";
public static  final String COLUMN_COORD_LAT = "coord_lat";
public static final String COLUMN_COORD_LONG = "coord_long";
public static final String COLUMN_CITY_NAME = "city_name";

}

public static final class WeatherEntry implements BaseColumns {

public static final String TABLE_NAME = "weather";

// Column with the foreign key into the location table.
public static final String COLUMN_LOC_KEY = "location_id";

public static final String COLUMN_DATE = "date";

public static final String COLUMN_WEATHER_CONTIDITION_ID = "weather_condition_id";
public static final String COLUMN_SHORT_DESC = "short_desc";

public static final String COLUMN_MIN_TEMP = "min";
public static final String COLUMN_MAX_TEMP = "max";

public static final String COLUMN_HUMIDITY = "humidity";
public static final String COLUMN_PRESSURE = "pressure";
public static final String COLUMN_WIND_SPEED = "wind";

// Degrees are meteorological degrees (e.g, 0 is north, 180 is south).  Stored as floats.
public static final String COLUMN_DEGREES = "degrees";

}


MORE

WeatherContract中更多定义:

//根据Uri简介中介绍,我们需要设置一个这样的常量,便于进行查询等功能。
public static final String CONTENT_AUTHORITY = "com.xxxx.app";
public static final Uri BASE_CONTENT_URI = Uri.parse("content://" + CONTENT_AUTHORITY);

public static final String PATH_WEATHER = "weather";
public static final String PATH_LOCATION = "location";


在各自类(表)中也需要有一些常量的定义,以及 必要的方法

// LocationEntry中
public static final Uri CONTENT_URI = BASE_CONTENT_URI.buildUpon().appendPath(PATH_LOCATION).build();

//DIR 代表 返回的Cursor中包含0或多条记录
public static final String CONTENT_TYPE =
ContentResolver.CURSOR_DIR_BASE_TYPE + "/" + CONTENT_AUTHORITY + "/" + PATH_LOCATION;
//ITEM 代表 返回的Cursor中为特定ID的一条记录
public static final String CONTENT_ITEM_TYPE =
ContentResolver.CURSOR_ITEM_BASE_TYPE + "/" + CONTENT_AUTHORITY + "/" + PATH_LOCATION;

// WeatherEntry中
public static final Uri CONTENT_URI = BASE_CONTENT_URI.buildUpon().appendPath(PATH_WEATHER).build();

public static final String CONTENT_TYPE =
ContentResolver.CURSOR_DIR_BASE_TYPE + "/" + CONTENT_AUTHORITY + "/" + PATH_WEATHER;
public static final String CONTENT_ITEM_TYPE =
ContentResolver.CURSOR_ITEM_BASE_TYPE + "/" + CONTENT_AUTHORITY + "/" + PATH_WEATHER;


由于Location中作为存储Weather的location部分,(Location附属于Weather)

故而Location中只用一个根据id取location数据的方法就够了

public static Uri buildLocationUri(long _id)
{
return ContentUris.withAppendedId(CONTENT_URI,
}


对于Weather表,则需要较多方法

//返回根据某id进行查询的Uri
public static Uri buildWeatherUri(long id){
return ContentUris.withAppendedId(CONTENT_URI , id);
}
//返回根据某位置查询的Uri。(locationSetting为右上角菜单中的Setting的location)
public static Uri buildWeatherLocation(String locationSetting){
return CONTENT_URI.buildUpon().appendPath(locationSetting).build();
}

public static Uri buildWeatherLocationWithStartDate(String locationSetting , long startDate){
long normalizedDate = normalizeDate(startDate); //
return CONTENT_URI.buildUpon().appendPath(locationSetting).appendQueryParameter(COLUMN_DATE , Long.toString(normalizedDate)).build();

}

public static Uri buildWeatherLocationWithDate(String locationSetting , long date){

return CONTENT_URI.buildUpon().appendPath(locationSetting).appendPath(Long.toString(normalizeDate(date)).build();

}

// 获取Uri中的部分数据
public static String getLocationSettingFromUri(Uri uri){
//  content://com.xxx.app/location/...
return uri.getPathSegments().get(1);
}
public static long getStartDateFromUri(Uri uri){

String dateString = uri.getQueryParameter(COLUMN_DATE);
if(null != dateString && dateString.length() > 0)
return Long.parseLong(dateString);
else return 0;
}
public static long getDateFromUri(Uri uri){
return Long.parseLong(uri.getPathSegments().get(2));
}


WeatherDbHelper

在Android中要想使用Sqlite数据库,首先应该创建一个类继承SQLiteOpenHelper类,我们把这个类命名为DatabaseHelper,它作为一个访问Sqlite的助手类,提供了两方面的功能:

第一 getReadableDatabase()/getWritableDatabase()可以获得SQLiteDatabase对象,通过该对象可以对数据库进行操作;

第二 提供OnCreate()和onUpgrade()两个回调函数,允许我们在创建和升级数据库时,进行自己的操作;

public class WeatherDbHelper extends SQLiteOpenHelper{

//打Log时,通常会用的标记 实用
private static final String LOG_TAG = WeatherDbHelper.class.getSimpleName();

//if you change the database schema , you must increment the database version
private static final int DATABASE_VERSION = 2 ;
static final String DATABASE_NAME = "weather.db";

public WeatherDbHelper(Context context){
super(context , DATABASENAME , null  , DATABASE_VERSION);
}
}


注:在继承SQLiteOpenHelper类时,必须要有public DatabaseHelper(Context context, String name, CursorFactory factory,int version) 这个构造方法。

不得不说这个DATABASE_VERSION很重要,当数据库已经成功建立以后,如果修改了数据库中的表等,需要改变这个VERSION值,不然程序会崩溃的。

Override SQLiteOpenHelper中的onCreate等方法

onCreate(SQLiteDatabase):

该函数是在第一次创建数据库的时候执行,实际上是在第一次得到SQLiteDatabse对象的时候,才会调用这个方法

@Override
public void onCreate(SQLiteDatabase sqLiteDatabase){
// weather表
// FOREIGN KEY (location) REFERENCES location (xxid)

//编辑器还有待改进啊,引号是怎么了,不起作用了。(这里的引号有些问题,我先用单引号替换一下,莫怪)
final String SQL_CREATE_WEATHER_TABLE = 'CREATE TABLE' +
WeatherEntry.TABLE_NAME + " ( " +
WeatherEntry._ID + " INTEGER PRIMARY KEY AUTOINCREMENT ," +
WeatherEntry.COLUMN_LOC_KEY + " INTEGER NOT NULL, " +
WeatherEntry.COLUMN_DATE + " INTEGER NOT NULL, " +
WeatherEntry.COLUMN_SHORT_DESC + " TEXT NOT NULL, " +
WeatherEntry.COLUMN_WEATHER_CONTIDITION_ID + " INTEGER NOT NULL," +

WeatherEntry.COLUMN_MIN_TEMP + " REAL NOT NULL, " +
WeatherEntry.COLUMN_MAX_TEMP + " REAL NOT NULL, " +

WeatherEntry.COLUMN_HUMIDITY + " REAL NOT NULL, " +
WeatherEntry.COLUMN_PRESSURE + " REAL NOT NULL, " +
WeatherEntry.COLUMN_WIND_SPEED + " REAL NOT NULL, " +
WeatherEntry.COLUMN_DEGREES + " REAL NOT NULL, " +

// Set up the location column as a foreign key to location table.
" FOREIGN KEY (" + WeatherEntry.COLUMN_LOC_KEY + ") REFERENCES " +
LocationEntry.TABLE_NAME + " (" + LocationEntry._ID + "), " +

// To assure the application have just one weather entry per day per location, it's created a UNIQUE constraint with REPLACE strategy
" UNIQUE (" + WeatherEntry.COLUMN_DATE + ", " +
WeatherEntry.COLUMN_LOC_KEY + ") ON CONFLICT REPLACE);";

//location表

final String SQL_CREATE_LOCATION_TABLE = "CREATE TABLE " + LocationEntry.TABLE_NAME + "(" +

LocationEntry._ID + " INTEGER PRIMARY KEY AUTOINCREMENT, "+

LocationEntry.COLUMN_LOCATION_SETTING + " TEXT UNIQUE NOT NULL , " +
LocationEntry.COLUMN_CITY_NAME + " TEXT NOT NULL, "+
LocationEntry.COLUMN_COORD_LAT + " REAL NOT NULL, "+
LocationEntry.COLUMN_COORD_LONG + " REAL NOT NULL ); "
;

sqLiteDatabase.execSQL(SQL_CREATE_WEATHER_TABLE);
sqLiteDatabase.execSQL(SQL_CREATE_LOCATION_TABLE);

}


onUpgrade(SQLiteDatabase , int oldVersion , int newVersion)

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

sqLiteDatabase.execSQL("DROP TABLE IF EXISTS " + LocationEntry.TABLE_NAME);
sqLiteDatabase.execSQL("DROP TABLE IF EXISTS " + WeatherEntry.TABLE_NAME);
onCreate(sqLiteDatabase);

}


WeatherProvider

Uri都已经在WeatherContract中定义好了,现在就开始利用这些准备工作了。

public class WeatherProvider extends ContentProvider {

//The URI Matcher used by this content provider
private UriMatcher sUriMatcher = buildUriMatcher();//自行将自定义的Uri注册好
private WeatherDbHelper mOpenHelper ;

//重写getType()方法
//Use the Uri Matcher to determine what kind of URI this is
@Override
public String getType(Uri uri){

}

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

}
@Override
public boolean onCreate(){
mOpenHelpre = new WeatherDbHelper(getContext());
return true;
}

@Override
public Uri insert(Uri uri , ContentValues values){

}

@Override int delete(Uri uri ,String selection , String[] selectionArgs){

}

@Override
public Uri update(
Uri uri , ContentValues values , String selection , String[] selectionArgs){

}

@Override
public int bulkInsert(Uri uri , ContentValues[] values){

}

}


buildUriMatcher()方法就是需要注册自定义的URI等,返回一个URIMatcher对象。这是一个静态方法。

注册了4个Uri

static UriMatcher buildUriMatcher() {

final UriMatcher sURIMatcher = new UriMatcher(UriMatcher.NO_MATCH);
final String authority = WeatherContract.CONTENT_AUTHORITY;

//使用addURI方法,将自定义的Uri添加,也就是注册吧。从WeatherContract中获取定义好的常量。(String authority , String path ,int code)
sURIMatcher.addURI(authority , WeatherContract.PATH_WEATHER , WEATHER);
sURIMatcher.addURI(authority , WeatherContract.PATH_WEATHER+"/*" , WEATHER_WITH_LOCATION);
sURIMatcher.addURI(authority , WeatherContract.PATH_WEATHER + "/*/#" ,WEATHER_WITH_LOCATION_AND_DATE);
sURIMatcher.addURI(authority , WeatherContract.PATH_LOCATION , LOCATION);

return sURIMatcher;
}


关于UriMatcher类

https://developer.android.com/reference/android/content/UriMatcher.html

addURI方法中的第三个参数 code的定义为:

static final int WEATHER = 100;
static final int WEATHER_WITH_LOCATION = 101;
static final int WEATHER_WITH_LOCATION_AND_DATE = 102;
static final int LOCATION = 300;


完成了Uri的注册后,还需要重写getType(Uri uri)方法:

对应于注册,根据解析出来的code(match),返回是Location表的内容,还是Weather表的内容,dir(多个)还是item(单个)。

官方文档中这么说的

Then when you need to match against a URI, call match(Uri), providing the URL that you have been given. You can use the result to build a query, return a type, insert or delete a row, or whatever you need, without duplicating all of the if-else logic that you would otherwise need. For example:

@Override
public String getType(Uri uri){
final int match = sUriMatcher.match(uri);

switch (match) {
case WEATHER_WITH_LOCATION_AND_DATE:
return WeatherContract.WeatherEntry.CONTENT_ITEM_TYPE;
case WEATHER_WITH_LOCATION:
return WeatherContract.WeatherEntry.CONTENT_TYPE;
case WEATHER:
return WeatherContract.WeatherEntry.CONTENT_TYPE;
case LOCATION:
return WeatherContract.LocationEntry.CONTENT_TYPE;
default:
throw new UnsupportedOperationException("Unknown uri:" + uri);
}

}


OK,现在来做查询这个步骤

查询数据库的结果数据通常存储在Cursor中

根据所给Uri match的结果,分别执行对应的查询,

SQLiteDatabase中,query方法

query(table , columns , selection , selectionArgs , groupBy , having , sortOrder, limit)


参数类型说明
tableString表名
columnsString[]返回的数据列的名字,如果是全部列的话,则置为null即可
selectionString对行的过滤,相当于sql的where语句,如果返回全部行,则置为null
selectionArgsString[]过滤行的条件参数
groupByStringsql基本语法
havingStringsql基本语法
sortOrderString对返回行进行排序
limitString
这里列出了较多的参数部分,不用的参数设置为null即可,或者重写参数较少的query方法。

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

Cursor retCursor;

switch (sUriMatcher.match(uri)) {
// "weather/*/#"
case WEATHER_WITH_LOCATION_AND_DATE: {
retCursor = getWeatherByLocationSettingAndDate(uri , projection, sortOrder);
break;
}
// "weather/*"
case WEATHER_WITH_LOCATION: {
retCursor = getWeatherByLocationSetting(uri , projection , sortOrder);
break;
}
// "weather"
case WEATHER: {
retCursor = mOpenHelper.getReadableDatabase().query(
WeatherContract.WeatherEntry.TABLE_NAME,
projection,
selection,
selectionArgs,
null,
null,
sortOrder
);
break;
}
// "location"
case LOCATION: {
retCursor = mOpenHelper.getReadableDatabase().query(
WeatherContract.LocationEntry.TABLE_NAME,
projection,
selection,
selectionArgs,
null,
null,
sortOrder
);
break;
}
}
// **************NOTICE*****************
retCursor.setNotificationUri(getContext().getContentResolver() , uri);
return retCursor;
}


NOTICE的内容,按住Ctrl ,鼠标点击进去,可以看到源码部分

里面注释说:Uri有改变时,对该Cursor发出通知。

查看AbstractCursor.setNotificationUri源码时,你会发现,这个方法的作用就是在ContentResolver上注册了一个Observer。

好吧,Java内置的观察者模式。。。(跳过,再分析吧)

由于前两个case中包含了一些对于location和date的操作,所以呢,单独提取出来,作为一个函数,会清晰一些;另外,对于这两个case,需要同时对两个表(weather & location)操作,需要用到 SQLiteQueryBuilder。

SQLiteQueryBuilder代码部分,这里面包含有对静态块的使用

Java静态变量的初始化,这个链接讲的挺好的。

/article/10130716.html

private static final SQLiteQueryBuilder sWeatherByLocationSettingQueryBuilder;

static{
sWeatherByLocationSettingQueryBuilder = new SQLiteQueryBuilder();

//This is an inner join which looks like
//weather INNER JOIN location ON weather.location_id = location._id
sWeatherByLocationSettingQueryBuilder.setTables(
WeatherContract.WeatherEntry.TABLE_NAME + " INNER JOIN " +
WeatherContract.LocationEntry.TABLE_NAME +
" ON " + WeatherContract.WeatherEntry.TABLE_NAME +
"." + WeatherContract.WeatherEntry.COLUMN_LOC_KEY +
" = " + WeatherContract.LocationEntry.TABLE_NAME +
"." + WeatherContract.LocationEntry._ID);
}


使用SQLiteQueryBuilder进行查询,相应的,SQLiteQueryBuilder的query方法也对应有其参数。

有一点需要说明,这个方法没有用到提供的selection和selectionArgs,因为这个方法名本身就包含它的查询含义了

//location.location_setting = ?
private static final String sLocationSettingSelection =
WeatherContract.LocationEntry.TABLE_NAME+
"." + WeatherContract.LocationEntry.COLUMN_LOCATION_SETTING + " = ? ";

//location.location_setting = ? AND date >= ?
private static final String sLocationSettingWithStartDateSelection =
WeatherContract.LocationEntry.TABLE_NAME+
"." + WeatherContract.LocationEntry.COLUMN_LOCATION_SETTING + " = ? AND " +
WeatherContract.WeatherEntry.COLUMN_DATE + " >= ? ";

//location.location_setting = ? AND date = ?
private static final String sLocationSettingAndDaySelection =
WeatherContract.LocationEntry.TABLE_NAME +
"." + WeatherContract.LocationEntry.COLUMN_LOCATION_SETTING + " = ? AND " +
WeatherContract.WeatherEntry.COLUMN_DATE + " = ? ";

//以上三条变量就对应着与 Location表和Weather表 间需要进行查询的selection。

//固定地点和日期,返回零条或一条数据 item
private Cursor getWeatherByLocationSettingAndDate(
Uri uri , String[] projection , String sortOrder){

String locationSetting = WeatherContract.WeatherEntry.getLocationSettingFromUri(uri);
long date = WeatherContract.WeatherEntry.getDateFromUri(uri);

return sWeatherByLocationSettingQueryBuilder.query(mOpenHelper.getReadableDatabase(),
projection,
sLocationSettingAndDaySelection,
new String[]{locationSetting , Long.toString(date)},
null,
null,
sortOrder
);
}

//固定地点,没有date限制,或大于某date。返回0条或多条数据 dir
private Cursor getWeatherByLocationSetting(Uri uri,String[] projection, String sortOrder){

String locationSetting = WeatherContract.WeatherEntry.getLocationSettingFromUri(uri);
long startDate = WeatherContract.WeatherEntry.getStartDateFromUri(uri);

String[] selectionArgs;
String selection;

if (startDate == 0) {
selection = sLocationSettingSelection;
selectionArgs = new String[]{locationSetting};
} else {
selectionArgs = new String[]{locationSetting, Long.toString(startDate)};
selection = sLocationSettingWithStartDateSelection;
}

return sWeatherByLocationSettingQueryBuilder.query(mOpenHelper.getReadableDatabase(),
projection,
selection,
selectionArgs,
null,
null,
sortOrder
);

}


接下来的 插入 ,删除,更新 等方法,执行完毕,需要notify一下,这是观察者模式下的一个步骤。

insert方法:要么给location表添加数据,要么给weather表添加数据

@Override
public Uri insert(Uri uri, ContentValues values) {
final SQLiteDatabase db = mOpenHelper.getWritableDatabase();
final int match = sUriMatcher.match(uri);
Uri returnUri;

switch (match) {
case WEATHER: {
normalizeDate(values);
long _id = db.insert(WeatherContract.WeatherEntry.TABLE_NAME, null, values);
if ( _id > 0 )
returnUri = WeatherContract.WeatherEntry.buildWeatherUri(_id);
else
throw new android.database.SQLException("Failed to insert row into " + uri);
break;
}
case LOCATION:{
long _id = db.insert(WeatherContract.LocationEntry.TABLE_NAME , null , values);
if (_id > 0)
returnUri = WeatherContract.LocationEntry.buildLocationUri(_id);
else
throw new android.database.SQLException("Failed to insert row into " + uri);
break;
}
default:
throw new UnsupportedOperationException("Unknown uri: " + uri);
}

//****************NOTICE********************
getContext().getContentResolver().notifyChange(uri, null);
return returnUri;
}


delete方法:

@Override
public int delete(Uri uri, String selection, String[] selectionArgs) {
// Student: Start by getting a writable database
final SQLiteDatabase db = mOpenHelper.getWritableDatabase();
final int match = sUriMatcher.match(uri);
int rowsDeleted ;

if(null == selection) selection ="1";//see db.delete,
switch (match){
case WEATHER:{
rowsDeleted = db.delete(WeatherContract.WeatherEntry.TABLE_NAME,selection , selectionArgs);
break;
}
case LOCATION:{
rowsDeleted = db.delete(WeatherContract.LocationEntry.TABLE_NAME , selection , selectionArgs);
break;
}
default:
throw new UnsupportedOperationException("Unknown uri: " + uri);
}

// Student: Use the uriMatcher to match the WEATHER and LOCATION URI's we are going to
// handle.  If it doesn't match these, throw an UnsupportedOperationException.

// Student: A null value deletes all rows.  In my implementation of this, I only notified
// the uri listeners (using the content resolver) if the rowsDeleted != 0 or the selection
// is null.
// Oh, and you should notify the listeners here.

// Student: return the actual rows deleted
if(rowsDeleted != 0)
{
getContext().getContentResolver().notifyChange(uri,null);
}
return rowsDeleted;
}


update方法:

@Override
public int update(

Uri uri, ContentValues values, String selection, String[] selectionArgs) {
// Student: This is a lot like the delete function.  We return the number of rows impacted by the update.

final SQLiteDatabase db = mOpenHelper.getWritableDatabase();
final int match = sUriMatcher.match(uri);
int rowsUpdated;

switch (match){
case WEATHER:
normalizeDate(values);
rowsUpdated = db.update(WeatherContract.WeatherEntry.TABLE_NAME , values , selection , selectionArgs);
break;
case LOCATION:
rowsUpdated = db.update(WeatherContract.LocationEntry.TABLE_NAME , values , selection, selectionArgs);
break;
default:
throw new UnsupportedOperationException("Unknown uri: "+ uri);
}
if(rowsUpdated != 0){
getContext().getContentResolver().notifyChange(uri , null);
}
return rowsUpdated;
}


值得一提的是bulkInsert方法,当一次性存储多条数据时,如果使用insert方法,则会多次打开关闭数据库,这个时候,应该采用事务,Transaction。(这里就不多说了)

bulkInsert方法

@Override
public int bulkInsert(Uri uri, ContentValues[] values) {
final SQLiteDatabase db = mOpenHelper.getWritableDatabase();
final int match = sUriMatcher.match(uri);
switch (match) {
case WEATHER:
db.beginTransaction();
int returnCount = 0;
try {
for (ContentValues value : values) {
normalizeDate(value);
long _id = db.insert(WeatherContract.WeatherEntry.TABLE_NAME, null, value);
if (_id != -1) {
returnCount++;
}
}
db.setTransactionSuccessful();
} finally {
db.endTransaction();
}
getContext().getContentResolver().notifyChange(uri, null);
return returnCount;
case LOCATION:
db.beginTransaction();
returnCount = 0;
try {
for (ContentValues value : values) {
normalizeDate(value);
long _id = db.insert(WeatherContract.LocationEntry.TABLE_NAME, null, value);
if (_id != -1) {
returnCount++;
}
}
db.setTransactionSuccessful();
} finally {
db.endTransaction();
}
getContext().getContentResolver().notifyChange(uri, null);
return returnCount;
default:
return super.bulkInsert(uri, values);
}
}


这几个方法中,有调用一个对date操作的函数,normalizeDate,其实就是WeatherContract当中 normalizeDate的方法的调用。

这里给出代码

private void normalizeDate(ContentValues values) {
// normalize the date value
if (values.containsKey(WeatherContract.WeatherEntry.COLUMN_DATE)) {
long dateValue = values.getAsLong(WeatherContract.WeatherEntry.COLUMN_DATE);
values.put(WeatherContract.WeatherEntry.COLUMN_DATE, WeatherContract.normalizeDate(dateValue));
}
}


调用Provider

前面给出了一系列的Provider的讲解,那么问题来了,什么时候调用自定义的Provider呢?我查了一下资料。

之后,让我们来使用这个定义好的Content Provider:

1)为应用程序添加ContentProvider的访问权限。

2)通过getContentResolver()方法得到ContentResolver对象。

3)调用ContentResolver类的query()方法查询数据,该方法会返回一个Cursor对象。

4)对得到的Cursor对象进行分析,得到需要的数据。

5)调用Cursor类的close()方法将Cursor对象关闭。


其中通过ContentResolver的调用,在源码当中,就是对Provider的调用

不好意思,我又来粘链接了,但是人家写的还是挺好懂的

/article/3995259.html

总结

写得还是有点乱了,这个其实是Udacity中的Sunshine的例子,我觉得讲的挺不错的

https://www.udacity.com/course/viewer#!/c-ud853/l-1614738811/e-1664298679/m-1664298681

可以选择课程1,2等等。

获取数据库数据等内容,就下节说。

Content Provider(这也是摘自别处的内容)

ContentProvider简介

在Android官方指出的Android的数据存储方式共有五种,分别是:SharedPreferences、网络存储、文件存储、外存储、SQLite。但是我们知道一般这些存储都只是在单独的一个应用程序之中达到一个数据的共享,有时候我们需要操作其他应用程序的一些数据,例如我们需要操作系统里的媒体库、通讯录等,这时我们就可能通过ContentProvider来满足我们的需求了。

为什么选择ContentProvider?

ContentProvider向我们提供了在应用程序之间共享数据的一种机制。

(1)ContentProvider为存储和获取数据提供了统一的接口。ContentProvider对数据进行封装,不用关心数据存储的细节。使用表的形式存储数据

(2)Android为常见的一些数据提供了默认的ContentProvider(包括音频、视频、图片和通讯录等)

Uri介绍

Uri类似于URL请求

格式:

content://com.example.android.sunshine.app/location/94043

content:// 是固定的

com.example.android.sunshine.app URI的标识,Authority:一般是定义该ContentProvider的包名。

/location 路径,通俗来说就是你要操作的数据库中表的名字

/94043 如果URI中包含表示需要获取的记录的ID;则就返回该ID对应的数据,如果没有ID,则表示返回全部;

UriMatcher类

因为Uri代表了要操作的数据,所以我们经常需要解析Uri,并从Uri中获取数据。Android系统提供了两个用于操作Uri的工具类,分别为UriMatcher和ContentUris。

UriMatcher类用于匹配Uri。

首先第一步把你需要匹配Uri路径全部给注册上,如下:

//常量UriMatcher.NO_MATCH表示不匹配任何路径的返回码
UriMatcher  sMatcher = new UriMatcher(UriMatcher.NO_MATCH);
//如果match()方法匹配content://com.bing.procvide.personprovider/person路径,返回匹配码为1
sMatcher.addURI("com.bing.procvide.personprovider", "person", 1);
//添加需要匹配uri,如果匹配就会返回匹配码
sMatcher.addURI("com.bing.provider.personprovider", "person/#", 2);//#号为通配符

switch (sMatcher.match(Uri.parse("content://com.ljq.provider.personprovider/person/10"))) {
case 1
break;
case 2
break;
default://不匹配
break;
}


ContentUris类使用介绍

ContentUris类用于操作Uri路径后面的ID部分,它有两个比较实用的方法:

withAppendedId(uri, id)用于为路径加上ID部分:

Uri uri = Uri.parse("content://com.bing.provider.personprovider/person")
Uri resultUri = ContentUris.withAppendedId(uri, 10);
//生成后的Uri为:content://com.bing.provider.personprovider/person/10
parseId(uri)方法用于从路径中获取ID部分:
Uri uri = Uri.parse("content://com.ljq.provider.personprovider/person/10")
long personid = ContentUris.parseId(uri);//获取的结果为:10


使用ContentProvider共享数据

ContentProvider类主要方法

public boolean onCreate():
在ContentProvider创建后就会被调用,Android开机后,ContentProvider在其它应用第一次访问它时才会被创建。
public Uri insert(Uri uri, ContentValues values):
供外部应用向ContentProvider中添加数据
public int delete(Uri uri , String[] selectionArgs):
供外部应用从ContentProvider删除数据
public int update(Uri uri , ContentValues values,String selection , String[] selectionArgs):
供外部应用更新ContentProvider中的数据。
public Cursor query(Uri uri ,String[] projection,String selection , String[] selectionArgs,String sortOrder):
供外部应用从ContentProvider中获取数据。
public String getType(Uri uri):
用于返回当前Uri所代表数据的MIME类型。


针对MIME类型:

集合类型:MIME类型字符串应该以vnd.android.cursor.dir/开头,

ContentResolver.CURSOR_DIR_BASE_TYPE

非集合类型:MIME类型字符串应该以vnd.android.cursor.item/开头,

ContentResolver.CURSOR_ITEM_BASE_TYPE

CursorResolver

SimpleCursorAdapter

Tips

Android Studio 快捷键:

Ctrl + 斜杠:

注释或取消注释当前行或选中的代码块,双斜杠的方式 “//”

Ctrl + shift + 斜杠:

注释或取消注释当前行或选中的代码块, “/……/”
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: