ContentProvider是Android四大组件之一,其重要性就不言而喻了。在学习Android的过程中,个人认为这个组件比另外3个组件Activity,Service,Broadcastreceiver更难上手,这篇文章也是参考了诸多的资料和分析代码而对ContentProvider有了自己的认识和理解,本文中如有理解错误和不对的地方,大家提出来一起讨论。
首先什么是ContentProvider?这个ContentProvider又是用来干什么的?
ContentProvider也叫内容提供者,说白了就是将自己程序的一些数据共享出来给其他应用程序读写,也可以给自己程序的其他组件(如:Activity)来读写。
有朋友可能会问了,为什么不能把一个程序中单独的Sqlite(.db文件)和SharePreference(.xml文件)直接共享出来给其他程序用了?
其实使用其他方法也可以对外共享数据,但数据访问方式会因数据存储的方式而不同,如:采用文件方式对外共享数据,需要进行文件操作读写数据;采用SharedPreferences共享数据,需要使用SharedPreferencesAPI读写数据等(这些方法我暂时也没掌握)。而使用ContentProvider共享数据的好处是统一了数据访问方式。
下面一边看代码一边讲解
先写一个提供数据共享的应用程序,本章需要先了解Sqlite相关知识,还不了解的朋友可以参考下面给的连接
首先还是像之前Android数据库基本操作中那样,写一个类继承SQLiteOpenHelper,并在OnCreate方法中创建表。
DbHelper
package com.huahuadashen.mycontentprovideractivity;
import android.content.Context;
import android.database.DatabaseErrorHandler;
import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteDatabase.CursorFactory;
import android.database.sqlite.SQLiteOpenHelper;
public class DbHelper extends SQLiteOpenHelper{
public DbHelper(Context context, String name, CursorFactory factory,
int version) {
super(context, name, factory, version);
// TODO Auto-generated constructor stub
}
@Override
public void onCreate(SQLiteDatabase db) {
// TODO Auto-generated method stub
String sql = "CREATE TABLE dota (_id INTEGER PRIMARY KEY AUTOINCREMENT, heroname VARCHAR,description VARCHAR)";
db.execSQL(sql);
}
@Override
public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
// TODO Auto-generated method stub
}
}
接着我们就写本文最关键的类MyContentProvider,此类继承ContentProvider
定义一个UriMatcher类
private static final UriMatcher MATCHER = new UriMatcher(
UriMatcher.NO_MATCH);
private static final int DOTAS = 1;
private static final int DOTA = 2;
static {
MATCHER.addURI("com.huahuadashen.mycontentprovideractivity.mycontentprovider", "dota", DOTAS);
MATCHER.addURI("com.huahuadashen.mycontentprovideractivity.mycontentprovider", "dota/#", DOTA);
}
好,这里我们先理解Uri,UriMatcher,ContentUris3个类的概念与使用方法
什么是Uri?
Uri代表了要操作的数据,Uri主要包含了两部分信息:
1.需要操作的ContentProvider
上面代码中要操作的就是"com.huahuadashen.mycontentprovideractivity.mycontentprovider"
固定标识(Authority)为 "包名.定义的类名"
2.对ContentProvider中的什么数据进行操作,一个Uri由以下几部分组成:
1)scheme:ContentProvider(内容提供者)的scheme已经由Android所规定为:content://。
2)标识(Authority):用于唯一标识这个ContentProvider,外部调用者可以根据这个标识来找到它,然后去读写数据。
3)路径(path):可以用来表示我们要操作的数据,路径的构建应根据业务而定,如下:
要操作dota表中id为10的记录,可以构建这样的路径:/dota/10
要操作dota表中id为10的记录的heroname字段,可以构建这样的路径:dota/10/heroname
要操作dota表中的所有记录,可以构建这样的路径:/dota
要操作的数据不一定来自数据库,也可以是文件等他存储方式,如下:
要操作xml文件中dota节点下的heroname节点,可以构建这样的路径:/dota/heroname
如果要把一个字符串转换成Uri,可以使用Uri类中的parse()方法,如下:
Uri uri = Uri.parse("content://com.huahuadashen.mycontentprovideractivity.mycontentprovider/dota")
什么是UriMatcher和ContentUris
我们很经常需要解析Uri,并从Uri中获取数据。Android系统提供了两个用于操作Uri的工具类,分别为UriMatcher 和ContentUris 。掌握它们的使用,会便于我们的开发工作。
UriMatcher:用于匹配Uri,它的用法如下:
1.首先把你需要匹配Uri路径全部给注册上,如下:
//常量UriMatcher.NO_MATCH表示不匹配任何路径的返回码(-1)。
UriMatcher uriMatcher = new UriMatcher(UriMatcher.NO_MATCH);
//添加需要匹配uri,如果匹配就会返回匹配码
UriMatcher.addURI(“com.huahuadashen.mycontentprovideractivity.mycontentprovider”, “dota”, 1);
UriMatcher.addURI(“com.huahuadashen.mycontentprovideractivity.mycontentprovider”, “dota/#”, 2);//#号为通配符
2.注册完需要匹配的Uri后,就可以使用uriMatcher.match(uri)方法对输入的Uri进行匹配,如果匹配就返回匹配码,匹配码是调用addURI()方法传入的第三个参数
//如果用UriMatcher.match()方法匹配
content://com.huahuadashen.mycontentprovideractivity.mycontentprovider/dota路径,返回匹配码为1
//如果UriMatcher.match()方法匹配content://com.huahuadashen.mycontentprovideractivity.mycontentprovider/dota/10路径,返回匹配码为2
这里参考了contentprovider的学习实例总结的博文
直接贴MyContentProvider的代码
package com.huahuadashen.mycontentprovideractivity;
import android.content.ContentProvider;
import android.content.ContentUris;
import android.content.ContentValues;
import android.content.UriMatcher;
import android.database.Cursor;
import android.database.sqlite.SQLiteDatabase;
import android.net.Uri;
public class MyContentProvider extends ContentProvider{
private DbHelper dbHelper;
// 定义一个UriMatcher类
private static final UriMatcher MATCHER = new UriMatcher(
UriMatcher.NO_MATCH);
private static final int DOTAS = 1;
private static final int DOTA = 2;
static {
MATCHER.addURI("com.huahuadashen.mycontentprovideractivity.mycontentprovider", "dota", DOTAS);
MATCHER.addURI("com.huahuadashen.mycontentprovideractivity.mycontentprovider", "dota/#", DOTA);
}
// 删除数据
@Override
public int delete(Uri uri, String selection, String[] selectionArgs) {
SQLiteDatabase db = dbHelper.getWritableDatabase();
int count = 0;
switch (MATCHER.match(uri)) {
case DOTAS:
count = db.delete("dota", selection, selectionArgs);
return count;
case DOTA:
long id = ContentUris.parseId(uri);
String where = "_id=" + id;
if (selection != null && !"".equals(selection)) {
where = selection + " and " + where;
}
count = db.delete("dota", where, selectionArgs);
return count;
default:
throw new IllegalArgumentException("Unkwon Uri:" + uri.toString());
}
}
// 返回当前操作的数据的mimeType
@Override
public String getType(Uri uri) {
switch (MATCHER.match(uri)) {
case DOTAS:
return "vnd.android.cursor.dir/dota";
case DOTA:
return "vnd.android.cursor.item/dota";
default:
throw new IllegalArgumentException("Unkwon Uri:" + uri.toString());
}
}
// 插入数据
@Override
public Uri insert(Uri uri, ContentValues values) {
SQLiteDatabase db = dbHelper.getWritableDatabase();
Uri insertUri = null;
switch (MATCHER.match(uri)) {
case DOTAS:
long rowid = db.insert("dota", "", values);
insertUri = ContentUris.withAppendedId(uri, rowid);
break;
default:
throw new IllegalArgumentException("Unkwon Uri:" + uri.toString());
}
return insertUri;
}
//在ContentProvider创建的时候执行
@Override
public boolean onCreate() {
dbHelper = new DbHelper(this.getContext(),"huahuadashen.db",null,1);
return false;
}
// 查询数据
@Override
public Cursor query(Uri uri, String[] projection, String selection,
String[] selectionArgs, String sortOrder) {
SQLiteDatabase db = dbHelper.getWritableDatabase();
switch (MATCHER.match(uri)) {
case DOTAS:
// 查询所有的数据
return db.query("dota", projection, selection, selectionArgs,
null, null, sortOrder);
case DOTA:
// 查询某个ID的数据
// 通过ContentUris这个工具类解释出ID
long id = ContentUris.parseId(uri);
String where = " _id=" + id;
if (!"".equals(selection) && selection != null) {
where = selection + " and " + where;
}
return db.query("dota", projection, where, selectionArgs, null,
null, sortOrder);
default:
throw new IllegalArgumentException("unknow uri" + uri.toString());
}
}
// 更新数据
@Override
public int update(Uri uri, ContentValues values, String selection,
String[] selectionArgs) {
SQLiteDatabase db = dbHelper.getWritableDatabase();
int count = 0;
switch (MATCHER.match(uri)) {
case DOTAS:
count = db.update("dota", values, selection, selectionArgs);
break;
case DOTA:
// 通过ContentUri工具类得到ID
long id = ContentUris.parseId(uri);
String where = "_id=" + id;
if (selection != null && !"".equals(selection)) {
where = selection + " and " + where;
}
count = db.update("dota", values, where, selectionArgs);
break;
default:
throw new IllegalArgumentException("Unkwon Uri:" + uri.toString());
}
return count;
}
}
MyContentProvider需要重写ContentProvider的6个方法,然后我们可以在自己的程序里通过Activity往数据库写入一些数据
package com.huahuadashen.mycontentprovideractivity;
import android.net.Uri;
import android.os.Bundle;
import android.app.Activity;
import android.content.ContentResolver;
import android.content.ContentUris;
import android.content.ContentValues;
import android.database.Cursor;
import android.view.Menu;
import android.view.View;
import android.widget.Button;
import android.widget.Toast;
public class MainActivity extends Activity {
private Button btn1;
private Button btn2;
private Button btn3;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
btn1 = (Button)findViewById(R.id.btn1);
btn1.setOnClickListener(new BtnClick());
btn2 = (Button)findViewById(R.id.btn2);
btn2.setOnClickListener(new BtnClick());
btn3 = (Button)findViewById(R.id.btn3);
btn3.setOnClickListener(new BtnClick());
btn3.setVisibility(View.GONE);
}
private class BtnClick implements View.OnClickListener{
@Override
public void onClick(View v) {
//增加数据
if(v.getId() == R.id.btn1)
{
ContentResolver cr = MainActivity.this.getContentResolver();
Uri uri = Uri.parse("content://com.huahuadashen.mycontentprovideractivity.mycontentprovider/dota");
ContentValues values = new ContentValues();
values.put("heroname", "幽鬼");
values.put("description", "1号位");
Uri result = cr.insert(uri, values);
if (ContentUris.parseId(result)>0) {
Toast.makeText(MainActivity.this, "增加成功", Toast.LENGTH_LONG).show();
}
}
//查询数据
else if(v.getId() == R.id.btn2)
{
ContentResolver cr = MainActivity.this.getContentResolver();
Uri uri = Uri.parse("content://com.huahuadashen.mycontentprovideractivity.mycontentprovider/dota");
Cursor c = cr.query(uri,null, null, null, null);
//循环显示
for(c.moveToFirst();!c.isAfterLast();c.moveToNext()){
Toast.makeText(MainActivity.this,
"第"+c.getInt(0)+"条记录,英雄是"+c.getString(1)+",位置是"+c.getString(2),
Toast.LENGTH_SHORT).show();
}
c.close();
}
//清空数据
else if(v.getId() == R.id.btn3)
{
ContentResolver cr = MainActivity.this.getContentResolver();
Uri uri = Uri.parse("content://com.huahuadashen.mycontentprovideractivity.mycontentprovider/dota");
cr.delete(uri, null, null);
}
}
}
@Override
public boolean onCreateOptionsMenu(Menu menu) {
// Inflate the menu; this adds items to the action bar if it is present.
getMenuInflater().inflate(R.menu.main, menu);
return true;
}
}
可以看到有一个新类ContentResolver,这个是干嘛的呢?
当外部应用程序或者内部应用程序的其他组件(如Activity)需要对ContentProvider中的数据进行添加、删除、修改和查询操作时,就要使用ContentResolver 类来完成,要获取ContentResolver 对象,可以使用Activity提供的getContentResolver()方法。 ContentResolver使用insert、delete、update、query方法,来操作数据
既然是这样,其实我们在操作ContentProvider提供的数据时,仅仅只用得到ContentResolver 对象,然后知道需要操作ContentProvider的Uri就可以了.
我们知道ContentProvider是4大组件之一,那么他一样一定要在AndroidManifest.xml添加声明才行
android:name=".MyContentProvider"
android:exported="true">//这句很关键,表示可以被其他应用程序读写
第一个程序就写好了,实现的功能:
1,提供了一个ContentProvider可以供其他程序去读写数据
2,程序内部组件Activity通过ContentResolver向数据库中写入数据和查询数据
下面实现通过外部应用来访问修改上面的数据库,就只有一个acticity
package com.huahua.readprovider;
import android.net.Uri;
import android.os.Bundle;
import android.app.Activity;
import android.content.ContentResolver;
import android.content.ContentUris;
import android.content.ContentValues;
import android.database.Cursor;
import android.view.Menu;
import android.view.View;
import android.widget.Button;
import android.widget.Toast;
public class MainActivity extends Activity {
private Button btn1;
private Button btn2;
private Button btn3;
private Button btn4;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
btn1 = (Button)findViewById(R.id.btn1);
btn1.setOnClickListener(new BtnClick());
btn2 = (Button)findViewById(R.id.btn2);
btn2.setOnClickListener(new BtnClick());
btn3 = (Button)findViewById(R.id.btn3);
btn3.setOnClickListener(new BtnClick());
btn4 = (Button)findViewById(R.id.btn4);
btn4.setOnClickListener(new BtnClick());
}
private class BtnClick implements View.OnClickListener{
@Override
public void onClick(View v) {
//增加数据
if(v.getId() == R.id.btn1)
{
ContentResolver cr = MainActivity.this.getContentResolver();
Uri uri = Uri.parse("content://com.huahuadashen.mycontentprovideractivity.mycontentprovider/dota");
ContentValues values = new ContentValues();
values.put("heroname", "修补匠");
values.put("description", "2号位");
Uri result = cr.insert(uri, values);
if (ContentUris.parseId(result)>0) {
Toast.makeText(MainActivity.this, "增加成功", Toast.LENGTH_SHORT).show();
}
}
//查询数据
else if(v.getId() == R.id.btn2)
{
ContentResolver cr = MainActivity.this.getContentResolver();
Uri uri = Uri.parse("content://com.huahuadashen.mycontentprovideractivity.mycontentprovider/dota");
Cursor c = cr.query(uri,null, null, null, null);
//循环显示
for(c.moveToFirst();!c.isAfterLast();c.moveToNext()){
Toast.makeText(MainActivity.this,
"第"+c.getInt(0)+"条记录,英雄是"+c.getString(1)+",位置是"+c.getString(2),
Toast.LENGTH_SHORT).show();
}
c.close();
}
//删除数据
else if(v.getId() == R.id.btn3)
{
ContentResolver cr = MainActivity.this.getContentResolver();
//这里删除的是表中第一行数据
Uri uri = Uri.parse("content://com.huahuadashen.mycontentprovideractivity.mycontentprovider/dota/1");
int result =cr.delete(uri, null, null);
if (result >= 1) {
Toast.makeText(MainActivity.this, "删除成功", Toast.LENGTH_LONG).show();
}else{
Toast.makeText(MainActivity.this, "这行数据不存在了!", Toast.LENGTH_LONG).show();
}
}
//更新数据
else if(v.getId() == R.id.btn4)
{
ContentResolver cr = MainActivity.this.getContentResolver();
//这里更改的是表中第二行数据
Uri uri = Uri.parse("content://com.huahuadashen.mycontentprovideractivity.mycontentprovider/dota/2");
ContentValues values = new ContentValues();
values.put("heroname", "随机英雄");
values.put("description","6号位");
int result = cr.update(uri, values, null, null);
if (result >= 1) {
Toast.makeText(MainActivity.this, "更新成功", Toast.LENGTH_SHORT).show();
}else{
Toast.makeText(MainActivity.this, "这行数据不存在了!", Toast.LENGTH_LONG).show();
}
}
}
}
@Override
public boolean onCreateOptionsMenu(Menu menu) {
// Inflate the menu; this adds items to the action bar if it is present.
getMenuInflater().inflate(R.menu.main, menu);
return true;
}
}
1,往数据库添加数据,
2,查询数据库中内容
3,修改数据库某一行的数据
4,删除数据库某一行的数据
增,删,改,查方法跟之前的activity一样.
下面是源码的下载地址
ContentProvider源码