望远山,知近路,而后自得其乐!

ContentProvider使用简介

ContentProvider中文名“内容提供者”,是Android系统不同应用程序之间进行数据交换的标准API,ContentProvide以Uri的形式对外提供数据,允许其他应用访问和修改数据;其他应用使用ContentResolve根据Uri进行访问操作指定的数据。Android内置的许多数据也都是使用ContentProvider形式,供开发者调用的;如视频,音频,图片,通讯录等。

统一资源标识符(URI)

访问ContentProvider都是统一通过Uri进行访问的,URI(Uniform Resource Identifier,即统一资源标识符)由三部分组成,即协议(scheme),所有者(authority),路径(path)。

在ContentProvider中,scheme统一为content:// ;authority用来作为当前Provider的唯一标识,一般以公司域名+数据标识; Path为具体的数据路径,一般为 “数据类型 / 数据ID” 的形式 。如上面例子中的100即为id。

UriMatcher类

既然ContentProvider需要通过RUI进行访问,所以android系统提供了UriMatcher来进行Uri的匹配处理。提供了以下两个接口,一个用来添加匹配规则,一个用来获取匹配结果。

public void addURI(String authority, String path, int code)
public int match(Uri uri)  

定义匹配规则

public static final int ID1 = 1;
public static final int ID2 = 2;
public static final String HOST = "com.test.provider.DataContentProvider";
public static final String PATH = "students";
private static UriMatcher uriMatcher = new UriMatcher(UriMatcher.NO_MATCH);
static {   
    uriMatcher.addURI(HOST,PATH, ID1);   
    uriMatcher.addURI(HOST,PATH + "/#", ID2); // # 为通配符 
}

匹配路径

switch (uriMatcher.match(Uri uri)) { 
   case ID1:
     break;
   case ID2:
     break;
   default:
     break;
}

如匹配的uri为:content://com.test.provider.DataContentProvider/students/10 , 则得到的匹配码为ID2。

ContentUris类

这个类是一个操作Uri字符串的工具类,主要是拼接Uri字符串用,它有两个方法:

public static  Uri withAppendedId(Uri uri, long  id)   // 用于为路径加上id部分
public static long parseId(Uri uri)   // 用于从指定的Uri中解析出所包含的id
Uri  uri = Uri.parse("content://com.test.provider.DataContentProvider/students");
Uri newUri = ContentUris.withAppendedId(uri, 100);
Uri uri = Uri.parse("content://com.test.provider.DataContentProvider/students/100")
long personid = ContentUris.parseId(uri); // 获取的结果为:100

ContentProvider类介绍

ContentProvider类是一个抽象类,具体的实现类需要实现其主要的几个虚函数,也正是通过这些虚函数的实现来完成具体的ContentProvider的具体功能的。主要的函数如下:

函数说明
boolean onCreate()ContentProvider创建后就会被调用,ContentProvider在其它应用第一次访问它时才会被创建
Cursor query(Uri uri, String[] projection, String selection,String[] selectionArgs, String sortOrder)用于供外部应用从ContentProvider中获取数据
String getType(Uri uri)用于返回当前Url所代表数据的MIME类型
Uri insert(Uri uri, ContentValues values)用于供外部应用往ContentProvider添加数据
int delete(Uri uri, String selection, String[] selectionArgs)用于供外部应用从ContentProvider删除数据
int update(Uri uri, ContentValues values, String selection, String[] selectionArgs)用于供外部应用更新ContentProvider中的数据

下面通过一个实列来展示ContentProvider的实现。

public class StudentProvider extends ContentProvider {

    public static final int TYPE_ITEM = 1;
    public static final int TYPE_TABLE = 2;
    public static final String HOST = "cc.ccbu.provider.StudentProvider";
    public static final String PATH = "students";
    private static UriMatcher uriMatcher = new UriMatcher(UriMatcher.NO_MATCH);
    public static final String CONTENT_TYPE_ITEM = "vnd.android.cursor.dir/students.item";
    public static final String CONTENT_TYPE_TABLE = "vnd.android.cursor.item/students.table";
    static {
        uriMatcher.addURI(HOST, PATH, TYPE_TABLE);
        uriMatcher.addURI(HOST,PATH + "/#", TYPE_ITEM);
    }

    private DbOpenHelper dbOpenHelper;

    @Override
    public boolean onCreate() {
        dbOpenHelper = new DbOpenHelper(getContext());
        return true;
    }

    @Nullable
    @Override
    public String getType(@NonNull Uri uri) {
        switch (uriMatcher.match(uri)){
            case TYPE_ITEM:
                return CONTENT_TYPE_ITEM;
            case TYPE_TABLE:
                return CONTENT_TYPE_TABLE;
            default:
                throw new IllegalArgumentException("this is unknown uri:" + uri);

        }
    }

    @Override
    public Uri insert(@NonNull Uri uri, @Nullable ContentValues values) {
        SQLiteDatabase db = dbOpenHelper.getWritableDatabase();
        switch (uriMatcher.match(uri)) {
            case TYPE_TABLE:
                long id = db.insert(DbOpenHelper.STUDENT_TABLE_NAME, null, values);
                Uri insertUri = ContentUris.withAppendedId(uri, id);
                return insertUri;
        }
        return null;
    }

    @Nullable
    @Override
    public Cursor query(@NonNull Uri uri, @Nullable String[] projection, @Nullable String selection, @Nullable String[] selectionArgs, @Nullable String sortOrder) {
        SQLiteDatabase db = dbOpenHelper.getWritableDatabase();
        switch (uriMatcher.match(uri)) {
            case TYPE_TABLE:
                return db.query(DbOpenHelper.STUDENT_TABLE_NAME, projection, selection, selectionArgs, null, null, sortOrder);
            case TYPE_ITEM:
                long id = ContentUris.parseId(uri);
                String where = "_id = " + id;
                if (null != selection && !"".equals(selection.trim()))
                {
                    where += " and " + selection;
                }
                return dbOpenHelper.getWritableDatabase().query(DbOpenHelper.STUDENT_TABLE_NAME, projection, where, selectionArgs, null, null, sortOrder);
        }
        return null;
    }

    @Override
    public int delete(@NonNull Uri uri, @Nullable String selection, @Nullable String[] selectionArgs) {
        SQLiteDatabase db = dbOpenHelper.getWritableDatabase();
        switch (uriMatcher.match(uri)) {
            case TYPE_TABLE:
                return db.delete(DbOpenHelper.STUDENT_TABLE_NAME, selection, selectionArgs);
            case TYPE_ITEM:
                long id = ContentUris.parseId(uri);
                String where = "_id = " + id;
                if (null != selection && !"".equals(selection.trim()))
                {
                    where += " and " + selection;
                }
                return db.delete(DbOpenHelper.STUDENT_TABLE_NAME, where, selectionArgs);
        }

        return 0;
    }

    @Override
    public int update(@NonNull Uri uri, @Nullable ContentValues values, @Nullable String selection, @Nullable String[] selectionArgs) {
        SQLiteDatabase db = dbOpenHelper.getWritableDatabase();
        switch (uriMatcher.match(uri)) {
            case TYPE_TABLE:
                return db.update(DbOpenHelper.STUDENT_TABLE_NAME, values, selection, selectionArgs);
            case TYPE_ITEM:
                long id = ContentUris.parseId(uri);
                String where = "_id = " + id;
                if (null != selection && !"".equals(selection.trim()))
                {
                    where += " and " + selection;
                }
                return db.update(DbOpenHelper.STUDENT_TABLE_NAME, values, where, selectionArgs);
        }
        return 0;
    }
}

Provider类写好后还需要在AndroidManifest.xml中进行注册。为了能使其他应用可以访问ContentProvider中的数据,需要在xml中将android:exported属性设置为true;若设置成false,则只能被自己所在的应用使用。

<provider
    android:authorities="cc.ccbu.provider.StudentProvider"
    android:name=".StudentProvider"
    android:enabled="true"
    android:exported="true"/>

下面是一个简单的测试例子。展示了常见的insert, update, qurey, delete操作。

public class MainActivity extends Activity {

    public static final String AUTHORITY = "cc.ccbu.provider.StudentProvider";
    public static final String STUDENT = "students";
    private static final String TAG = "MainActivity";
    private Uri uri;

    private long insertId = -1;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        uri = Uri.parse("content://" + AUTHORITY + "/" + STUDENT);

        onInsert();
        onQuery();
        onUpdate();
        onQuery();
        onDelete();
        onQuery();
    }

    public void onInsert() {
        ContentValues cvs = new ContentValues();
        cvs.put("name", "tom");
        Uri uriRet = getContentResolver().insert(uri, cvs);
        if (uriRet != null) {
            insertId = ContentUris.parseId(uriRet);
            Log.d(TAG, "onInsert : " + insertId);
        }
    }

    public void onUpdate() {
        if (insertId >= 0) {
            Uri uriUpdate = ContentUris.withAppendedId(uri, insertId);
            ContentValues cvs = new ContentValues();
            cvs.put("name", "jack");
            int updateId = getContentResolver().update(uriUpdate, cvs, null, null);
            Log.d(TAG, "onUpdate : " + updateId);
        }
    }

    public void onQuery(){
        Cursor cursor = getContentResolver().query(uri, null, null, null, null);
        if (cursor != null) {
            while (cursor.moveToNext()) {
                Log.d(TAG, "onQuery : " + cursor.getInt(0) + ", " + cursor.getString(1));
            }
        }
    }

    public void onDelete() {
        Uri uriDelete = ContentUris.withAppendedId(uri, insertId);
        getContentResolver().delete(uriDelete, null, null);
        Log.d(TAG, "onDelete");
    }
}

运行结果如下:

D/MainActivity: onInsert : 1
D/MainActivity: onQuery : 1, tom
D/MainActivity: onUpdate : 1
D/MainActivity: onQuery : 1, jack
D/MainActivity: onDelete

ContentProvider的监听

ContentProvider以Uri的形式为其他应用提供了简单的访问方式,通过次方式,外部应用可以很轻松的访问ContentProvider中的数据,但这些访问动作都是应用主动发起的,当ContentProvider中数据变化了之后,有没有办法被通知数据变化了呢,答案当时是有的。ContentObserver是系统为我们提供的ContentProvider数据监听的基类,需要监听ContentProvider的数据变化必须继承此类,从类名可以看出,此次的监听是以观察者模式来实现的。

private void testObserver() {
        getContentResolver().registerContentObserver(uri, true, new ContentObserver(new Handler(Looper.getMainLooper())) {
            @Override
            public void onChange(boolean selfChange, Uri uri) {
                Log.d(TAG, "onChange : " + uri.getPath());
            }
        });
    }

ContentObserver在构造函数中需要提供一个Handler实例,当我们通过ContentResolverregisterContentObserver注册某一Uri的监听后,当此Uri的数据发生变化时,我们就可以在onChange回调函数中收到通知。当然,需要完全实现此功能,在ContentProvider中还需要做些相应的处理,当ContentProvider中的数据发生变化时,需要通知数据变化了。

@Override
public Uri insert(@NonNull Uri uri, @Nullable ContentValues values) {
    SQLiteDatabase db = dbOpenHelper.getWritableDatabase();
    switch (uriMatcher.match(uri)) {
        case TYPE_TABLE:
            long id = db.insert(DbOpenHelper.STUDENT_TABLE_NAME, null, values);
            Uri insertUri = ContentUris.withAppendedId(uri, id);
            getContext().getContentResolver().notifyChange(insertUri, null);
            return insertUri;
        default:
            throw new IllegalArgumentException("this is unknown uri:" + uri);
    }
}

上面的例子中,当插入新的数据时,将新数据的Uri通知出去;监听者也就可以收到想要的通知。

ContentProvider的使用权限

ContentProvider可以被其他应用公开的访问,同时也可以设置为只有本应用自己才可以访问,或者在对其他应用公开访问时设置访问权限。ContentProvider需要在provider标签中设置android:permission属性来设置访问权限。权限通常为一个字符串,为了与其他应用区分,防止重复,一般以应用的PackageName开头。

<provider
    android:authorities="cc.ccbu.provider.StudentProvider"
    android:name=".StudentProvider"
    android:enabled="true"
    android:exported="true"
    android:permission="cc.ccbu.provider.StudentProvider"/>

同时需要在AndroidManifest.xml的根节点下用permission标签来申明权限。

<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="cc.ccbu.provider">

    <permission android:name="cc.ccbu.provider.StudentProvider"
        android:label="StudentProvider"
        android:protectionLevel="normal"/>
        
        ......
</manifest>

此时,其他应用需要有权限访问此ContentProvider的数据,则需要在AndroidManifest.xml申请权限。

 <uses-permission android:name="cc.ccbu.provider.StudentProvider"/>

访问权限还可以进一步的细化,分为读权限和写权限。通过来设置android:readPermission属性和android:writePermission属性项来设置。

<provider
    android:authorities="cc.ccbu.provider.StudentProvider"
    android:name=".StudentProvider"
    android:enabled="true"
    android:exported="true"
    android:readPermission="cc.ccbu.provider.StudentProvider.READ"
    android:writePermission="cc.ccbu.provider.StudentProvider.WRITE"/>

同样的需要申明一下这两种权限

<permission android:name="cc.ccbu.provider.StudentProvider.READ"
        android:label="StudentProvider.read"
        android:protectionLevel="normal"/>

<permission android:name="cc.ccbu.provider.StudentProvider.WRITE"
        android:label="StudentProvider.write"
        android:protectionLevel="normal"/>

访问的应用则也需要在AndroidManifest中进行对应的权限申请才可以进行访问。

另外,android:writePermissionandroid:readPermission权限的优先级比android:permission的优先级高,所以设置了read和write权限后,访问方就必须申请相应的read和write权限,否则是无法访问的。

文章评论已关闭!