美文网首页
2.4 Android中的IPC方式(三)

2.4 Android中的IPC方式(三)

作者: 武安长空 | 来源:发表于2016-06-17 12:15 被阅读143次

    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方法来完成的。

    相关文章

      网友评论

          本文标题:2.4 Android中的IPC方式(三)

          本文链接:https://www.haomeiwen.com/subject/nxrvdttx.html