前言
之前只知道ContentProvider是Android进程间通信的一种方式,最近才做了一个具体的学习,特此记录
ContentProvider也是AndroidIPC的一种很重要的方式,和Messenger一样,天生就适合进程间通信。同样的,它使用起来也比AIDL要简单很多,因为都是对Binder做了一层封装。
1.概念理解
使用ContentProvider,其实是提供了一套标准的数据操作接口给外界。外界可以通过这一整套接口(具体就是实现CRUD的功能的接口),实现数据之间的共享,从而实现进程间通信。至于具体操作的数据源是什么,比如是本地数据库,还是文件,还是网络获取,外界并不需要关心,这是ContentProvider内部的事情。
2.具体使用
①创建Book实体类
/**
* Created by didiwei on 2022/5/15
* desc: Book实体类
*/
public class Book {
int bookId;
String bookName;
public Book(){
}
public Book(int bookId, String bookName) {
this.bookId = bookId;
this.bookName = bookName;
}
public int getBookId() {
return bookId;
}
public void setBookId(int bookId) {
this.bookId = bookId;
}
public String getBookName() {
return bookName;
}
public void setBookName(String bookName) {
this.bookName = bookName;
}
@Override
public String toString() {
return "Book{" +
"bookId=" + bookId +
", bookName='" + bookName + '\'' +
'}';
}
}
②创建数据库,作为ContentProvider操作的数据来源
/**
* Created by didiwei on 2022/5/15
* desc: 数据库
*/
public class DatabaseHelper extends SQLiteOpenHelper {
private static final int DB_VERSION = 1;//数据库版本号
private static final String DB_NAME = "book_provider.db";//数据库名称
public static final String BOOK_TABLE_NAME = "book";//book表
public static final String USER_TABLE_NAME = "user";//user表
//创建book表和user表
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_TABLE_NAME +
"(_id integer primary key," + "name text," + "sex int)";
//创建默认构造方法
public DatabaseHelper(Context context){
super(context,DB_NAME,null,DB_VERSION);
}
@Override
public void onCreate(SQLiteDatabase db) {
//创建book表和user表
db.execSQL(CREATE_BOOK_TABLE);
db.execSQL(CREATE_USER_TABLE);
}
@Override
public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
}
}
③创建ContentProvider,即BookProvider
同时让它运行在一个独立的进程中

其中,
(1)authorities是ContentProvider的唯一标识。
(2)外界应用如果想访问BookProvider,就必须声明android:permission="com.example.PROVIDER"这个权限
/**
* todo query 参数相对应的 sql语句
*
* String[] projection = {
* ContactsContract.Contacts._ID,
* ContactsContract.CommonDataKinds.Phone.DISPLAY_NAME,
* ContactsContract.CommonDataKinds.Phone.NUMBER
* };
*
* String selectionClause = ContactsContract.CommonDataKinds.Phone.NUMBER + " = ?";
*
* String[] selectionArgs = {"123456"};
*
* getContentResolver().query(uri, projection, selectionClause, selectionArgs, "sort_key COLLATE LOCALIZED asc");
*
* todo -----------------------------------------
*
* 上面的代码 类似于下面的sql语句
* SELECT _ID, displayName, number FROM uri WHERE number = "123456" ORDER BY sort_key COLLATE LOCALIZED asc
*/
/**
* Created by didiwei on 2022/5/15
* desc: ContentProvider实体类
*
* todo 其实ContentProvider不一定操作数据库,还可以是文件,网络等。它只暴露一套CRUD的接口给外界,至于数据源具体从哪来,外界不需要管
* 只是在这里为了方便演示,就以ContentProvider操作数据库为例。
*/
public class BookProvider extends ContentProvider {
SQLiteDatabase mDb;//数据库
public static final String AUTHORITY = "com.example.provider";//ContentProvider唯一标识
//分别操作Book表和User表的uri
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_URI_CODE = 0;
public static final int USER_URI_CODE = 1;
//UriMatcher
private static final UriMatcher uriMatcher = new UriMatcher(UriMatcher.NO_MATCH);
static {
uriMatcher.addURI(AUTHORITY,"book",BOOK_URI_CODE);
uriMatcher.addURI(AUTHORITY,"user",USER_URI_CODE);
}
@Override
public boolean onCreate() {
Log.v("ljh","onCreate,current thread:" + Thread.currentThread().getName());
//实际项目中,不建议在onCreate中进行耗时的数据库操作
initProviderData();
return false;
}
//进行数据库的初始化操作
public void initProviderData(){
mDb = new DatabaseHelper(getContext()).getWritableDatabase();
mDb.execSQL("delete from " + DatabaseHelper.BOOK_TABLE_NAME);
mDb.execSQL("delete from " + DatabaseHelper.USER_TABLE_NAME);
mDb.execSQL("insert into book values(3,'Android');");
mDb.execSQL("insert into book values(4,'Ios');");
mDb.execSQL("insert into book values(5,'Html');");
mDb.execSQL("insert into user values(1,'jake',1);");
mDb.execSQL("insert into user values(2,'Android',0);");
}
@Nullable
@Override
public Cursor query(@NonNull Uri uri, @Nullable String[] projection, @Nullable String selection, @Nullable String[] selectionArgs, @Nullable String sortOrder) {
Log.v("ljh","query,current thread:" + Thread.currentThread().getName());
//首先根据uri得到表的名称
String tableName = getTableName(uri);
if(tableName.equals("")){
throw new IllegalArgumentException("Unsupported URI:" + uri);
}
return mDb.query(tableName,projection,selection,selectionArgs,null,null,sortOrder,null);
}
@Nullable
@Override
public String getType(@NonNull Uri uri) {
Log.v("ljh","getType,current thread:" + Thread.currentThread().getName());
return null;
}
@Nullable
@Override
public Uri insert(@NonNull Uri uri, @Nullable ContentValues values) {
Log.v("ljh","insert,current thread:" + Thread.currentThread().getName());
String tableName = getTableName(uri);
if(tableName.equals("")){
throw new IllegalArgumentException("Unsupported URI:" + uri);
}
mDb.insert(tableName,null,values);
getContext().getContentResolver().notifyChange(uri,null);//通知外界当前ContentProvider已经改变
return null;
}
@Override
public int delete(@NonNull Uri uri, @Nullable String selection, @Nullable String[] selectionArgs) {
Log.v("ljh","delete,current thread:" + Thread.currentThread().getName());
String tableName = getTableName(uri);
if(tableName.equals("")){
throw new IllegalArgumentException("Unsupported URI:" + uri);
}
int count = mDb.delete(tableName,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) {
Log.v("ljh","update,current thread:" + Thread.currentThread().getName());
String tableName = getTableName(uri);
if(tableName.equals("")){
throw new IllegalArgumentException("Unsupported URI:" + uri);
}
int row = mDb.update(tableName,values,selection,selectionArgs);
if(row > 0){
getContext().getContentResolver().notifyChange(uri,null);
}
return row;
}
//得到要操作的表的名字
private String getTableName(Uri uri){
String tableName = "";
switch (uriMatcher.match(uri)){
case BOOK_URI_CODE:
//操作的是Book表
tableName = DatabaseHelper.BOOK_TABLE_NAME;
break;
case USER_URI_CODE:
//操作的是User表
tableName = DatabaseHelper.USER_TABLE_NAME;
break;
default:
break;
}
return tableName;
}
}
④(可选)创建ContentObserver
看名字也能看出来,ContentObserver是Content的观察者。当有人对相应uri的数据进行更改的时候,就会触发ContentObserver
/**
* Created by didiwei on 2022/5/15
* desc: 监听ContentProvider中指定Uri标识数据的变化
*
* 参考文章:https://www.cnblogs.com/longjunhao/p/8926858.html
*/
public class MyContentObserver {
public static final int CONTENTPROVIDER_ONCHANGE = 1;
Handler handler;
Context context;
MyContentObserver(Context context,Handler handler){
this.handler = handler;
this.context = context;
}
public final ContentObserver mContentObserver = new ContentObserver(new Handler()) {
@Override
public void onChange(boolean selfChange, @Nullable Uri uri) {
//selfChange一般为false
Log.v("ljh","MyContentObserver");
handler.obtainMessage(CONTENTPROVIDER_ONCHANGE,"这里是来自onChange方法的Message").sendToTarget();
}
};
}
⑤写客户端,即操作ContentProvider的角色
public class MainActivity extends AppCompatActivity {
//ContentObserver
MyContentObserver myContentObserver;
//Handler
Handler handler = new Handler(){
@Override
public void handleMessage(@NonNull Message msg) {
switch (msg.what){
case MyContentObserver.CONTENTPROVIDER_ONCHANGE:
Log.v("ljh","这里是MainActivity里面的Handler,传来的Msg的obj为" + (String)msg.obj);
break;
default:
break;
}
}
};
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
//得到book表
Uri bookUri = BookProvider.BOOK_CONTENT_URI;
//创建监听
myContentObserver = new MyContentObserver(this,handler);
//为book表注册监听
//notifyForDescendants 为false 表示精确匹配,即只匹配该Uri ,为true 表示可以同时匹配其派生的Uri
getContentResolver().registerContentObserver(bookUri,false,myContentObserver.mContentObserver);
//todo -----------------对book表的insert-----------------
//为book表insert一条数据
ContentValues values = new ContentValues();
values.put("_id",6);
values.put("name","在MainActivity新增的书");
getContentResolver().insert(bookUri,values);
//todo -----------------对book表的query-----------------
Cursor cursor = getContentResolver().query(bookUri, new String[]{"_id", "name"}, null, null, null);
while(cursor.moveToNext()){
Book book = new Book();
book.setBookId(cursor.getInt(0));
book.setBookName(cursor.getString(1));
Log.v("ljh","query book:" + book);
}
cursor.close();
//为book表解除监听
getContentResolver().unregisterContentObserver(myContentObserver.mContentObserver);
}
}
效果


注意进程区别
3.项目链接与文章推荐
项目地址:https://github.com/LJHnb666666/ContentProviderDemo
好文章推荐
本文介绍了ContentProvider作为Android进程间通信的重要方式,通过提供标准的数据操作接口实现数据共享。详细讲解了如何创建Book实体类、数据库、ContentProvider以及客户端的编写,同时也提到了ContentObserver的使用,最后给出了项目链接和相关文章推荐。
3万+

被折叠的 条评论
为什么被折叠?



