2.4 Android中的IPC方式(三)

1. ContentProvider简介

ContentProvider是Android中专门用于不同应用间进行数据共享的方式,也就是可以实现进程间通信。和Messenger一样,ContentProvider的底层实现同样也是Binder。
系统预置了许多ContentProvider,比如通讯录信息、日程表信息等,要跨进程访问这些信息,只需要通过ContentResolver的query,update,insert和delete方法即可。
ContentProvider主要以表格的形式来组织数据,并且可以包含多个表,对于每个表格来说,他们都具有行和列的层次性,行往往对应一条记录,而列对应一条记录中的一个字段。和数据库类似。
除了表格形式,ContentProvider还支持文件数据,比如图片、视频等。文件数据和表格数据的结构不同,因此处理此类数据时可以在ContentProvider中返回文件的句柄(handle)给外界从而让文件来访问ContentProvider中的文件信息。Android系统提供的MediaStore功能就是文件类型的ContentProvider,详细参考MediaStore。
虽然ContentProvider的底层看起来像是一个SQLite数据库,但是ContentProvider对底层的数据存储方式没有任何要求,我们既可以使用SQLite数据库,也可以使用普通文件,甚至可以使用内存中的对象进行存储。

2. 自己实现ContentProvider

public class BookProvider extends ContentProvider {

    @Override
    public boolean onCreate() {
        Log.e("aaa", "thread:" + Thread.currentThread().getName());
        return false;
    }

    @Override
    public Cursor query(Uri uri, String[] projection, String selection,
                        String[] selectionArgs, String sortOrder) {
        Log.e("aaa", "thread:" + Thread.currentThread().getName());
        return null;
    }

    @Override
    public int update(Uri uri, ContentValues values, String selection,
                      String[] selectionArgs) {
        throw new UnsupportedOperationException("Not yet implemented");
    }


    @Override
    public int delete(Uri uri, String selection, String[] selectionArgs) {
        throw new UnsupportedOperationException("Not yet implemented");
    }

    // getType用来返回一个Uri请求所对应的MIME类型(媒体类型),比如图片,视频等,如果我们不关注类型,可以返回null或*/*
    @Override
    public String getType(Uri uri) {
        return "*/*";
    }

    @Override
    public Uri insert(Uri uri, ContentValues values) {
        throw new UnsupportedOperationException("Not yet implemented");
    }
}

2. manifest中的ContentProvider配置

<provider
    android:name="._2activity.ContentProvider.BookProvider"
    android:authorities="qingfengmy.developmentofart.bookprovider"
    android:exported="true"
    android:permission="qingfengmy.developmentofart.provider"
    android:process=":provider" />

authorities是ContentProvider的唯一标识,通过这个属性外部应用就可以访问我们的BookProvider,因此authorities必须是唯一的,建议加上包名前缀,保证唯一。
permission是我们加的权限,访问我们Provider数据必须加上这个权限。权限还支持readPermission和writePermission属性,如果声明了读权限和写权限,那么外界应用还要加上这两个权限。读权限和写权限是高于普通权限的,也就是三个权限都定义了,外界只要有读权限就可读,有写权限就可以写,有普通权限什么也干不了。
export是允许让其他应用访问。

<permission
    android:name="qingfengmy.developmentofart.provider"
    android:protectionLevel="normal" />

自定义权限,provider中添加的权限是需要自定义的,否则只是一个普通字符串。权限是需要在系统中声明的,字符串没有声明,是永远匹配不到的。

3. 外界访问

Uri uri = Uri.parse("content://qingfengmy.developmentofart.bookprovider");
getContentResolver().query(uri,null,null,null,null);
getContentResolver().query(uri,null,null,null,null);
getContentResolver().query(uri,null,null,null,null);

Uri中,content是scheme协议名称,后面的是我们在manifest中配置的authorities的值,是唯一的。执行三次查询,打印他们的线程名称如下:

06-17 09:22:32.040 8594-8594/qingfengmy.developmentofart:remote E/aaa: thread:main
06-17 09:22:32.042 8594-8606/qingfengmy.developmentofart:remote E/aaa: thread:Binder_2
06-17 09:22:32.042 8594-8605/qingfengmy.developmentofart:remote E/aaa: thread:Binder_1
06-17 09:22:32.043 8594-8606/qingfengmy.developmentofart:remote E/aaa: thread:Binder_2

其中onCreate是main线程;其他三次查询是三个不同的子线程,binder开头,说明是Binder线程池中的。

另外,我们在同一应用中, 尽管访问和provider是不同进程,我们应用中不配置user-permission也可以访问数据。
如果不同应用,肯定是不同进程,不配置权限,访问不了。报如下错误:

 Caused by: java.lang.SecurityException: 
 Permission Denial: 
 opening provider qingfengmy.developmentofart._2activity.ContentProvider.BookProvider 
 from ProcessRecord{9e674b5 10691:qingfengmy.behaviordemo.free/u0a95} 
 (pid=10691, uid=10095) that is not exported from uid 10668

加上权限则可以访问。可见权限是应用级别的,不是进程级别的。同应用没有权限限制,不同应用才有限制。Binder中的权限拦截,是java代码主动检测的,所以本应用内,也需要配置user-permission。

4. 实现数据库管理图书和用户信息

public class DbOpenHelper extends SQLiteOpenHelper {

    private static final String DB_NAME = "book_provider.db";
    public static final String BOOK_TABLE_NAME = "book";
    public static final String USER_TALBE_NAME = "user";

    private static final int DB_VERSION = 1;

    private String CREATE_BOOK_TABLE = "CREATE TABLE IF NOT EXISTS "
            + BOOK_TABLE_NAME + "(_id INTEGER PRIMARY KEY," + "name TEXT)";

    private String CREATE_USER_TABLE = "CREATE TABLE IF NOT EXISTS "
            + USER_TALBE_NAME + "(_id INTEGER PRIMARY KEY," + "name TEXT,"
            + "sex INT)";

    public DbOpenHelper(Context context) {
        super(context, DB_NAME, null, DB_VERSION);
    }

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

    @Override
    public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
        // TODO ignored
    }

}

5. 定义Uri和Uri_Code

 private static final String AUTHORITY = "qingfengmy.developmentofart.bookprovider";
private static final Uri BOOK_CONTENT_URI = Uri.parse("content://" + AUTHORITY + "/book");
private static final Uri USER_CONTENT_URI = Uri.parse("content://" + AUTHORITY + "/user");

public static final int BOOK_URI_CODE = 0;
public static final int USER_URI_CODE = 1;

private static final UriMatcher sUriMatcher = new UriMatcher(UriMatcher.NO_MATCH);

static {
    sUriMatcher.addURI(AUTHORITY, "book", BOOK_URI_CODE);
    sUriMatcher.addURI(AUTHORITY, "user", USER_URI_CODE);
}

ContentProvider通过Uri来区分外界要访问的数据集合,BookProvider支持book表和user表的访问,所以要单独定义Uri和Uri_Code,并用UriMatcher的addURI关联起来。上面单独定义了book的uri和code,以及user的uri和code.
addURI方法定义如下:

public void addURI(String authority, String path, int code){}

6. 根据uri获取表名

private String getTableName(Uri uri){
    String tableName = null;
    // match方法是根据addURI加入的uri和code返回code
    switch (sUriMatcher.match(uri)){
        case BOOK_URI_CODE:
            tableName = DbOpenHelper.BOOK_TABLE_NAME;
            break;
        case USER_URI_CODE:
            tableName = DbOpenHelper.USER_TALBE_NAME;
            break;
    }
    return tableName;
}

7. 实现查询操作

@Override
public Cursor query(Uri uri, String[] projection, String selection,
                    String[] selectionArgs, String sortOrder) {
    String tableName = getTableName(uri);
    return mDb.query(tableName, projection, selection, selectionArgs, null, null, sortOrder, null);
}
List<Book> bookList = new ArrayList<>();
Uri uri = Uri.parse("content://qingfengmy.developmentofart.bookprovider/book");
Cursor bookCursor = (Cursor) getContentResolver().query(uri, null, null, null, null);
while (bookCursor.moveToNext()) {
    int bookId = bookCursor.getInt(0);
    String bookName = bookCursor.getString(1);
    Book book = new Book(bookId, bookName);
    bookList.add(book);
}
Log.e("aaa", bookList.toString());

8. 实现insert操作

@Override
public Uri insert(Uri uri, ContentValues values) {
    String tableName = getTableName(uri);
    mDb.insert(tableName,null,values);
    // 回调监听
    getContext().getContentResolver().notifyChange(uri,null);
    return uri;
}

数据变化时的监听

 getContentResolver().registerContentObserver(uri, true, new ContentObserver(new Handler()) {
    @Override
    public void onChange(boolean selfChange, Uri uri) {
        super.onChange(selfChange, uri);
        Log.e("aaa", "onChange:" + uri.toString());
    }
});

9. 线程同步问题

query,update,insert,delete四大方法都是存在多线程并发访问的,因此要做好线程同步。
在本例中,由于采用的是SQLite并且只有一个SQLiteDatabase的连接,所以可以正确应对多线程的情况。具体原因是SQLiteDatabase内部对数据库的操作有同步处理。

10. call方法

ContentProvider除了增删改查四大方法之外,还可以自定义方法。这个过程是通过ContentProvider的Call方法和ContentProvider的Call方法来完成的。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 202,607评论 5 476
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,047评论 2 379
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 149,496评论 0 335
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,405评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,400评论 5 364
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,479评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,883评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,535评论 0 256
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,743评论 1 295
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,544评论 2 319
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,612评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,309评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,881评论 3 306
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,891评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,136评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,783评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,316评论 2 342

推荐阅读更多精彩内容