【我的Android进阶之旅】自定义ContentProvider

引言

我们知道Android有四大组件,ContentProvider是其中之一,顾名思义:内容提供者。什么是内容提供者呢?一个抽象类,可以暴露应用的数据给其他应用。应用里的数据通常说的是数据库,事实上普通的文件,甚至是内存中的对象,也可以作为内容提供者暴露的数据形式。为什么要使用内容提供者呢?从上面定义就知道,内容提供者可以实现应用间的数据访问,一般是暴露表格形式的数据库中的数据。内容提供者的实现机制是什么呢?由于是实现应用间的数据通信,自然也是两个进程间的通信,其内部实现机制是Binder机制。那么,内容提供者也是实现进程间通信的一种方式。

事实上在开发中,很少需要自己写一个ContentProvider,一般都是去访问其他应用的ContentProvider。本篇文章之所以去研究如何自己写一个ContentProvider,也是为了更好的在开发中理解:如何访问其他应用的内容提供者

如何自定义一个ContentProvider

接下来介绍如何自己去实现一个内容提供者,大致分三步进行:
  1. 继承抽象类ContentProvider,重写onCreate,CUDR,getType六个方法:onCreate()方法中,获取SQLiteDatabase对象;CUDR方法通过对uri进行判断,做相应的增删改查数据的操作;getType方法是返回uri对应的MIME类型。
  2. 注册可以访问内容提供者的uri:创建静态代码块,static{…code},在类加载的时候注册可以访问内容提供者的uri,使用类UriMatcher的addURI(…)完成。
  3. 清单文件中配置provider:注册内容提供者,加入authorities属性,对外暴露该应用的内容提供者。

以下是代码部分

package com.wzy.myprovider;

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.database.SQLException;
import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteOpenHelper;
import android.net.Uri;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;

public class BookProvider extends ContentProvider {
    static final String DATABASE_NAME = "provider.db";
    static final String DATABASE_TABLE = "book";
    static final int DATABASE_VERSION = 1;
    static final String DATABASE_CREATE = "create table " +
            DATABASE_TABLE + " (_id integer primary key autoincrement, " + "name text not null, author text not null);";
    static final String PROVIDER_NAME = "com.wzy.provider";
    static final int BOOKS = 1;
    static final int BOOK_ID = 2;
    static UriMatcher uriMatcher;
    SQLiteDatabase db;

    static {
        uriMatcher = new UriMatcher(UriMatcher.NO_MATCH);
        uriMatcher.addURI(PROVIDER_NAME, "books", BOOKS);
        uriMatcher.addURI(PROVIDER_NAME, "books/#", BOOK_ID);
    }

    private static class DatabaseHelper extends SQLiteOpenHelper {
        DatabaseHelper(Context context) {
            super(context, DATABASE_NAME, null, DATABASE_VERSION);
        }

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

        @Override
        public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
            db.execSQL("DROP TABLE IF EXISTS BookInfo");
            onCreate(db);
        }
    }


    @Override
    public boolean onCreate() {
        DatabaseHelper helper = new DatabaseHelper(getContext());
        db = helper.getWritableDatabase();
        return false;
    }


    @Nullable
    @Override
    public String getType(@NonNull Uri uri) {
        switch (uriMatcher.match(uri)) {
            case BOOKS:
                return "vnd.android.cursor.dir/vnd.com.wzy.provider.books";
            case BOOK_ID:
                return "vnd.android.cursor.item/vnd.com.wzy.provider.books";
            default:
                throw new IllegalArgumentException("Unsupported URI: " + uri);
        }
    }

    @Nullable
    @Override
    public Cursor query(@NonNull Uri uri, @Nullable String[] projection, @Nullable String selection, @Nullable String[] selectionArgs, @Nullable String sortOrder) {
        Cursor cursor = null;
        switch (uriMatcher.match(uri)) {
            case BOOK_ID:
                cursor = db.query(DATABASE_TABLE, projection, "_id = ", new String[]{uri.getPathSegments().get(1)}, null, null, sortOrder);
                break;
            case BOOKS:
                cursor = db.query(DATABASE_TABLE, projection, selection, selectionArgs, null, null, sortOrder);
                break;
            default:
                throw new IllegalArgumentException("Unknown URI " + uri);
        }
        return cursor;
    }

    @Nullable
    @Override
    public Uri insert(@NonNull Uri uri, @Nullable ContentValues values) {
        long rowId = db.insert(DATABASE_TABLE, null, values);
        if (rowId > 0) {
            Uri bookUri = ContentUris.withAppendedId(uri, rowId);
            return bookUri;
        }
        throw new SQLException("Failed to insert row into " + uri);
    }

    @Override
    public int delete(@NonNull Uri uri, @Nullable String selection, @Nullable String[] selectionArgs) {
        int rowIDs;
        switch (uriMatcher.match(uri)) {
            case BOOK_ID:
                String bookID = uri.getPathSegments().get(1);
                rowIDs = db.delete(DATABASE_TABLE, "_id = ", new String[]{bookID});
                break;
            case BOOKS:
                rowIDs = db.delete(DATABASE_TABLE, selection, selectionArgs);
                break;
            default:
                throw new IllegalArgumentException("Unknown URI " + uri);
        }
        return rowIDs;
    }

    @Override
    public int update(@NonNull Uri uri, @Nullable ContentValues values, @Nullable String selection, @Nullable String[] selectionArgs) {
        int rowIDs;
        switch (uriMatcher.match(uri)) {
            case BOOK_ID:
                String bookID = uri.getPathSegments().get(1);
                rowIDs = db.update(DATABASE_TABLE, values, "_id = ?", new String[]{bookID});
                break;
            case BOOKS:
                rowIDs = db.update(DATABASE_TABLE, values, selection, selectionArgs);
                break;
            default:
                throw new IllegalArgumentException("Unknown URI " + uri);
        }
        return rowIDs;
    }

}

初次观察代码,我们可以发现,内容提供者就是基于sql实现的一层封装。我们再观察静态代码块,在这里,值得一提的是静态代码块总是运行在类的构造方法执行之前。

static {
    uriMatcher = new UriMatcher(UriMatcher.NO_MATCH);
    uriMatcher.addURI(PROVIDER_NAME, "books", BOOKS);
    uriMatcher.addURI(PROVIDER_NAME, "books/#", BOOK_ID);
}

我们进入UriMatcher的构造方法去查看下

public UriMatcher(int code)
{
    mCode = code;
    mWhich = -1;
    mChildren = new ArrayList<UriMatcher>();
    mText = null;
}

在这个里面维护了一个ArrayList,可以预知的是,这个类里面的addURI和match方法就是基于对这个列表添加和遍历实现的。在这里就不多做赘述了,有兴趣的童鞋可以自行研究。

我们继续往下观察,我们创建了一个抽象帮助类SQLiteOpenHelper的子类并在onCreate中去实例化了一个对象,然后通过getWritableDatabase()获取SQLiteDatabase实例。其实这里应该的做法是单独创建一个类继承自SQLiteOpenHelper,并使用单例模式来获取其实例。

我们来了解下uri。uri是如何构成的呢? uri 有三个构成元素scheme + authorities + path。先看这样一个uri,uri = “content://com.wzy.provider/books”,在这里,三个构成元素分别是:

scheme:“content://”; 一般是固定的
authorities :“com.wzy.provider”;authorities就是在清单文件中配置的authorities属性的值,唯一标识该应用的内容提供者。
path:"/books";path里面常常放的是一些表名,字段信息,确定访问该数据库中哪个表的哪些数据.

继续往下,我们接下里分析CUDR操作,我们重写了这样四个方法:query,insert,delete,update,这个四个方法的参数都是想访问该应用的其他用户传递过来的.我们调用uriMatcher.match(uri)来匹配uri进行对应的CRUD操作.如果返回的code没有在uriMatcher中注册过.就跑出IllegalArgumentException("Unsupported URI: " + uri)异常,代表uri不合法。如果合法就调用SQLiteDatabase的query,insert,delete,update四个方法进行增删改查数据。

值得一提的是,在进行insert操作的时候返回的是一个uri,我们往数据库插入一条记录并成功返回该记录的ID的时候,会将该ID以路径的方式拼接到原始uri上面。举个栗子:比如说我们通过"content://com.wzy.provider/books"往数据库表book成功插入一条数据,并返回id为99,那么我们得到的uri就是"content://com.wzy.provider/books/99".

这样,一个完整的ContentProvider就定义完成了。不要忘记在清单文件中进行申明:

<provider
    android:name=".BookProvider"
    android:authorities="com.wzy.provider"
    android:exported="true">

</provider>

要注意,设置exported="true"是为了其他应用能访问到我们的BookProvider。

关于调用

现在内容提供者也定义好了,我们该派他上场了。之前说过,内容提供者是基于Binder实现的,可以进行跨进程访问。现在我们重新创建一个测试应用,写个简单的测试代码并打印log可以看下效果
private void test() {
    Uri uri = Uri.parse("content://com.wzy.provider/books");
    ContentValues values = new ContentValues();
    values.put("name","西游记");
    values.put("author","吴承恩");
    Uri bookUri = getContentResolver().insert(uri,values);
    values.put("name","红楼梦");
    values.put("author","曹雪芹");
    getContentResolver().update(bookUri,values,null,null);
}

写 在最后

附上代码下载地址

继承ContentProvider还有一个重要的getType()方法需要重写,这个请看

【我的Android进阶之旅】关于ContentProvider的getType方法

作者:王宗耀 欢迎转载,与人分享是进步的源泉!
转载请保留原文地址:http://blog.csdn.net/wzy901213

在这里插入图片描述

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值