Android探索之ContentProvider熟悉而又陌生的组件

前言:

    总结这篇文章之前我们先来回顾一下Android Sqlite数据库,参考文章:http://www.cnblogs.com/whoislcj/p/5506294.html,Android程序内部数据存储如果使用Sqlite数据库,那么Android 如何实现程序间数据共享?Android 提供了一种机制可以实现程序间的数据共享,它就是Android 四大组件之一ContentProvider,Android为存储和获取数据提供统一的接口,用于实现程序间数据共享,不要将其理解为数据库。

     为什么说是熟悉又陌生呢?因为我们经常使用到,Android内置的许多数据都是采用ContentProvider,比如图片,视频,音频,手机联系人等,至于陌生那是因为我很少自己去实现一个ContentProvider,今天我们重点是来实现一个自定义ContentProvider。

ContentProvider类简介:

     1.) 我们一般要继承ContentProvider,那么要实现那些函数呢?
  • ContentProvider()   构造函数
  • onCreate()    创建数据时调用的回调函数
  • insert()      插入数据
  • delete()     删除数据
  • update()    更新数据
  • query()      查询数据
  • getType()  得到数据类型
   2.)URI简介:

     ContentProvider通过URI来访问数据执行增删改查的操作,一个完整的URI有 content://自定义ContentProvider/xxx数据库名称 

     我们先声明一个作用域:

    //访问URI作用域
    public static final String CONTENT_URI="com.whoislcj.testsqlite.personprovider";

    对应URI举例说明一下:

  • content://com.whoislcj.testsqlite.personprovider/person   返回person所以数据
  • content://com.whoislcj.testsqlite.personprovider/person/10 返回id为10的person数据
   3.)UriMatcher简介

       主要用于匹配Uri,为什么要匹配Uri呢?通过上面的Uri举例可以看出操作域不一样,对于执行一个delete、update、query来说我们要获取参数参数执行不能对应操作。

   使用:

  //定义一个UriMatcher类对象,用来匹配Uri的。
    private static final UriMatcher uriMatcher;
    //集合操作
    public static final int INCOMING_COLLECTION = 1;
    //单个ID操作
    public static final int INCOMING_SIGNAL = 2;
    static {
        //常量UriMatcher.NO_MATCH表示不匹配任何路径的返回码
        uriMatcher = new UriMatcher(UriMatcher.NO_MATCH);
        //如果match()方法匹配com.whoislcj.testsqlite.personprovider/person路径,返回匹配码为1
        uriMatcher.addURI(CONTENT_URI, "person", INCOMING_COLLECTION);//添加需要匹配uri,如果匹配就会返回匹配码
        //如果match()方法匹配content://com.ljq.provider.personprovider/person/230路径,返回匹配码为2
        uriMatcher.addURI(CONTENT_URI, "person/#", INCOMING_SIGNAL);//#号为通配符
    }
4.)ContentUris简介

      ContentUris是对URI的操作类,比如获取URI路径里的参数,或者给URI拼接一个参数

    举例说明:

  • long id = ContentUris.parseId(uri);//从uri中获取id
  • Uri rowUri = ContentUris.withAppendedId(uri, rowId);//uri追加id 生成该条数据完整的URI地址
  5.)ContentResolver简介   

       ContentResolver主要用于为外部程序提供增删改查的操作函数,也可以注册观察者来监听数据的变化。

 6.)自定义ContentProvider具体实现:
public class PersonProvider extends ContentProvider {
    // DatabaseHelper操作句柄
    private DBHelper dbHelper;
    //访问URI
    public static final String CONTENT_URI="com.whoislcj.testsqlite.personprovider";
    // 数据集的MIME类型字符串则应该以vnd.android.cursor.dir/开头
    public static final String CONTENT_TYPE = "vnd.android.cursor.dir/person";
    // 单一数据的MIME类型字符串应该以vnd.android.cursor.item/开头
    public static final String CONTENT_TYPE_ITME = "vnd.android.cursor.item/person";
    //定义一个UriMatcher类对象,用来匹配Uri的。
    private static final UriMatcher uriMatcher;
    //集合操作
    public static final int INCOMING_COLLECTION = 1;
    //单个ID操作
    public static final int INCOMING_SIGNAL = 2;
    static {
        //常量UriMatcher.NO_MATCH表示不匹配任何路径的返回码
        uriMatcher = new UriMatcher(UriMatcher.NO_MATCH);
        //如果match()方法匹配com.whoislcj.testsqlite.personprovider/person路径,返回匹配码为1
        uriMatcher.addURI(CONTENT_URI, "person", INCOMING_COLLECTION);//添加需要匹配uri,如果匹配就会返回匹配码
        //如果match()方法匹配content://com.ljq.provider.personprovider/person/230路径,返回匹配码为2
        uriMatcher.addURI(CONTENT_URI, "person/#", INCOMING_SIGNAL);//#号为通配符
    }

    public PersonProvider() {
    }

    /**
     * 回调函数,在ContentProvider创建的时候,就会运行
     * 作用获取操作用户的句柄
     */
    @Override
    public boolean onCreate() {
        //这里会调用 DBHelper的构造函数创建一个数据库;
        dbHelper = new DBHelper(getContext());
        return true;
    }

    /**
     * 执行插入数据函数
     *
     * @param uri
     * @param values
     * @return
     */
    @Override
    public Uri insert(Uri uri, ContentValues values) {
        //获取一个可写的数据库
        SQLiteDatabase db = dbHelper.getWritableDatabase();
        //调用数据库的插入操作 也可以自己构造sql语句 执行  db.execSQL();相对比较麻烦
        long rowId = db.insert(DBHelper.TABLE_NAME, "", values);
        //判断是否插入成功
        if (rowId > 0) {
            Uri rowUri = ContentUris.withAppendedId(uri, rowId);//uri追加id 生成该条数据完整的URI地址
            getContext().getContentResolver().notifyChange(uri, null);
            return rowUri;
        }
        throw new SQLException("Failed to insert row" + uri);
    }

    /**
     * 删除数据操作
     *
     * @param uri
     * @param selection
     * @param selectionArgs
     * @return
     */
    @Override
    public int delete(Uri uri, String selection, String[] selectionArgs) {
        //获取一个可写的数据库
        SQLiteDatabase db = dbHelper.getWritableDatabase();
        int count = 0;
        switch (uriMatcher.match(uri)) {
            case INCOMING_COLLECTION:
                //执行删除操作
                count = db.delete(DBHelper.TABLE_NAME, selection, selectionArgs);
                getContext().getContentResolver().notifyChange(uri, null);
                break;
            case INCOMING_SIGNAL:
                long id = ContentUris.parseId(uri);//从uri中获取id
                String where = "id=" + id; // 删除指定id的记录
                where += !TextUtils.isEmpty(selection) ? " and (" + selection + ")" : ""; // 把其它条件附加上
                count = db.delete(DBHelper.TABLE_NAME, where, selectionArgs);
                getContext().getContentResolver().notifyChange(uri, null);
                break;
            default:
                throw new SQLException("Failed to delete row " + uri);
        }
        //关闭数据库
        db.close();
        return count;
    }

    /**
     * 更新数据操作
     *
     * @param uri
     * @param values
     * @param selection
     * @param selectionArgs
     * @return
     */
    @Override
    public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs) {
        //获取一个可写的数据库
        SQLiteDatabase db = dbHelper.getWritableDatabase();
        int count = 0;
        switch (uriMatcher.match(uri)) {
            case INCOMING_COLLECTION:
                //执行更新数据
                count = db.update(DBHelper.TABLE_NAME, values, selection, selectionArgs);
                getContext().getContentResolver().notifyChange(uri, null);
                break;
            case INCOMING_SIGNAL:
                long id = ContentUris.parseId(uri);//从uri中获取id
                String where = "id=" + id;    // 删除指定id的记录
                where += !TextUtils.isEmpty(selection) ? " and (" + selection + ")" : "";// 把其它条件附加上
                //执行更新数据
                count = db.update(DBHelper.TABLE_NAME, values, where, selectionArgs);
                getContext().getContentResolver().notifyChange(uri, null);
                break;
            default:
                throw new SQLException("Failed to update row " + uri);
        }
        //关闭数据库
        db.close();
        return count;
    }

    /**
     * 查询操作
     *
     * @param uri
     * @param projection
     * @param selection
     * @param selectionArgs
     * @param sortOrder
     * @return
     */
    @Override
    public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder) {
        //获取一个可读的数据库
        SQLiteDatabase db = dbHelper.getReadableDatabase();
        Cursor cursor = null;
        switch (uriMatcher.match(uri)) {
            case INCOMING_COLLECTION:
                //执行查询
                cursor = db.query(DBHelper.TABLE_NAME, projection, selection, selectionArgs, null, null, sortOrder);
                break;
            case INCOMING_SIGNAL:
                long id = ContentUris.parseId(uri);//从uri中获取id
                String where = "id=" + id;    // 删除指定id的记录
                where += !TextUtils.isEmpty(selection) ? " and (" + selection + ")" : "";// 把其它条件附加上
                cursor = db.query(DBHelper.TABLE_NAME, projection, where, selectionArgs, null, null, sortOrder);
                break;
            default:
                throw new SQLException("Failed to query " + uri);
        }
        return cursor;
    }

    /**
     * 该方法用于返回当前Url所代表数据的MIME类型。
     *
     * @param uri
     * @return
     */
    @Override
    public String getType(Uri uri) {
        switch (uriMatcher.match(uri)) {
            case INCOMING_COLLECTION:
                return CONTENT_TYPE;
            case INCOMING_SIGNAL:
                return CONTENT_TYPE_ITME;
            default:
                throw new IllegalArgumentException("Unknown URI " + uri);
        }
    }
}
View Code
7.)外部如何访问
        ContentResolver resolver = getContentResolver();
        Uri uri = Uri.parse("content://com.whoislcj.testsqlite.personprovider/person");
        //添加一条记录
        ContentValues values = new ContentValues();
        values.put("name", "whoislcj");
        resolver.insert(uri, values);

        //更新一条数据
        ContentValues updateValues = new ContentValues();
        updateValues.put("name", "lcj");
        //组合
        resolver.update(uri, updateValues, "id=?", new String[]{"2"});
        //单个
        Uri updateIdUri = ContentUris.withAppendedId(uri, 5);
        resolver.update(updateIdUri, updateValues, null, null);
        //删除person表指定数据
        Uri deleteIdUri = ContentUris.withAppendedId(uri, 5);
        resolver.delete(deleteIdUri, null, null);

        //获取person表指定数据
        Uri tempUri = ContentUris.withAppendedId(uri, 5);
        Cursor cursor = resolver.query(tempUri, null, null, null, "id asc");
        while (cursor.moveToNext()) {
            Log.e("testContentProvider", "signal id=" + cursor.getInt(0) + ",name=" + cursor.getString(1));
        }
        cursor.close();

        //获取person表中所有记录
        cursor = resolver.query(uri, null, null, null, "id asc");
        while (cursor.moveToNext()) {
            Log.e("testContentProvider", "id=" + cursor.getInt(0) + ",name=" + cursor.getString(1));
        }
        cursor.close();
View Code
8.)如何监听数据变化

需要注册一个自定义的观察者,当时如下

     // 为uri的数据改变注册监听器
        getContentResolver().registerContentObserver(
                Uri.parse("content://com.whoislcj.testsqlite.personprovider/person"), true,
                new Observer(new Handler()));

    // 提供方自定义的ContentOberver监听器
    private final class Observer extends ContentObserver {

        public Observer(Handler handler) {
            super(handler);
        }

        @Override
        public void onChange(boolean selfChange, Uri uri) {
            // 查询发送邮箱中的短息(处于正在发送状态的短信放在发送箱)
            Log.e("MainActivity", "onChange--->uri :" + uri.toString());
        }
    }

同样数据操作位置也需要执行如下代码

getContext().getContentResolver().notifyChange(uri, null);
9.)访问权限控制

声明读写自定义权限

    <permission android:name="com.whoislcj.testsqlite.personprovider.read" />
    <permission android:name="com.whoislcj.testsqlite.personprovider.write" />

    <uses-permission android:name="com.whoislcj.testsqlite.personprovider.read" />
    <uses-permission android:name="com.whoislcj.testsqlite.personprovider.write" />

ContentProvider注册声明:

       <provider
            android:name=".PersonProvider"
            android:authorities="com.whoislcj.testsqlite.personprovider"
            android:enabled="true"
            android:exported="true"
            android:readPermission="com.whoislcj.testsqlite.personprovider.read"
            android:writePermission="com.whoislcj.testsqlite.personprovider.write">
        </provider>

 10.)关于getTpye

        ContentProvider里面一个getType ()函数很多人不知道 这个干嘛的,接下来介绍一下,

    // 数据集的MIME类型字符串则应该以vnd.android.cursor.dir/开头
    public static final String CONTENT_TYPE = "vnd.android.cursor.dir/person";
    // 单一数据的MIME类型字符串应该以vnd.android.cursor.item/开头
    public static final String CONTENT_TYPE_ITME = "vnd.android.cursor.item/person";

    /**
     * 该方法用于返回当前Url所代表数据的MIME类型。
     *
     * @param uri
     * @return
     */
    @Override
    public String getType(Uri uri) {
        switch (uriMatcher.match(uri)) {
            case INCOMING_COLLECTION:
                return CONTENT_TYPE;
            case INCOMING_SIGNAL:
                return CONTENT_TYPE_ITME;
            default:
                throw new IllegalArgumentException("Unknown URI " + uri);
        }
    }

假设我们在项目搞了一个联系人列表Activity,我们需要外面来访问这个Activity,首先看下这个Activity的注册声明:

<activity android:name=".TestActivity" android:icon="@mipmap/ic_launcher">
            <intent-filter>
                <action android:name="com.whoislcj.testsqlite.personprovider" />
                <category android:name="android.intent.category.DEFAULT"/>
                <data android:mimeType="vnd.android.cursor.dir/person" />
            </intent-filter>

        </activity>

看到上面的mimeType:vnd.android.cursor.dir/person

外部如何启动呢:

    Uri uri = Uri.parse("content://com.whoislcj.testsqlite.personprovider/person");
                Intent intent = new Intent();
                intent.setAction("com.whoislcj.testsqlite.personprovider");
                intent.setData(uri);
                startActivity(intent);

这样以来系统会去调用你定义的ContentProvider中的getType,去匹配出相应的Activity来实现跳转。

posted on 2016-05-19 10:56  总李写代码  阅读(1523)  评论(0编辑  收藏  举报