Android IPC 通信方式之一 -------------ContentProvider

  ContentProvider 的作用

  Android 提供的用于不同应用间进行数据共享的方式 contentprovider 内部使用binder 原理进行通信。

 采用的数据源是Android中的SQLite数据库 ,文件 ,xml 数据 ,网络数据等

目录

 (1) 创建一个 类 继承 ContentProvider ,在清单文件中注册(四大组件都需要注册)并声明authorities 。

 (2) 访问 在这一步 就可以在的 activity 中去访问了 ,比如查询的方法(query)

(3)创建SQLiteOpenHelper (数据库帮助类)


辅助类介绍  URI ,UriMatcher ,ContentReslover:

统一资源标识符(URI)
定义:Uniform Resource Identifier,即统一资源标识符
作用:唯一标识 ContentProvider & 其中的数据 
外界进程通过 URI 找到对应的ContentProvider & 其中的数据,再进行数据操作

具体使用 
URI分为 系统预置 & 自定义,分别对应系统内置的数据(如通讯录、日程表等等)和自定义数据库 
1. 关于 系统预置URI 此处不作过多讲解,需要的同学可自行查看 
2. 此处主要讲解 自定义URI

扫描二维码关注公众号,回复: 4217006 查看本文章

示æå¾

/ 设置URI
Uri uri = Uri.parse("content://com.carson.provider/User/1") 
// 上述URI指向的资源是:名为 `com.carson.provider`的`ContentProvider` 中表名 为`User` 中的 `id`为1的数据

// 特别注意:URI模式存在匹配通配符* & #

// *:匹配任意长度的任何有效字符的字符串
// 以下的URI 表示 匹配provider的任何内容
content://com.example.app.provider/* 
// #:匹配任意长度的数字字符的字符串
// 以下的URI 表示 匹配provider中的table表的所有行
content://com.example.app.provider/table/# 

  • ContentUris
  • UriMatcher
  • ContentObserver

 ContentUris类
作用:操作 URI
具体使用 
核心方法有两个:withAppendedId() &parseId()
// withAppendedId()作用:向URI追加一个id
Uri uri = Uri.parse("content://cn.scu.myprovider/user") 
Uri resultUri = ContentUris.withAppendedId(uri, 7);  
// 最终生成后的Uri为:content://cn.scu.myprovider/user/7

// parseId()作用:从URL中获取ID
Uri uri = Uri.parse("content://cn.scu.myprovider/user/7") 
long personid = ContentUris.parseId(uri); 
//获取的结果为:7
--------------------- 

UriMatcher类
作用

在ContentProvider 中注册URI
根据 URI 匹配 ContentProvider 中对应的数据表
具体使用

// 步骤1:初始化UriMatcher对象
    UriMatcher matcher = new UriMatcher(UriMatcher.NO_MATCH); 
    //常量UriMatcher.NO_MATCH  = 不匹配任何路径的返回码
    // 即初始化时不匹配任何东西

// 步骤2:在ContentProvider 中注册URI(addURI())
    int URI_CODE_a = 1;
    int URI_CODE_b = 2;
    matcher.addURI("cn.scu.myprovider", "user1", URI_CODE_a); 
    matcher.addURI("cn.scu.myprovider", "user2", URI_CODE_b); 
    // 若URI资源路径 = content://cn.scu.myprovider/user1 ,则返回注册码URI_CODE_a
    // 若URI资源路径 = content://cn.scu.myprovider/user2 ,则返回注册码URI_CODE_b

// 步骤3:根据URI 匹配 URI_CODE,从而匹配ContentProvider中相应的资源(match())

@Override   
    public String getType(Uri uri) {   
      Uri uri = Uri.parse(" content://cn.scu.myprovider/user1");   

      switch(matcher.match(uri)){   
     // 根据URI匹配的返回码是URI_CODE_a
     // 即matcher.match(uri) == URI_CODE_a
      case URI_CODE_a:   
        return tableNameUser1;   
        // 如果根据URI匹配的返回码是URI_CODE_a,则返回ContentProvider中的名为tableNameUser1的表
      case URI_CODE_b:   
        return tableNameUser2;
        // 如果根据URI匹配的返回码是URI_CODE_b,则返回ContentProvider中的名为tableNameUser2的表
    }   
}
 ContentObserver类
定义:内容观察者
作用:观察 Uri引起 ContentProvider 中的数据变化 & 通知外界(即访问该数据访问者) 
当ContentProvider 中的数据发生变化(增、删 & 改)时,就会触发该 ContentObserver类

// 步骤1:注册内容观察者ContentObserver
    getContentResolver().registerContentObserver(uri);
    // 通过ContentResolver类进行注册,并指定需要观察的URI

// 步骤2:当该URI的ContentProvider数据发生变化时,通知外界(即访问该ContentProvider数据的访问者)
    public class UserContentProvider extends ContentProvider { 
      public Uri insert(Uri uri, ContentValues values) { 
      db.insert("user", "userid", values); 
      getContext().getContentResolver().notifyChange(uri, null); 
      // 通知访问者
   } 
}

// 步骤3:解除观察者
 getContentResolver().unregisterContentObserver(uri);
    // 同样需要通过ContentResolver类进行解
--------------------- 

4.2 MIME数据类型
作用:指定某个扩展名的文件用某种应用程序来打开 
如指定.html文件采用text应用程序打开、指定.pdf文件采用flash应用程序打开

具体使用:

4.2.1 ContentProvider根据 URI 返回MIME类型

ContentProvider.geType(uri) ;
1
4.2.2 MIME类型组成 
每种MIME类型 由2部分组成 = 类型 + 子类型

MIME类型是 一个 包含2部分的字符串

text / html
// 类型 = text、子类型 = html

text/css
text/xml
application/pdf
1
2
3
4
5
6
4.2.3 MIME类型形式 
MIME类型有2种形式:


// 形式1:单条记录  
vnd.android.cursor.item/自定义
// 形式2:多条记录(集合)
vnd.android.cursor.dir/自定义 

// 注:
  // 1. vnd:表示父类型和子类型具有非标准的、特定的形式。
  // 2. 父类型已固定好(即不能更改),只能区别是单条还是多条记录
  // 3. 子类型可自定义
--------------------- 
 

   使用 需要 三步:

 (1) 创建一个 类 继承 ContentProvider ,在清单文件中注册(四大组件都需要注册)并声明authorities 。

 并且重写 六个抽象方法 oncreate ,query ,update ,insert ,delete  gettype;

oncreate :contentprovider 的创建方法 用来做初始化的操作 。

query :查询方法 。

update 修改(更新)数据方法

insert 添加数据方法 

delete 删除数据 。

gettype 返回uri 请求所对应的MiME 类型 ,图片和视频 数据的类型 。

android :authorities ="com.example.liq.apfirst.Provider.BookProvider ";  这个是自定义的 ,可以是 你应用的包名

android:exported="true"  // 设置此provider是否可以被其他进程使用

// 声明本应用 可允许通信的权限 <permission android:name="scut.carson_ho.Read" android:protectionLevel="normal"/>


               // 声明外界进程可访问该Provider的权限(读 & 写)
               android:permission="scut.carson_ho.PROVIDER"             

               // 权限可细分为读 & 写的权限
               // 外界需要声明同样的读 & 写的权限才可进行相应操作,否则会报错
               // android:readPermisson = "scut.carson_ho.Read"
               // android:writePermisson = "scut.carson_ho.Write"

--------------------- 
 

补充 一点 :

根据binder的工作原理 ,除了oncreate 方法 由系统回调并运行在主线程,其他五个方法 都运行在contentprovider 的进程中 由外界回调 并运行在Binder 的线程池 

@Override
public boolean onCreate() {
    mContext = getContext();
        initProviderData();
        return true;;
}  
  
 private void initProviderData() {
        mDb = new DbOpenHelper(mContext).getWritableDatabase();
        mDb.execSQL("delete from " + DbOpenHelper.BOOK_TABLE_NAME);
        mDb.execSQL("delete from " + DbOpenHelper.USER_TALBE_NAME);
        mDb.execSQL("insert into book values(3,'Android');");
        mDb.execSQL("insert into book values(4,'Ios');");
        mDb.execSQL("insert into book values(5,'Html5');");
        mDb.execSQL("insert into user values(1,'jake',1);");
        mDb.execSQL("insert into user values(2,'jasmine',0);");
    } 
 @Nullable
    @Override
    public Cursor query(@NonNull Uri uri, @Nullable String[] projection, @Nullable String selection, @Nullable String[] selectionArgs, @Nullable String sortOrder) {
       
    String table=getTableName(uri);
if (table==null){
    throw new IllegalArgumentException("Unsuppported uri");
}
return  mdb.query(table,projection,selection,selectionArgs,null,null,sortOrder,null);
    }

    @Nullable
    @Override
    public String getType(@NonNull Uri uri) {
        return null;
    }

    @Nullable
    @Override
    public Uri insert(@NonNull Uri uri, @Nullable ContentValues values) {
        String table=getTableName(uri);
           if (table==null){
                  throw  new IllegalArgumentException("unsportted uri"+uri);
                             }
                   mdb.insert(table,null,values);
                   mcontext.getContentResolver().notifyChange(uri,null);
                   return uri;
    }

    @Override
    public int delete(@NonNull Uri uri, @Nullable String selection, @Nullable String[] selectionArgs) {
     String table=getTableName(uri);
     if (table==null){
        throw  new IllegalArgumentException("unsportted uri"+uri);
                     }
     int count=mdb.delete(table,selection,selectionArgs);
      if (count>0){
        getContext().getContentResolver().notifyChange(uri,null);
                     }
             return count;
    }

    @Override
    public int update(@NonNull Uri uri, @Nullable ContentValues values, @Nullable String selection, @Nullable String[] selectionArgs) {
      String table=getTableName(uri);
if (table==null){
    throw  new IllegalArgumentException("unsupported URI"+uri);
}
 int row=mdb.update(table,values,selection,selectionArgs);
if (row>0){
    getContext().getContentResolver().notifyChange(uri,null);
}
return row;
    }

 (2) 访问 在这一步 就可以在的 activity 中去访问了 ,比如查询的方法(query)

   Uri uri=Uri.parse("content://com.com.example.liq.apfirst.Provider.BookProvider/user"); //括号内的参数 就是在清单文件注册的         authorities+数据库表名  // 设置ContentProvider的URI

ContentResolver contentResolver=getContentResolver();// 可通过在所有继承Context的类中 通过调用getContentResolver()来获 得ContentResolver

 Uri bookUri = Uri.parse("content://com.ryg.chapter_2.book.provider/book");
        ContentValues values = new ContentValues();
        values.put("_id", 6);
        values.put("name", "程序设计的艺术");
        getContentResolver().insert(bookUri, values);
        Cursor bookCursor = getContentResolver().query(bookUri, new String[]{"_id", "name"}, null, null, null);
        while (bookCursor.moveToNext()) {
            Book book = new Book();
            book.bookId = bookCursor.getInt(0);
            book.bookName = bookCursor.getString(1);
            Log.d(TAG, "query book:" + book.toString());
        }
        bookCursor.close();

        Uri userUri = Uri.parse("content://com.ryg.chapter_2.book.provider/user");
        Cursor userCursor = getContentResolver().query(userUri, new String[]{"_id", "name", "sex"}, null, null, null);
        while (userCursor.moveToNext()) {
            User user = new User();
            user.userId = userCursor.getInt(0);
            user.userName = userCursor.getString(1);
            user.isMale = userCursor.getInt(2) == 1;
            Log.d(TAG, "query user:" + user.toString());
        }
        userCursor.close();
    }

(3)创建SQLiteOpenHelper (数据库帮助类)

管理数据库的方法 创建,升级,和降级

public class DBOpenHelper extends SQLiteOpenHelper {

private static final String DB_name = "book_provider.db"; // 声明数据库 
public static final String BookTabelName = "book"; //创建 一个数据 表 book 
public static final String User_TabelName = "user";//创建 一个数据 表 user  
private static final int DB_Version = 1; // 数据库的版本号 
private String CREATE_BOOK_Table = "CREATE TABLE IF NOT EXISTS " + BookTabelName + "(_ID INTEGER PRIMARY KEY," + "name Text)";
private String CREATE_BOOK_User = "CREATE TABLE IF NOT EXISTS " + User_TabelName + "(_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);    //创建book表并且 创建参数 
    db.execSQL(CREATE_BOOK_User);     //创建user表并且 创建参数 
}

@Override
public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {  // 数据库升级的方法 

}

为了 让外界能够区分要访问的数据 需要通过UriMatcher 来定义 Uri 和Uri_Code 使他们可以进行关联 。

provider 类里 

public  static  final  String AUTHORITY="com.com.example.liq.apfirst.Provider.BookProvider";
public  static  final  Uri BOOK_CONTENT_URI=Uri.parse("content://"+AUTHORITY+"/book");
public  static  final  Uri USER_CONTENT_URI=Uri.parse("content://"+AUTHORITY+"/user");
public  static  final int BOOK_CODE=0;
public  static  final int USER_CODE=1;
public static  final UriMatcher sURI_MATCHER=new UriMatcher(UriMatcher.NO_MATCH);/常量UriMatcher.NO_MATCH = 不匹配任何路径的返回码
static {
    sURI_MATCHER.addURI(AUTHORITY,"book",BOOK_CODE);
    sURI_MATCHER.addURI(AUTHORITY,"user",USER_CODE);
}

这个时候 数据Uri 和Uri_Code 已经进行关联成功了  下面这段代码意思 是 根据uri 取出 uri_code 在根据 uri_code 得到数据库的名称。

private  String getTableName(Uri uri){
    String TableName=null;
    switch (sURI_MATCHER.match(uri)){
        case BOOK_CODE: //访问book 表
            TableName=DBOpenHelper.BookTabelName;
            break;
        case USER_CODE: //访问 user 表 
            TableName=DBOpenHelper.User_TabelName;
             break;
    }
    return  TableName;
}

猜你喜欢

转载自blog.csdn.net/Jsonuu/article/details/84468284