Android之路

导航

自定义ContentProvider的使用

    作为Android四大组件之一的ContentProvider,主要用于应用程序间数据共享。平常的开发中更多的是使用getContentResolver操作系统的多媒体数据库(MediaProvider)。本文主要讲述如何自定义ContentProvider及注意事项。

一、自定义SimpleContentProvider.java,继承ContentProvider。

代码如下:

public class SimpleContentProvider extends ContentProvider {
    private static final String TAG = "SimpleContentProvider";
    
    // 若不匹配采用UriMatcher.NO_MATCH(-1)返回
    private static final UriMatcher MATCHER = new UriMatcher(UriMatcher.NO_MATCH);
    private static final int CODE_NOPARAM = 1;    //无参                 // 匹配码
    private static final int CODE_PARAM = 2;    //带参数
    
    static
    {        
        // 对等待匹配的URI进行匹配操作,必须符合com.test.providers.SimpleContentProvider/artical格式
        // 匹配返回CODE_NOPARAM,不匹配返回-1
        MATCHER.addURI("com.test.providers.SimpleContentProvider", "artical", CODE_NOPARAM);

        // #表示数字 com.test.providers.SimpleContentProvider/artical/10
        // 匹配返回CODE_PARAM,不匹配返回-1
        MATCHER.addURI("com.test.providers.SimpleContentProvider", "artical/#", CODE_PARAM);
        
    }
    
    private DBOpenHelper dbOpenHelper;    //操作数据库
    private final String ARTICAL_TABLE = "artical";
    
    @Override
    public boolean onCreate() {
        // TODO Auto-generated method stub
        dbOpenHelper = new DBOpenHelper(getContext());
        return true;
    }
    
    @Override
    public Uri insert(Uri uri, ContentValues cv) {
        // TODO Auto-generated method stub
        Log.i(TAG, "insert()");
        
        SQLiteDatabase db = dbOpenHelper.getWritableDatabase();
        switch (MATCHER.match(uri)) {
        case CODE_NOPARAM:
            long id = db.insert(ARTICAL_TABLE, null, cv);    //若主键是自增的,则返回主键值;否则为行号
            Uri insertUri = ContentUris.withAppendedId(uri, id);
            return insertUri;
        default:
            throw new IllegalArgumentException("This is unknow uri: " + uri);
        }
    }
    
    @Override
    public int delete(Uri uri, String selection, String[] selectionArgs) {
        // TODO Auto-generated method stub
        Log.i(TAG, "delete()");
        
        SQLiteDatabase db = dbOpenHelper.getWritableDatabase();
        switch (MATCHER.match(uri)) {
        case CODE_NOPARAM:
            return db.delete(ARTICAL_TABLE, selection, selectionArgs);    //删除所有记录
        case CODE_PARAM:
            long id = ContentUris.parseId(uri);        //取得Uri后面的数字
            String where = "_id = " + id;
            if(null != selection && !(selection.trim()).equals("")){
                where += "and " + selection;
            }
            return db.delete(ARTICAL_TABLE, where, selectionArgs);
        default:
            throw new IllegalArgumentException("This is unknow uri: " + uri);
        }
        
    }

    @Override
    public int update(Uri uri, ContentValues cv, String selection, String[] selectionArgs) {
        // TODO Auto-generated method stub
        Log.i(TAG, "update()");
        
        SQLiteDatabase db = dbOpenHelper.getWritableDatabase();
        switch (MATCHER.match(uri)) {
        case CODE_NOPARAM:
            return db.update(ARTICAL_TABLE, cv, selection, selectionArgs);    //更新所有记录
        case CODE_PARAM:
            long id = ContentUris.parseId(uri);        //取得Uri后面的数字
            String where = "_id = " + id;
            if(null != selection && !(selection.trim()).equals("")){
                where += " and " + selection;
            }
            return db.update(ARTICAL_TABLE, cv, where, selectionArgs);
        default:
            throw new IllegalArgumentException("This is unknow uri: " + uri);
        }
    }
    
     /**
     * 返回对应的内容类型
     * 如果返回集合的内容类型,必须以com.test.android.cursor.dir开头
     * 如果是单个元素,必须以com.test.android.cursor.item开头
     */
    @Override
    public String getType(Uri uri) {
        // TODO Auto-generated method stub
        Log.i(TAG, "getType()");
        
        switch (MATCHER.match(uri)) {
        case CODE_NOPARAM:
            return "com.test.android.cursor.dir/artical";
        case CODE_PARAM:
            return "com.test.android.cursor.item/artical";
        default:
            throw new IllegalArgumentException("This is unknow uri: " + uri);
        }
    }

    @Override
    public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs,
            String orderBy) {
        // TODO Auto-generated method stub
        Log.i(TAG, "query()");
        
        SQLiteDatabase db = dbOpenHelper.getReadableDatabase();        
        switch (MATCHER.match(uri)) {
        case CODE_NOPARAM:
            return db.query(ARTICAL_TABLE, projection, selection, selectionArgs, null, null, orderBy);
        case CODE_PARAM:
            long id = ContentUris.parseId(uri);        //取得Uri后面的数字
            String where = "_id = " + id;
            if(null != selection && !(selection.trim()).equals("")){
                where += "and " + selection;
            }
            return db.query(ARTICAL_TABLE, projection, where, selectionArgs, null, null, orderBy);
        default:
            throw new IllegalArgumentException("This is unknow uri: " + uri);
        }
        
    }

}

该类提供了基本的增删改查方法,可以看出,实际操作的是数据库。其中DBOpenHelper类是一个继承自SQLiteOpenHelper的自定义类,里面包含基本的数据库创建、表的创建等操作,比较简单,此处就不贴代码了。

具体步骤:

1、已经创建了自定义类,接下来需要将其注册到AndroidManifest.xml中:

    <!-- 自定义的一些权限,需要发布才能被其他应用使用 -->
    <permission android:name="android.permission.WRITE_SCP" />
    <permission android:name="android.permission.READ_SCP"/>

     <provider android:name="com.test.contentprovider.SimpleContentProvider" android:authorities="com.test.providers.SimpleContentProvider" android:exported="true" android:readPermission="android.permission.READ_SCP" android:writePermission="android.permission.WRITE_SCP" > <path-permission android:pathPrefix="/search_query" android:readPermission="android.permission.GLOBAL_SEARCH" /> </provider>

说明:

android:exported="true",允许在其他应用中访问该Provider;若设置为false时,只有同一个应用程序的组件或带有相同用户ID的应用程序才能启动或绑定该服务。
android:authorities  授权信息,用于区分不同的ContentProvider
android:permission    声明访问provider的权限

将权限细化,可分为下述两个:
android:readPermission        表示读权限,
android:writePermission        写权限

【疑问】细化后的权限比permission优先级高。
但是,测试结果显示:仅添加readPermission或writePermission权限,在另一应用中读写provider都可以,那细化的作用在哪里?在网上也没有找到相关的介绍,谁若知道,可留言告知,谢谢!

2、既然声明了权限,那么其他应用访问该provider时,也需要AndroidManifest.xml申请相应权限,否则无法访问。

    <uses-permission android:name="android.permission.READ_SCP"/>
    <uses-permission android:name="android.permission.WRITE_SCP"/>

3、程序中访问:

            Uri uri = Uri.parse("content://com.test.providers.SimpleContentProvider/artical");
            ContentResolver resolver = getContentResolver();
            ContentValues values = new ContentValues();
            values.put("title", "testcase1 ");
            values.put("content", "ContentProvider-put");
            resolver.insert(uri, values); 

以上即是一个简单的访问自定义ContentProvider的例子。

4、总结:

    通常情况下,ContentProvider存储数据操作的还是数据库,只是进行了封装。无论是自定义类还是系统原生的类,都是采用ContentProvider + SQLiteOpenHelper实现的。(自定义类继承ContentProvider,自定义类继承SQLiteOpenHelper实现对应数据库操作。)例:系统的MediaProvider、SettingsProvider或者上述自定义SimpleContentProvider均是如此。

    但是,这并不表示ContentProvider只能操作数据库,也可以是文件等,不过,我没测试过,感兴趣的可自行验证。

二、扩充:

    总结Cursor遍历ContentProvider时,常用的几种方式:其实就是for()与while的使用。

 1 ContentResolver resolver = getContentResolver();
 2 Cursor cursor = resolver.query(uri, null, null, null, null);
 3 if(cursor != null && cursor.getCount() > 0){
 4 .....//遍历代码位置
 5 }
 6 
 7 方法一:
 8 for(cursor.moveToFirst(); !cursor.isAfterLast(); cursor.moveToNext()){
 9 ......
10 }
11 
12 方法二:
13 while(cursor.moveToNext()){
14     ....//具体操作
15 }
16 
17 方法三:
18 
19 cursor.moveToFirst();        //移动到第一行;默认cursor下标在-1
20 while(!cursor.isAfterLast()){
21     .......
22     .......//具体操作
23     cursor.moveToNext();
24 }
25 
26 方法四:
27 cursor.moveToFirst();
28 do{
29     ......
30 }while(cursor.moveToNext());

上述代码看似很简单,但是在自测的过程却出现了奇怪的问题:

    使用while偶现几次丢失数据的情况(即当前有三条数据,只读取了两条;但是此时(使用while)若将记录的id进行读取则能成功读取三条数据),具体原因未找到,之后又多次测试则无法复现了,因此在此处先记下该种情况,有知道的可以留言告知,谢谢。

 

 

 

 

 

 

    

posted on 2019-07-24 17:26  Android之路  阅读(3029)  评论(0编辑  收藏  举报