【Android 笔记】ContentProvider 及其实现 IPC

Author Avatar
vecrates 12月 14, 2017

  ContentProvider 是 Android 中用于实现不同应用中共享数据的组件。通过 ContentProvider 可以将数据分享给其他应用或进程,但是它不直接存储数据,只提供管理数据的一系列方法,数据的存储可以是 SQLite、文件等。调用者也不是直接使用 ContentProvider 里的方法,而是需要借助 ContentResolver 对象。
 即 调用者 ==> ContentResolver ==> ContentProvider ==> 数据

使用 ContentProvider

继承 ContentProvider,实现:

  • boolean onCreate()
  • Cursor query()
  • Uri insert()
  • int delete()
  • int update()
  • String getType(),返回 Uri 请求的 MIME 数据类型
    其中 query()、insert()、delete()、update() 可能存在多并发的情况,需要处理好同步问题。

使用 ContentProvider 还需在 Manifest 中注册

  • android:authorities,用于唯一标识该 ContentProvider
  • exported,boolean值,表示能否被调用
  • permission,读写权限,第三方应用要使用该 ConetentProvider 必须声明
  • readPermission,query() 权限
  • writePermission,insert()、update()、delete() 权限

ContentResolver

调用者使用 ContentProvider 是通过ContentResolver 来实现的,ContentResolver 提供了与 ContentProvider 对应的一系列方法用于对数据的增删改查,它可以调用任何可以调用的 ContentProvider,而识别具体的 ContentProvider 需要通过 Uri

UriMatch

ContentResolver 通过 Uri 来调用 ContentProvider,一个 ContentProvider 中可能可以访问多个表,为了知道 Uri 指的是哪张表,需要使用 UriMatch 来进行匹配。

  • void addURI(String authority,String path, int code),添加一个用于匹配的 Uri 以及 Uri 所对应的 code
  • int match(Uri uri),uri 匹配成功时,返回 addURI() 中的 code

Demo

实现 Activity 跨进程访问 ContentProvider 插入、删除、查询联系人,数据存储使用 SQLite。
1)实现联系人数据库的管理

public class DBOpenHelper extends SQLiteOpenHelper {

    private static final String DB_NAME = "contacts.db";
    public static final String CONTACTS_TABLE_NAME = "t_contacts";

    private static final String CREATE_CONTACTS_TABLE = "CREATE TABLE IF NOT EXISTS "
            + CONTACTS_TABLE_NAME + " (phonenumber CHAR(11) PRIMARY KEY, name TEXT)";

    public static final int DB_VESION = 1;

    public DBOpenHelper(Context context) {
        super(context, DB_NAME, null, DB_VESION);
    }

    @Override
    public void onCreate(SQLiteDatabase sqLiteDatabase) {
        sqLiteDatabase.execSQL(CREATE_CONTACTS_TABLE);
    }

    @Override
    public void onUpgrade(SQLiteDatabase sqLiteDatabase, int i, int i1) {

    }
}

2)继承 ContentProvider,实现对联系人的增删改查等

public class ContactsProvider extends ContentProvider {

    private static final String AUTHORITY = "cn.vecrates.ipc.contentProvider.ContactsProvider";
    public static final String CONTACTS_URI = "content://" + AUTHORITY + "/" + DBOpenHelper.CONTACTS_TABLE_NAME;

    private static final int CONTACTS_TABLE_CODE = 0;

    private UriMatcher mUriMatcher; //用于匹配不同的URI,以便操作不同的表
    private SQLiteDatabase mDb;

    @Override
    public boolean onCreate() {

        Log.v("_v", "ContactsProvider进程Id " + Process.myPid());

        mUriMatcher = new UriMatcher(UriMatcher.NO_MATCH);
        //使t_contacts 表与 code 联系起来
        mUriMatcher.addURI(AUTHORITY, DBOpenHelper.CONTACTS_TABLE_NAME, CONTACTS_TABLE_CODE);

        mDb = new DBOpenHelper(getContext()).getWritableDatabase();
        return false;
    }

    @Nullable
    @Override
    public Cursor query(Uri uri, String[] strings, String s, String[] strings1, String s1) {
        String tableName = getTableName(uri);
        if(tableName == null) {
            throw new IllegalArgumentException("Erro Uri: " + uri);
        }
        Cursor cursor = mDb.query(tableName, strings, s, strings1, s1, null, null);
        return cursor;
    }

    /**
     * Uri中的数据的MIME类型
     * @param uri
     * @return
     */
    @Nullable
    @Override
    public String getType(Uri uri) {
        return null;
    }

    @Nullable
    @Override
    public Uri insert(Uri uri, ContentValues contentValues) {
        String tableName = getTableName(uri);
        if(tableName == null) {
            throw new IllegalArgumentException("Erro Uri: " + uri);
        }
        mDb.insert(tableName, null, contentValues);
        getContext().getContentResolver().notifyChange(uri, null);
        return null;
    }

    @Override
    public int delete(Uri uri, String s, String[] strings) {
        String tableName = getTableName(uri);
        if(tableName == null) {
            throw new IllegalArgumentException("Erro Uri: " + uri);
        }
        int count = mDb.delete(tableName, s, strings);
        if(count > 0) {
            getContext().getContentResolver().notifyChange(uri, null);
        }
        return 0;
    }

    @Override
    public int update(Uri uri, ContentValues contentValues, String s, String[] strings) {
        return 0;
    }

    /**
     * 通过此方法判别 URI 是指向那张数据表
     * @param uri
     * @return
     */
    private String getTableName(Uri uri) {
        String tableName = null;
        switch (mUriMatcher.match(uri)) {
            case CONTACTS_TABLE_CODE:
                tableName = DBOpenHelper.CONTACTS_TABLE_NAME;
                break;
        }
        return tableName;
    }
}

并且注册 ContentProvider

        <provider
            android:name=".contentProvider.ContactsProvider"
            android:authorities="cn.vecrates.ipc.contentProvider.ContactsProvider"
            android:permission="cn.vecrates.BOOKPROVIDER"
            android:process=":provider"/>

3)通过 ContentResolver 调用

public class ProviderActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_provider);

        Log.v("_v", "Activity进程Id " + Process.myPid());

        Uri contactsUri = Uri.parse(ContactsProvider.CONTACTS_URI);

//        getContentResolver().delete(contactsUri, "name=?", new String[]{"vecrates"});

        ContentValues values = new ContentValues();
        values.put("phoneNumber", "15000000001");
        values.put("name", "vecrates");
        getContentResolver().insert(contactsUri, values);
        Log.v("_v", "插入了 " + "vecrates " + " 15000000001");

        String[] colum = new String[]{"phoneNumber", "name"};
        Cursor cursor = getContentResolver().query(contactsUri, colum, null, null, null);
        while(cursor.moveToNext()) {
            String phoneNumber = cursor.getString(0);
            String name = cursor.getString(1);
            Log.v("_v", "获取到联系人 " + phoneNumber + "  " + name);
        }
        cursor.close();
    }

}