您的位置:首页 > 移动开发 > Android开发

如何在Android中自定义 Content Provider

2016-03-22 09:46 405 查看
http://codecloud.net/android-content-provider-5474.html

Android 提供的了多种不同的方法来供应用之间相互通信。Android app可以与其它app共享数据,这样其它app便可以使用数据数据来构建自己的业务。举个例子,你有时可能需要查询手机上的联系人的信息。在Android中推荐的方式是通过content providers来使用共享数据。Content provider是某些内容的拥有者,它提供定义好的API接口来供read, insert, update 和delete数据。Content provider在内部可以使用任何存储空间,比如本地文件、本地数据库或者远程服务器。在这篇文章中,我们将学习如何创建自己的 content provider 并从其它app来访问数据。



创建 Content Provider 类

我们从创建一个Content provider类开始,这类用来管理图片元数据。我们使用本地的 SQLite 数据库来存储数据,当然你可以将数据存在其它地方。

首先创建一个继承了 ContentProvider 的类ImagesProvider,并重写继承来的方法。

import android.content.ContentProvider;
import android.content.ContentValues;
import android.database.Cursor;
import android.net.Uri;
public class ImagesProvider extends ContentProvider {

@Override
public String getType(Uri uri) {
return "";
}

@Override
public boolean onCreate() {
return true;
}

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

@Override
public Uri insert(Uri uri, ContentValues values) {
return null;
}

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

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


在上面的代码中,我们创建了ImagesProvider类,并实重写了一些需要用到的方法。

我们将从实现getType方法入手,getType方法将数据的MIME类型作为字符串返回。

返回的MIME类型应该是这种格式:vnd../vnd..,对于单行应该是 android.cursor.item,对于多行而言应该是android.cursor.dir ,而应该是全局唯一的(使用包名)。对于相应的URI应该是唯一的。现在让我们更新一下getType方法的实现:

import android.content.ContentProvider;
import android.content.ContentValues;
import android.content.UriMatcher;
import android.database.Cursor;
import android.net.Uri;

public class ImagesProvider extends ContentProvider {

private static final String PROVIDER_NAME = "androidcontentproviderdemo.androidcontentprovider.images";
private static final Uri CONTENT_URI = Uri.parse("content://" + PROVIDER_NAME + "/images");
private static final int IMAGES = 1;
private static final int IMAGE_ID = 2;
private static final UriMatcher uriMatcher = getUriMatcher();
private static UriMatcher getUriMatcher() {
UriMatcher uriMatcher = new UriMatcher(UriMatcher.NO_MATCH);
uriMatcher.addURI(PROVIDER_NAME, "images", IMAGES);
uriMatcher.addURI(PROVIDER_NAME, "images/#", IMAGE_ID);
return uriMatcher;
}

@Override
public String getType(Uri uri) {
switch (uriMatcher.match(uri)) {
case IMAGES:
return "vnd.android.cursor.dir/vnd.com.androidcontentproviderdemo.androidcontentprovider.provider.images";
case IMAGE_ID:
return "vnd.android.cursor.item/vnd.com.androidcontentproviderdemo.androidcontentprovider.provider.images";

}
return "";
}
@Override
public boolean onCreate() {
return true;
}

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

@Override
public Uri insert(Uri uri, ContentValues values) {
return null;
}

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

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


实现 onCreate 方法和查询功能

为了存储内容,content provider需要一个 database helper类,创建 ImageDataBase类如下:

import android.content.Context;
import android.database.Cursor;
import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteOpenHelper;
import android.database.sqlite.SQLiteQueryBuilder;

public class ImageDataBase extends SQLiteOpenHelper {

private static final String DATABASE_NAME = "ImagesDatabase.db";
private static final String TABLE_NAME = "imagestore";
private static final String SQL_CREATE = "CREATE TABLE " + TABLE_NAME +
" (_id INTEGER PRIMARY KEY, IMAGETITLE TEXT , IMAGEURL TEXT , IMAGEDESC TEXT )";

private static final String SQL_DROP = "DROP TABLE IS EXISTS " + TABLE_NAME ;

ImageDataBase(Context context) {
super(context, DATABASE_NAME, null, 1);
}

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

@Override
public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
db.execSQL(SQL_DROP);
onCreate(db);
}

public Cursor getImages(String id, String[] projection, String selection, String[] selectionArgs, String sortOrder) {
SQLiteQueryBuilder sqliteQueryBuilder = new SQLiteQueryBuilder();
sqliteQueryBuilder.setTables(TABLE_NAME);

if(id != null) {
sqliteQueryBuilder.appendWhere("_id" + " = " + id);
}

if(sortOrder == null || sortOrder == "") {
sortOrder = "IMAGETITLE";
}
Cursor cursor = sqliteQueryBuilder.query(getReadableDatabase(),
projection,
selection,
selectionArgs,
null,
null,
sortOrder);
return cursor;
}
}


上面这个类继承了 SQLiteOpenHelper并在数据库ImagesDatabase.db中创建了一个命名为imagestore的表,设定的列属性有:’id’, IMAGETITLE, ‘IMAGEURL’ 和’IMAGEDESC’。函数getImages使用SQLiteQueryBuilder类查询数据库并返回结果cursor。现在我们来实现content provider中的onCreate and query函数。

import android.content.ContentProvider;
import android.content.ContentValues;
import android.content.Context;
import android.content.UriMatcher;
import android.database.Cursor;
import android.net.Uri;

public class ImagesProvider extends ContentProvider {

private static final String PROVIDER_NAME = "androidcontentproviderdemo.androidcontentprovider.images";
private static final Uri CONTENT_URI = Uri.parse("content://" + PROVIDER_NAME + "/images");
private static final int IMAGES = 1;
private static final int IMAGE_ID = 2;
private static final UriMatcher uriMatcher = getUriMatcher();
private static UriMatcher getUriMatcher() {
UriMatcher uriMatcher = new UriMatcher(UriMatcher.NO_MATCH);
uriMatcher.addURI(PROVIDER_NAME, "images", IMAGES);
uriMatcher.addURI(PROVIDER_NAME, "images/#", IMAGE_ID);
return uriMatcher;
}

private ImageDatabase imageDataBase = null;

@Override
public String getType(Uri uri) {
switch (uriMatcher.match(uri)) {
case IMAGES:
return "vnd.android.cursor.dir/vnd.com.androidcontentproviderdemo.androidcontentprovider.provider.images";
case IMAGE_ID:
return "vnd.android.cursor.item/vnd.com.androidcontentproviderdemo.androidcontentprovider.provider.images";

}
return "";
}

@Override
public boolean onCreate() {
Context context = getContext();
imageDataBase = new ImageDataBase(context);
return true;
}

@Override
public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder) {
String id = null;
if(uriMatcher.match(uri) == IMAGE_ID) {
//Query is for one single image. Get the ID from the URI.
id = uri.getPathSegments().get(1);
}
return imageDataBase.getImages(id, projection, selection, selectionArgs, sortOrder);
}

@Override
public Uri insert(Uri uri, ContentValues values) {
return null;
}

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

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


在 onCreate 函数中,我们创建了一个ImageDatabase 类的对象,并给它传递了一个context。存储这个变量,以至于我们可以用它来对content provider做各种操作。在query 函数中,我们检查查询是否具有给定的id,如果有,我们获取id,如果没有,将其记为null,并传递这些参数给 ImageDatabase:getImages 去查询image数据,并返回结果集cursor。

在 content provider 中 Inserting, Deleting, Updating data

实现好了查询函数后,接下来实现content provider中的其它函数。我们需要在ImageDatabase中添加支持实现insert, delete and update操作的函数,代码如下:

import android.content.ContentValues;
import android.content.Context;
import android.database.Cursor;
import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteOpenHelper;
import android.database.sqlite.SQLiteQueryBuilder;

import java.sql.SQLException;

public class ImageDataBase extends SQLiteOpenHelper {

private static final String DATABASE_NAME = "ImagesDatabase.db";
private static final String TABLE_NAME = "imagestore";
private static final String SQL_CREATE = "CREATE TABLE " + TABLE_NAME +
" (_id INTEGER PRIMARY KEY, IMAGETITLE TEXT , IMAGEURL TEXT , IMAGEDESC TEXT )";

private static final String SQL_DROP = "DROP TABLE IS EXISTS " + TABLE_NAME ;

ImageDataBase(Context context) {
super(context, DATABASE_NAME, null, 1);
}

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

@Override
public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
db.execSQL(SQL_DROP);
onCreate(db);
}

public Cursor getImages(String id, String[] projection, String selection, String[] selectionArgs, String sortOrder) {
SQLiteQueryBuilder sqliteQueryBuilder = new SQLiteQueryBuilder();
sqliteQueryBuilder.setTables(TABLE_NAME);

if(id != null) {
sqliteQueryBuilder.appendWhere("_id" + " = " + id);
}

if(sortOrder == null || sortOrder == "") {
sortOrder = "IMAGETITLE";
}
Cursor cursor = sqliteQueryBuilder.query(getReadableDatabase(),
projection,
selection,
selectionArgs,
null,
null,
sortOrder);
return cursor;
}

public long addNewImage(ContentValues values) throws SQLException {
long id = getWritableDatabase().insert(TABLE_NAME, "", values);
if(id <=0 ) {
throw new SQLException("Failed to add an image");
}

return id;
}

public int deleteImages(String id) {
if(id == null) {
return getWritableDatabase().delete(TABLE_NAME, null , null);
} else {
return getWritableDatabase().delete(TABLE_NAME, "_id=?", new String[]{id});
}
}

public int updateImages(String id, ContentValues values) {
if(id == null) {
return getWritableDatabase().update(TABLE_NAME, values, null, null);
} else {
return getWritableDatabase().update(TABLE_NAME, values, "_id=?", new String[]{id});
}
}
}


所有的新函数访问数据库并调用相应的insert, delete and update函数,并传递相应的id、content values参数。现在更新ImagesProvider 类如下:

import android.content.ContentProvider;
import android.content.ContentUris;
import android.content.ContentValues;
import android.content.Context;
import android.content.UriMatcher;
import android.database.Cursor;
import android.net.Uri;
import android.util.Log;

public class ImagesProvider extends ContentProvider {

private static final String PROVIDER_NAME = "androidcontentproviderdemo.androidcontentprovider.images";
private static final Uri CONTENT_URI = Uri.parse("content://" + PROVIDER_NAME + "/images");
private static final int IMAGES = 1;
private static final int IMAGE_ID = 2;
private static final UriMatcher uriMatcher = getUriMatcher();
private static UriMatcher getUriMatcher() {
UriMatcher uriMatcher = new UriMatcher(UriMatcher.NO_MATCH);
uriMatcher.addURI(PROVIDER_NAME, "images", IMAGES);
uriMatcher.addURI(PROVIDER_NAME, "images/#", IMAGE_ID);
return uriMatcher;
}

private ImageDatabase imageDataBase = null;

@Override
public String getType(Uri uri) {
switch (uriMatcher.match(uri)) {
case IMAGES:
return "vnd.android.cursor.dir/vnd.com.androidcontentproviderdemo.androidcontentprovider.provider.images";
case IMAGE_ID:
return "vnd.android.cursor.item/vnd.com.androidcontentproviderdemo.androidcontentprovider.provider.images";

}
return "";
}

@Override
public boolean onCreate() {
Context context = getContext();
imageDataBase = new ImageDatabase(context);
return true;
}

@Override
public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder) {
String id = null;
if(uriMatcher.match(uri) == IMAGE_ID) {
//Query is for one single image. Get the ID from the URI.
id = uri.getPathSegments().get(1);
}
return imageDataBase.getImages(id, projection, selection, selectionArgs, sortOrder);
}

@Override
public Uri insert(Uri uri, ContentValues values) {
try {
long id = imageDataBase.addNewImage(values);
Uri returnUri = ContentUris.withAppendedId(CONTENT_URI, id);
return returnUri;
} catch(Exception e) {
return null;
}
}

@Override
public int delete(Uri uri, String selection, String[] selectionArgs) {
String id = null;
if(uriMatcher.match(uri) == IMAGE_ID) {
//Delete is for one single image. Get the ID from the URI.
id = uri.getPathSegments().get(1);
}

return imageDataBase.deleteImages(id);
}

@Override
public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs) {
String id = null;
if(uriMatcher.match(uri) == IMAGE_ID) {
//Update is for one single image. Get the ID from the URI.
id = uri.getPathSegments().get(1);
}

return imageDataBase.updateImages(id, values);
}
}


在上面的代码中,如果URI是为获取单个image,我们就获取id值,否则,我们保持id为null。然后将值传递给ImageDataBase中相应的函数。

在 AndroidManifest 中声明 content provider

我们必须得在AndroidManifest.xml文件中用标签provide声明content provider。

<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.androidcontentproviderdemo.androidcontentprovider">
<application android:allowBackup="true" android:label="@string/app_name"
android:icon="@mipmap/ic_launcher" android:theme="@style/AppTheme">
<provider android:name="com.androidcontentproviderdemo.androidcontentprovider.ImagesProvider" android:authorities="androidcontentproviderdemo.androidcontentprovider.images">
</provider>
</application>
</manifest>


在标签中android:name属性应该是Content Provider类,android:authorities 应该指定使用content的URI。

使用 content provider

一旦上面的工作都完成了,我们便可以测试content provider了。因此,我们可以创建一个test app并在Main Activity测试。

import android.app.Activity;
import android.content.ContentValues;
import android.content.CursorLoader;
import android.database.Cursor;
import android.net.Uri;
import android.os.Bundle;
import android.util.Log;
import android.view.Menu;
import android.view.MenuItem;
import android.view.View;
import android.widget.EditText;
import android.widget.ListView;
import android.widget.SimpleCursorAdapter;
import android.widget.Toast;

public class MainActivity extends Activity {

private static final String PROVIDER_NAME = "androidcontentproviderdemo.androidcontentprovider.images";
private static final Uri CONTENT_URI = Uri.parse("content://" + PROVIDER_NAME + "/images");

private ListView listView;
private SimpleCursorAdapter adapter;

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

listView = (ListView) findViewById(R.id.lstViewImages);

adapter = new SimpleCursorAdapter(getBaseContext(),
R.layout.list_layout,
null,
new String[] { "IMAGETITLE", "IMAGEURL", "IMAGEDESC"},
new int[] { R.id.imgTitle , R.id.imgUrl, R.id.imgDesc }, 0);

listView.setAdapter(adapter);
refreshValuesFromContentProvider();
}

private void refreshValuesFromContentProvider() {
CursorLoader cursorLoader = new CursorLoader(getBaseContext(), CONTENT_URI,
null, null, null, null);
Cursor c = cursorLoader.loadInBackground();
adapter.swapCursor(c);
}

@Override
public boolean onCreateOptionsMenu(Menu menu) {
// Inflate the menu; this adds items to the action bar if it is present.
getMenuInflater().inflate(R.menu.menu_main, menu);
return true;
}

public void onClickAddImage(View view) {
ContentValues contentValues = new ContentValues();
contentValues.put("IMAGETITLE", ((EditText) findViewById(R.id.edtTxtImageTitle)).getText().toString());
contentValues.put("IMAGEURL" , ((EditText)findViewById(R.id.edtImageUrl)).getText().toString());
contentValues.put("IMAGEDESC", ((EditText) findViewById(R.id.edtImageDesc)).getText().toString());
Uri uri = getContentResolver().insert(CONTENT_URI, contentValues);
Toast.makeText(getBaseContext(), uri.toString(), Toast.LENGTH_LONG).show();
refreshValuesFromContentProvider();
}
}


这个activity涉及到了两个layout文件,一个是main content的view,一个是list view。实现如下:

activitymain.xml

<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent"
android:layout_height="match_parent" android:paddingLeft="@dimen/activity_horizontal_margin"
android:paddingRight="@dimen/activity_horizontal_margin"
android:paddingTop="@dimen/activity_vertical_margin"
android:paddingBottom="@dimen/activity_vertical_margin"
tools:context=".ContentProviderUsageActivity">

<LinearLayout
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_centerHorizontal="true">

<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textAppearance="?android:attr/textAppearanceMedium"
android:text="Image Title"
android:id="@+id/txtViewImageTitle"
android:layout_gravity="center_horizontal" />

<EditText
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:id="@+id/edtTxtImageTitle"
android:layout_gravity="center_horizontal" />

<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textAppearance="?android:attr/textAppearanceMedium"
android:text="Image URL"
android:id="@+id/txtViewImageUrl"
android:layout_gravity="center_horizontal" />

<EditText
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:id="@+id/edtImageUrl"
android:layout_gravity="center_horizontal" />

<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textAppearance="?android:attr/textAppearanceMedium"
android:text="Image description"
android:id="@+id/txtViewImageDesc"
android:layout_gravity="center_horizontal" />

<EditText
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:id="@+id/edtImageDesc"
android:layout_gravity="center_horizontal" />

<Button
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Add Image"
android:id="@+id/btnAddImage"
android:layout_gravity="center_horizontal"
android:onClick="onClickAddImage" />

<ListView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:id="@+id/lstViewImages"
android:layout_gravity="center_horizontal" />
</LinearLayout>

</RelativeLayout>


listlayout.xml

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical" android:layout_width="match_parent"
android:layout_height="match_parent">

<TextView
android:id="@+id/imgTitle"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
/>

<TextView
android:id="@+id/imgUrl"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
/>

<TextView
android:id="@+id/imgDesc"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
/>

</LinearLayout>


在上面的代码中,onCreate 函数使用CursorLoader 函数从content provider中获取所有contents,同时传递CONTENT_URI给它。

为了插入值,getContentResolver().insert 方法被调用,同时传递了content URI和ContentValues供插入。运行效果如下:



Android 中的Content provider提供了干净系统级的方式供apps来共享数据和使用其它apps的数据。它标准化了如何在多个apps中共享数据。Android中提供了很多内建的content providers和API,这使得非常容易自定义content providers。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签:  android content provider