四大组件之ContentProvider

参考:ContentProvider内容提供者

ContentProvider 简介

用于在不同的应用程序之间实现数据共享的功能。ContentProvider 可以理解为应用对外开放的接口,只要是符合它所定义的 Uri 格式的请求,均可以正常访问执行操作。其他的应用可以使用 ContentResolver 对象通过与 ContentProvider 同名的方法请求执行,被执行的就是 ContentProvider 中的同名方法。

ContentProvider的方法

boolean onCreate()   
初始化提供者

Cursor query(Uri uri, String[] projection, String selection, 
String[] selectionArgs, String sortOrder)  
查询数据,返回一个数据Cursor对象

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

int update(Uri uri, ContentValues values, String selection, String[] selectionArgs) 
根据条件更新数据。其中参数selection和selectionArgs是外部程序提供的条件

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

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

备注:还有一个 call() 方法,使用该方法,理论上可以在 ContentResolver 中执行 ContentProvider 暴露出来的任何方法。

Uri

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

<srandard_prefix>://<authority>/<data_path>/<id>
  • srandard_prefix:ContentProvider 的 srandard_prefix 始终是 content:。

  • authority:ContentProvider 的名称。

  • data_path:请求的数据类型。

  • id:指定请求的特定数据。

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

添加一个 Uri 匹配项

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

匹配传递的 Uri ,返回 addURI() 传递的 code 参数

int match(Uri uri):

代码示例:

总共有4个类,DBHelper(用于初始化SQLite数据库),PersonDao(用于CRUD操作的工具类),PersonProvider(用于对外开放的接口),ProviderTest(用于测试)

DBHelper

public class DBHelper extends SQLiteOpenHelper {

    private static String name = "test.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
        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) {
        String sql = "alter table person add sex varcher(8)";
        db.execSQL(sql);
    }
}

PersonDao

public class PersonDao {

    private DBHelper mDBHelper;

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

    public long insertPserson(ContentValues values) {
        //返回插入的行号
        long id = -1;
        SQLiteDatabase database = null;
        try {
            database = mDBHelper.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 = mDBHelper.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) {
        //返回更新的条数
        int count = -1;
        SQLiteDatabase database = null;
        try {
            database = mDBHelper.getWritableDatabase();
            count = database.update("person",values, whereClause, whereArgs);
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            if (database != null) {
                database.close();
            }
        }
        return count;
    }

    public Cursor queryPersons(String selection, String[] selectionArgs) {
        SQLiteDatabase database = null;
        Cursor cursor = null;
        try {
            // 这里获取读数据库
            database = mDBHelper.getReadableDatabase();
            cursor = database.query(true, "person", null, selection, selectionArgs, null, null, null, null);
        } catch (Exception e) {
            e.printStackTrace();
        } finally {

        }
        return cursor;
    }
}

PersonProvider

public class PersonProvider extends ContentProvider {

    private final String TAG = "PersonProvider: ";
    private PersonDao mPersonDao;
    private final static String AUTHORITY = "zeng.fanda.com.fourmoduledemo.provider.PersonProvider";
    private final static UriMatcher URI_MATCHER = new UriMatcher(UriMatcher.NO_MATCH); //默认规则不匹配
    // 操作单行记录
    private static final int PERSON = 1;
    // 操作多行记录
    private static final int PERSONS = 2;

    static {
        //添加两个匹配规则
        URI_MATCHER.addURI(AUTHORITY, "person", PERSONS);
        //使用通配符#匹配任意数字
        URI_MATCHER.addURI(AUTHORITY, "person/#", PERSON);
    }

    @Override
    public boolean onCreate() {
        //初始化实例
        mPersonDao = new PersonDao(getContext());
        Logger.i(TAG + "onCreate");
        return true;
    }

    @Nullable
    @Override
    public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder) {
        Cursor cursor = null;
        //匹配uri ,返回自定义code
        int flag = URI_MATCHER.match(uri);
        switch (flag) {
            case PERSON:
                // 解析出uri中结尾的id
                long id = ContentUris.parseId(uri);
                cursor = mPersonDao.queryPersons("id = ?", new String[]{String.valueOf(id)});
                break;
            case PERSONS:
                cursor = mPersonDao.queryPersons(selection, selectionArgs);
                break;
        }
        if (cursor != null) {
            Logger.i(TAG + "--》查询成功,count = " + cursor.getCount());
        }
        return cursor;
    }

    @Nullable
    @Override
    public String getType(Uri uri) {
        // 用于获取uri对象所对应的MIME类型
        int flag = URI_MATCHER.match(uri);
        switch (flag) {
            case PERSON:
                //单条记录,规则为 vnd.android.cursor.item/自定义path
                return "vnd.android.cursor.item/person";
            case PERSONS:
                //多条记录,规则为 vnd.android.cursor.dir/自定义path
                return "vnd.android.cursor.dir/persons";       
        }
        return null;
    }


    @Nullable
    @Override
    public Uri insert(Uri uri, @Nullable ContentValues values) {
        Uri resultUri = null;
        // 解析Uri ,返回Code
        int flag = URI_MATCHER.match(uri);
        switch (flag) {
            case PERSONS:
                // 执行插入操作,返回当前的行号
                long id = mPersonDao.insertPserson(values);
                resultUri = ContentUris.withAppendedId(uri, id);
                Logger.i(TAG + "--》插入成功,id = " + id);
                Logger.i(TAG + "--》插入成功,resultUri = " + resultUri.toString());
        }
        return resultUri;
    }

    @Override
    public int delete(Uri uri, @Nullable String selection, @Nullable String[] selectionArgs) {
        int count = -1; //影响数据库的行数
        int flag = URI_MATCHER.match(uri);
        switch (flag) {
            case PERSON:
                // 单条数据,使用 ContentUris 工具类解析出结尾的 Id
                long id = ContentUris.parseId(uri);
                count = mPersonDao.deletePerson("id = ?", new String[]{String.valueOf(id)});
                break;
            case PERSONS:
                count = mPersonDao.deletePerson(selection, selectionArgs);
                break;
        }
        Logger.i(TAG + "--》删除成功,count = " + count);
        return count;
    }

    @Override
    public int update(Uri uri, @Nullable ContentValues values, @Nullable String selection, @Nullable String[] selectionArgs) {
        int count = -1;
        int flag = URI_MATCHER.match(uri);
        switch (flag) {
            case PERSON:
                long id = ContentUris.parseId(uri);
                count = mPersonDao.updatePerson(values, "id = ?", new String[]{String.valueOf(id)});
                break;
            case PERSONS:
                count = mPersonDao.updatePerson(values, selection, selectionArgs);
                break;
        }
        Logger.i(TAG + "--》更新成功,count = " + count);
        return count;
    }


    @Nullable
    @Override
    public Bundle call( @NonNull String method, @Nullable String arg,  @Nullable Bundle extras) {
        Bundle bundle = new Bundle();
        bundle.putString("returnCall","call被执行了");
        return bundle;
    }
}

ProviderTest

@RunWith(AndroidJUnit4.class)
public class ProviderTest {

    @Test
    public void callTest() {
        Context appContext = InstrumentationRegistry.getTargetContext();
        ContentResolver resolver = appContext.getContentResolver();
        Uri uri = Uri.parse("content://zeng.fanda.com.fourmoduledemo.provider.PersonProvider/person");
        Bundle bundle = resolver.call(uri, "method", null, null);
        Logger.d("callTest: " + bundle.getString("returnCall"));
    }

    @Test
    public void insertTest() {
        // 插入数据
        Context appContext = InstrumentationRegistry.getTargetContext();
        ContentResolver resolver = appContext.getContentResolver();
        Uri uri = Uri.parse("content://zeng.fanda.com.fourmoduledemo.provider.PersonProvider/person");
        ContentValues values = new ContentValues();
        values.put("name", "蛋卷");
        values.put("address", "茶光村");
        resolver.insert(uri, values);

    }

    @Test
    public void deleteTest() {
        // 根据 id 来删除
        Context appContext = InstrumentationRegistry.getTargetContext();
        ContentResolver resolver = appContext.getContentResolver();
        Uri uri = Uri.parse("content://zeng.fanda.com.fourmoduledemo.provider.PersonProvider/person/2");
        resolver.delete(uri, null, null);

    }

    @Test
    public void updateTest() {
        // 根据 id 来更新
        Context appContext = InstrumentationRegistry.getTargetContext();
        ContentResolver resolver = appContext.getContentResolver();
        Uri uri = Uri.parse("content://zeng.fanda.com.fourmoduledemo.provider.PersonProvider/person/1");
        ContentValues values = new ContentValues();
        values.put("name", "乖巧");
        values.put("address", "珠光村");
        resolver.update(uri, values,null, null);

    }

    @Test
    public void queryTest() {
        //查询所有
        Context appContext = InstrumentationRegistry.getTargetContext();
        ContentResolver resolver = appContext.getContentResolver();
        Uri uri = Uri.parse("content://zeng.fanda.com.fourmoduledemo.provider.PersonProvider/person");
        Cursor cursor = resolver.query(uri, null, null, null, null);
        if (cursor != null) {
            while (cursor.moveToNext()) {
                Logger.d("PersonProvider: " + cursor.getString(cursor.getColumnIndex("name")));
            }
            cursor.close();
        }
    }

    @Test
    public void querySelectionTest() {
        // 根据查询条件测试
        Context appContext = InstrumentationRegistry.getTargetContext();
        ContentResolver resolver = appContext.getContentResolver();
        Uri uri = Uri.parse("content://zeng.fanda.com.fourmoduledemo.provider.PersonProvider/person/3");
        Cursor cursor = resolver.query(uri, null, "name=?", new String[]{"fanda"}, null);
        if (cursor != null) {
            while (cursor.moveToNext()) {
                Logger.d("PersonProvider: " + cursor.getString(cursor.getColumnIndex("name")));
            }
        }
    }

}        

此外,要在清单文件 AndroidManifest 中注册:

<provider
    android:name=".provider.PersonProvider"
    android:authorities="zeng.fanda.com.fourmoduledemo.provider.PersonProvider"
    android:exported="true" />

注意:exported = true 时,才可能跨应用跨进程访问数据。

下面是分别调用单元测试的方法所打印的日志:

insertTest方法:

deleteTest 方法:

updateTest 方法:

queryTest 方法:

callTest 方法: