Android组件系列----ContentProvider内容提供者

文章来源:http://www.cnblogs.com/smyhvae/p/4108017.html

【正文】
一、ContentProvider简介

ContentProvider内容提供者(四大组件之一)主要用于在不同的应用程序之间实现数据共享的功能。

ContentProvider可以理解为一个Android应用对外开放的接口,只要是符合它所定义的Uri格式的请求,均可以正常访问执行操作。其他的Android应用可以使用ContentResolver对象通过与ContentProvider同名的方法请求执行,被执行的就是ContentProvider中的同名方法。所以ContentProvider有很多对外可以访问的方法,在ContentResolver中均有同名的方法,是一一对应的。

Android附带了许多有用的ContentProvider,但是本文暂时不涉及到这么多(本文将学习如何创建自己的ContentProvider)。Android中自带的ContentProvider包括:

Browser:存储如浏览器的信息。
CallLog:存储通话记录等信息。
Contacts Provider:存储联系人(通讯录)等信息。
MediaStore:存储媒体文件的信息。
Settings:存储设备的设置和首选项信息。
此外,还有日历、

ContentProvider的方法:

如果要创建自己的内容提供者,需要新建一个类继承抽象类ContentProvider,并重写其中的抽象方法。抽象方法如下:

boolean onCreate()   
初始化提供者

Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder)  
查询数据,返回一个数据Cursor对象。其中参数selection和selectionArgs是外部程序提供的查询条件

Uri insert(Uri uri, ContentValues values) 
插入一条数据。参数values是需要插入的值

int update(Uri uri, ContentValues values, String selection, String[] selectionArgs) 
根据条件更新数据

int delete(Uri uri, String selection, String[] selectionArgs)  
根据条件删除数据

String getType(Uri uri)   
返回MIME类型对应内容的URI

除了onCreate()和getType()方法外,其他的均为CRUD操作,这些方法中,Uri参数为与ContentProvider匹配的请求Uri,剩下的参数可以参见SQLite的CRUD操作,基本一致。

备注:还有两个非常有意思的方法,必须要提一下,call()和bulkInsert()方法,使用call,理论上可以在ContentResolver中执行ContentProvider暴露出来的任何方法,而bulkInsert()方法用于插入多条数据。

Uri:

在Android中,Uri是一种比较常见的资源访问方式。而对于ContentProvider而言,Uri也是有固定格式的::

:ContentProvider的srandard_prefix始终是content://。
:ContentProvider的名称。
:请求的数据类型。
:指定请求的特定数据。
在ContentProvider的CRUD操作,均会传递一个Uri对象,通过这个对象来匹配对应的请求。那么如何确定一个Uri执行哪项操作呢?需要用到一个UriMatcher对象,这个对象用来帮助内容提供者匹配Uri。它所提供的方法非常简单,仅有两个:

void addURI(String authority,String path,int code):添加一个Uri匹配项,authority为AndroidManifest.xml中注册的ContentProvider中的authority属性;path为一个路径,可以设置通配符,#表示任意数字,*表示任意字符;code为自定义的一个Uri代码。
int match(Uri uri):匹配传递的Uri,返回addURI()传递的code参数。

二、代码举例

PersonDao是增删改查数据库的工具类,并在PersonContentProvider中得到调用。DBHelper用于初始化SQLite数据库。

PersonContentProvider用于向外提供增删改查的接口。并最终在ContentResolverTest的MyTest.java中进行单元测试,实现CRUD。

本文的核心类是:PersonContentProvider和MyTest

下面来看一下具体的实现步骤。

新建工程文件ContetProviderTest01。

(1)新建类PersonDao:用于进行对SQLite的CRUD操作。代码如下:

PersonDao.java:


package com.example.contentprovidertest01.dao;

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

import com.example.contentprovidertest01.db.DBHelper;

public class PersonDao {
    private DBHelper helper = null;

    public PersonDao(Context context) {
        helper = new DBHelper(context);
    }

    //方法:插入操作,返回的long类型为:插入当前行的行号
    public long insertPerson(ContentValues values) {
        long id = -1;
        SQLiteDatabase database = null;
        try {
            database = helper.getWritableDatabase();
            id = database.insert("person", null, values);
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            if (database != null) {
                database.close();
            }
        }
        return id;
    }

    public int deletePerson(String whereClause, String[] whereArgs) {
        int count = -1;
        SQLiteDatabase database = null;
        try {
            database = helper.getWritableDatabase();
            count = database.delete("person", whereClause, whereArgs);
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            if (database != null) {
                database.close();
            }
        }
        return count;
    }

    public int updatePerson(ContentValues values, String whereClause,
            String[] whereArgs) {
        SQLiteDatabase database = null;
        int count = -1;
        try {
            database = helper.getWritableDatabase();
            count = database.update("person", values, whereClause, whereArgs);
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            if (null != database) {
                database.close();
            }
        }
        return count;
    }

    public Cursor queryPersons(String selection, String[] selectionArgs) {
        SQLiteDatabase database = null;
        Cursor cursor = null;
        try {
            database = helper.getReadableDatabase();
            cursor = database.query(true, "person", null, selection,
                    selectionArgs, null, null, null, null);
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            if (null != database) {
                // database.close();
            }
        }
        return cursor;
    }

}

(2)新建类DBHelper:用于初始化SQLiate数据库

DBHelper.java:

package com.example.contentprovidertest01.db;

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

public class DBHelper extends SQLiteOpenHelper {

    private static String name = "mydb.db"; // 数据库的名字
    private static int version = 1; // 数据库的版本

    public DBHelper(Context context) {
        super(context, name, null, version);
    }

    @Override
    public void onCreate(SQLiteDatabase db) {
        // 只能支持基本数据类型:varchar int long float boolean text blob clob
        // 建表语句执行
        String sql = "create table person(id integer primary key autoincrement,name varchar(64),address varchar(64))";
        db.execSQL(sql);
    }

    @Override
    public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
        // TODO Auto-generated method stub
        String sql = "alter table person add sex varchar(8)";
        db.execSQL(sql);
    }

}

(3)【核心】新建类PersonContentProvider,继承ContetProvider

PersonContentProvider.java:

package com.example.contentprovidertest01;

import com.example.contentprovidertest01.dao.PersonDao;

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

public class PersonContentProvider extends ContentProvider {

    private final String TAG = "PersonContentProvider";
    private PersonDao personDao = null;
    private static final UriMatcher URI_MATCHER = new UriMatcher(
            UriMatcher.NO_MATCH);// 默认的规则是不匹配的
    private static final int PERSON = 1; // 操作单行记录
    private static final int PERSONS = 2; // 操作多行记录
    // 往UriMatcher中添加匹配规则。注意,这里面的url不要写错了,我就是因为写错了,半天没调试出来。哎···
    static {
        // 添加两个URI筛选
        URI_MATCHER.addURI("com.example.contentprovidertest01.PersonContentProvider",
                "person", PERSONS);
        // 使用通配符#,匹配任意数字
        URI_MATCHER.addURI("com.example.contentprovidertest01.PersonContentProvider",
                "person/#", PERSON);
    }

    public PersonContentProvider() {

    }

    @Override
    public boolean onCreate() {
        // 初始化一个数据持久层
        personDao = new PersonDao(getContext());
        //Log.i(TAG, "--->>onCreate()被调用");
        return true;
    }

    @Override
    public Uri insert(Uri uri, ContentValues values) {
        Uri resultUri = null;
        // 解析Uri,返回Code
        int flag = URI_MATCHER.match(uri);
        switch (flag) {
        case PERSONS:
            //调用数据库的访问方法   
            long id = personDao.insertPerson(values); //执行插入操作的方法,返回插入当前行的行号
            resultUri = ContentUris.withAppendedId(uri, id);
            Log.i(TAG,"--->>插入成功, id=" + id);
            Log.i(TAG,"--->>插入成功, resultUri=" + resultUri.toString());
            System.out.println("insert success");
            break;
        }
        return resultUri;
    }

    //方法:删除记录。注:参数:selection和selectionArgs是查询的条件,是由外部(另一个应用程序)传进来的
    @Override
    public int delete(Uri uri, String selection, String[] selectionArgs) {
        int count = -1; //影响数据库的行数
        try {
            int flag = URI_MATCHER.match(uri);
            switch (flag) {
            case PERSON:
                // delete from student where id=?
                // 单条数据,使用ContentUris工具类解析出结尾的Id
                long id = ContentUris.parseId(uri);
                String where_value = "id = ?";
                String[] args = { String.valueOf(id) };
                count = personDao.deletePerson(where_value, args);
                break;
            case PERSONS:
                count = personDao.deletePerson(selection, selectionArgs);
                break;
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
        Log.i(TAG, "--->>删除成功,count=" + count);
        return count;
    }

    @Override
    public int update(Uri uri, ContentValues values, String selection,
            String[] selectionArgs) {
        int count = -1;
        try {
            int flag = URI_MATCHER.match(uri);
            switch (flag) {
            case PERSON:
                long id = ContentUris.parseId(uri);
                String where_value = " id = ?";
                String[] args = { String.valueOf(id) };
                count = personDao.updatePerson(values, where_value, args);
                break;
            case PERSONS:
                count = personDao
                        .updatePerson(values, selection, selectionArgs);
                break;
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
        Log.i(TAG, "--->>更新成功,count=" + count);
        return count;
    }

    @Override
    public Cursor query(Uri uri, String[] projection, String selection,
            String[] selectionArgs, String sortOrder) {
        Cursor cursor = null;
        try {
            int flag = URI_MATCHER.match(uri);
            switch (flag) {
            case PERSON:
                long id = ContentUris.parseId(uri);
                String where_value = " id = ?";
                String[] args = { String.valueOf(id) };
                cursor = personDao.queryPersons(where_value, args);
                break;
            case PERSONS:
                cursor = personDao.queryPersons(selection, selectionArgs);
                break;
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
        Log.i(TAG, "--->>查询成功,Count=" + cursor.getCount());
        return cursor;
    }

    @Override
    public String getType(Uri uri) {
        int flag = URI_MATCHER.match(uri);
        switch (flag) {
        case PERSON:
            return "vnd.android.cursor.item/person"; // 如果是单条记录,则为vnd.android.cursor.item/
                                                        // + path

        case PERSONS:
            return "vnd.android.cursor.dir/persons"; // 如果是多条记录,则为vnd.android.cursor.dir/
                                                        // + path
        }
        return null;
    }

    @Override
    public Bundle call(String method, String arg, Bundle extras) {
        Log.i(TAG, "--->>" + method);
        Bundle bundle = new Bundle();
        bundle.putString("returnCall", "call被执行了");
        return bundle;
    }
}

UriMatcher类的作用是:匹配内容uri,默认的规则是不匹配的。UriMatcher提供了一个addURI方法:

void android.content.UriMatcher.addURI(String authority, String path, int code)
这三个参数分别代表:权限、路径、和一个自定义代码。一般第一个参数是uri(包名.内容提供者的类名),第二个参数一般是数据库的表名。

匹配规则的解释:*表示匹配任意字符,#表示匹配任意数字。注:如果内部的匹配规则越多,越容易访问。

getType(Uri uri)方法:所有的内容提供者都必须提供的一个方法。用于获取uri对象所对应的MIME类型。

然后,每编写一个内容提供者,都必须在清单文件中进行声明。在AndroidManifest.xml中节点中增加,格式如下:

<provider
    android:name=".内容提供者的类名"
    android:authorities="包名.内容提供者的类名"
    android:exported="是否允许其他应用访问" >
</provider>

所以在本程序中,在AndroidManifest.xml的节点中增加如下代码:

<provider
  android:name=".PersonContentProvider"
  android:authorities="com.example.contentprovidertest01.PersonContentProvider" 
  android:exported="true">
</provider>

(4)单元测试类:

这里需要涉及到另外一个知识:ContentResolver内容访问者。

要想访问ContentProvider,则必须使用ContentResolver。可以通过ContentResolver来操作ContentProvider所暴露处理的接口。一般使用Content.getContentResolver()方法获取ContentResolver对象。第一段中已经提到:ContentProvider有很多对外可以访问的方法,在ContentResolver中均有同名的方法,是一一对应的。所以它也存在insert、query、update、delete等方法。
MyTest.java:

package com.example.contentresolvertest;

import android.content.ContentResolver;
import android.content.ContentValues;
import android.database.Cursor;
import android.net.Uri;
import android.os.Bundle;
import android.test.AndroidTestCase;
import android.util.Log;

public class MyTest extends AndroidTestCase {

    public MyTest() {
        // TODO Auto-generated constructor stub

    }

    public void calltest() {
        ContentResolver contentResolver = getContext().getContentResolver();
        Uri uri = Uri
                .parse("content://com.example.contentprovidertest01.PersonContentProvider/person");
        Bundle bundle = contentResolver.call(uri, "method", null, null);
        String returnCall = bundle.getString("returnCall");
        Log.i("main", "-------------->" + returnCall);
    }

    //测试方法:向数据库中添加记录。如果之前没有数据库,则会自动创建
    public void insert() {
        // 使用内容解析者ContentResolver访问内容提供者ContentProvider
        ContentResolver contentResolver = getContext().getContentResolver();
        ContentValues values = new ContentValues();
        values.put("name", "生命贰号");
        values.put("address", "湖北");
        // content://authorities/person
        // http://
        Uri uri = Uri
                .parse("content://com.example.contentprovidertest01.PersonContentProvider/person");
        contentResolver.insert(uri, values);
    }

    //测试方法:删除单条记录。如果要删除所有记录:content://com.example.contentprovidertest01.PersonContentProvider/person
    public void delete() {
        ContentResolver contentResolver = getContext().getContentResolver();
        Uri uri = Uri
                .parse("content://com.example.contentprovidertest01.PersonContentProvider/person/2");//删除id为1的记录
        contentResolver.delete(uri, null, null);
    }

    //测试方法:根据条件删除记录。
    public void deletes() {
        ContentResolver contentResolver = getContext().getContentResolver();
        Uri uri = Uri
                .parse("content://com.example.contentprovidertest01.PersonContentProvider/person");
        String where = "address=?"; 
        String[] where_args = { "HK" };
        contentResolver.delete(uri, where, where_args);  //第二个参数表示查询的条件"address=?",第三个参数表示占位符中的具体内容
    }

    //方法:根据id修改记录。注:很少有批量修改的情况。
    public void update() {
        ContentResolver contentResolver = getContext().getContentResolver();
        Uri uri = Uri
                .parse("content://com.example.contentprovidertest01.PersonContentProvider/person/2");
        ContentValues values = new ContentValues();
        values.put("name", "李四");
        values.put("address", "上海");
        contentResolver.update(uri, values, null, null);
    }

    //方法:根据条件来修改记录。
    public void updates() {
        ContentResolver contentResolver = getContext().getContentResolver();
        Uri uri = Uri
                .parse("content://com.example.contentprovidertest01.PersonContentProvider/person/student");
        ContentValues values = new ContentValues();
        values.put("name", "王五");
        values.put("address", "深圳");
        String where = "address=?";
        String[] where_args = { "beijing" };
        contentResolver.update(uri, values, where, where_args);
    }

    //测试方法:查询所有记录。如果要查询单条记录:content://com.example.contentprovidertest01.PersonContentProvider/person/1
    public void query() {
        ContentResolver contentResolver = getContext().getContentResolver();
        Uri uri = Uri
                .parse("content://com.example.contentprovidertest01.PersonContentProvider/person");
        Cursor cursor = contentResolver.query(uri, null, null, null, null);
        while (cursor.moveToNext()) {
            Log.i("MyTest",
                    "--->>"
                            + cursor.getString(cursor.getColumnIndex("name")));
        }
    }

    //测试方法:根据条件查询所有记录。
    public void querys() {
        ContentResolver contentResolver = getContext().getContentResolver();
        Uri uri = Uri
                .parse("content://com.example.contentprovidertest01.PersonContentProvider/person");
        String where = "address=?";
        String[] where_args = { "深圳" };
        Cursor cursor = contentResolver.query(uri, null, where, where_args,
                null);
        while (cursor.moveToNext()) {
            Log.i("main",
                    "-------------->"
                            + cursor.getString(cursor.getColumnIndex("name")));
        }
    }

}

既然ContetProvider实现的是跨应用访问数据,那这个测试类Test.java就应该写在另一个应用程序中才行。于是,我们新建另外一个工程文件ContentResolverTest,在里面添加单元测试,里面的代码其实和上方的Test.java的代码是一模一样的。运行单元测试,依然能在ContentResolverTest中实现对ContentProviderTest01中的CRUD.核心在于:使用应用1中的内容解析者ContentResolver访问应用2中的内容提供者ContentProvider

现在运行ContentProviderTest01中的单元测试类:

1、运行insert()方法,实现插入操作。后台打印如下:

这里写图片描述

上图中红框部分表明,这个uri就是代表内容提供者中,person表中,id为1的数据。

此时,打开file Explorer,进行查看,发现确实多了个文件:
这里写图片描述

注意:如果SQLite中之前没有mydb.db这个数据库,当实现插入操作时,会自动创建mydb.db这个数据库,并自动创建person表(因为在PersonDao类中执行了getWritableDatabase()方法)。

现在将上图中的mydb.db导出,然后用SQLiteExpert软件打开,输入sql查询语句,就可以看到person表中的数据了:

这里写图片描述

如果再执行insert()方法,又会继续添加一条记录(id是自动增长的)。

2、运行query()方法,查询所有记录(目前一共两条记录)。后台输出效果如下:
这里写图片描述

经测试,其他方法也都是可以执行的。

事实证明,新建的另外一个工程文件ContentResolverTest中,在里面运行单元测试,也是可以执行的(单元测试的代码不变,实现的CRUD功能也一模一样),也就是说,能够对ContentProviderTest01中的SQLite进行CRUD操作。例如,运行query()方法,后台输出如下:

这里写图片描述

这样,我们的目的也就达到了。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值