【Android】进程间如何通信
三三要成为安卓糕手
一:Content Providers
Content Providers是Android系统提供的一种机制,用于在应用程序之间共享数据(进程间通信的一种)。通过Content Providers,我们可以将数据从一个应用程序传递给另一个应用程序。
二:场景应用
1:需求
假设我们有两个应用:A和B。应用A提供了一个Content Provider来共享它的数据,应用B通过这个Content Provider访问应用A的数据。
第一步先在进程B中完成Sqlite数据库的创建,并插入一些数据,并检查确认数据无误。参考以前的写文章
2:代码
/*** 中介方*/
public class MyContentProvider extends ContentProvider {private MySqliteHelper sqliteHelper;private static final String AUTHORITY = "com.xlong.providerprojecttest.provider";private static final String TABLE_NAME = "user";//初始化contentProvider的时候会被调用//一般会在这里进行数据的初始化,比如说数据库的实例@Overridepublic boolean onCreate() {sqliteHelper = new MySqliteHelper(getContext());if (sqliteHelper == null){return false;}return true;//表示初始化成功}@Nullable@Overridepublic Cursor query(@NonNull Uri uri, @Nullable String[] projection, @Nullable String selection, @Nullable String[] selectionArgs, @Nullable String sortOrder) {//通过数据库对象进行数据查找,这里的查找条件(参数列表里的参数)是B应用(接收方)提供的SQLiteDatabase database = sqliteHelper.getReadableDatabase();//查询的列,子句,占位符Cursor cursor = database.query(TABLE_NAME, projection, selection, selectionArgs, null, null, sortOrder);return cursor;}@Nullable@Overridepublic String getType(@NonNull Uri uri) {//返回查询结果的MIME类型String string = "vnd.android.cursor.dir/vnd." + AUTHORITY + "." + TABLE_NAME;return string;}@Nullable@Overridepublic Uri insert(@NonNull Uri uri, @Nullable ContentValues values) {SQLiteDatabase database = sqliteHelper.getWritableDatabase();//返回的是插入的那一行,主键值long id = database.insert(TABLE_NAME, null, values);//content://com.xlong.providerprojecttest.provider/userUri contentUri = Uri.parse("content://" + AUTHORITY + "/" + TABLE_NAME);Uri resultUri = ContentUris.withAppendedId(contentUri, id);return resultUri;}@Overridepublic int delete(@NonNull Uri uri, @Nullable String selection, @Nullable String[] selectionArgs) {SQLiteDatabase writableDatabase = sqliteHelper.getWritableDatabase();int count = writableDatabase.delete(TABLE_NAME, selection, selectionArgs);//返回受影响的数据有多少条return count;}@Overridepublic int update(@NonNull Uri uri, @Nullable ContentValues values, @Nullable String selection, @Nullable String[] selectionArgs) {SQLiteDatabase database = sqliteHelper.getWritableDatabase();int update = database.update(TABLE_NAME, values, selection, selectionArgs);return update;}
}
三:Provider中重写方法分析
1:表名和授权标识
-
authority
是ContentProvider
的授权标识(用于唯一确定ContentProvider
),定义格式:包名+.provider -
table
是对应的数据表名
2:query方法
接收数据的B应用调用中介provider的查询方法,B应用需要传参查询的地址和一些限制条件;
query方法内部进而去数据库中查,把查询结果封装成一个cursor对象返回回去
-
uri
:查询的 Content Provider 的资源标识符,指向特定数据集 -
projection
:指定要从 Content Provider 中返回的列(字段)- 示例:
String[] projection = {"id", "name"};
- 示例:
-
selection
:类似于 SQL 中的WHERE
子句,用于指定查询的条件- 示例:
String selection = "name LIKE '%John%'";
- 示例:
-
selectionArgs
:用于为selection中的占位符(?)提供具体的值 -
sortOrder
:数据的排序方式
3:getType
通过我们的ContentProvider查询到的数据类型
(1)MIME类型格式
Android 为 ContentProvider
定义了特定的 MIME 类型格式,用于区分 “单个数据项” 和 “多个数据项集合”:
-
多个数据项(集合) :格式为
vnd.android.cursor.dir/vnd.<authority>.<table>
。vnd.android.cursor.dir
:表示这是一个数据集合(多行记录,类似数据库中的表)。vnd.<authority>.<table>
:是自定义部分,这样组合能唯一标识该ContentProvider
下某张表的 “数据集合” 类型。
-
单个数据项:格式为
vnd.android.cursor.item/vnd.<authority>.<table>
,其中vnd.android.cursor.item
表示这是单个数据项(一行记录)。
(2)作用
当其他组件(如 ContentResolver
)通过 Uri
获取 MIME 类型时,就能根据这个格式判断数据是 “集合” 还是 “单个项”,从而进行后续适配的操作(比如集合可能需要列表展示,单个项可能需要详情展示)。
这里是告诉调用者B应用,当前 Uri
对应的是一个数据集合(比如数据库中的整张表,包含多行数据)
(3)类比MediaStore
有异曲同工之处
ContentValues values = new ContentValues();//名字values.put(MediaStore.Images.Media.DISPLAY_NAME,"my_picture.jpeg");//类型values.put(MediaStore.Images.Media.MIME_TYPE,"image/jpeg");//路径values.put(MediaStore.Images.Media.RELATIVE_PATH, Environment.DIRECTORY_PICTURES);
4:insert
(1)database.insert
返回的 id
插入数据成功后,会返回新插入行的行 ID(这是 SQLite 表中该行的主键值,自增且唯一)
(2)nullColumnHack解释
hack不好翻译
最常见含义:指通过非正统、巧妙甚至略带 “取巧” 的方式解决问题,尤其是绕过常规限制或快速常规流程的解决方案。
Android 中的 nullColumnHack
,就体现了这种意味 —— 当插入空数据时,通过指定一个可设为 NULL
的列来 "绕过"SQL 语法限制,是一种为了兼容兼容性或特殊场景设计的取巧机制。
比如,假设我们有一张表,所有列都不允许为空,而我们又想插入一条空数据,如果没有 nullColumnHack 且 ContentValues 为空,INSERT 语句就无法正确构造。有了 nullColumnHack,我们可以指定某一列(比如name列),让其值为 NULL,从而使 INSERT 语句合法。
(3)Uri.parse
把拼接的字符串转化为Uri
(4)ContentUris.withAppendedId(contentUri, id)
contentUri表示一个内容集合的uri
比如 content://authority/table_uesr
,指的就是user这个表
追加一个标识具体资源的 ID,指向确定的某一行数据
(5)通信约定
content://${AUTHORITY}/${TABLE_NAME}
这种格式,是 Android 官方推荐的 ContentProvider
Uri
规范格式;
本质是一种通信约定“要操作哪个 Provider、哪个表 / 资源”,然后通过 Uri.parse 将这个字符串解析为 Uri 对象,供 ContentResolver 等组件识别和操作。
"content://"
是协议头,表明这是ContentProvider
的Uri
(类似于 HTTP 协议的http://
)AUTHORITY
是当前ContentProvider
的授权标识,确保能定位到正确的ContentProvider
TABLE_NAME
作为path
部分,用于指定要操作的数据表(因为这里是对数据库表进行插入操作,所以用表名作为路径来标识数据所在的 “位置”)。
四:跨进程清单和权限设置
1:数据提供A方
(1)provider声明
<providerandroid:name=".MyContentProvider"android:authorities="com.xlong.providerprojecttest.provider"android:exported="true"android:grantUriPermissions="true"/>
- grantUriPermission:授权Uri许可,方便其它的工程通过uri来进行访问
2:数据使用B方
<!--在安卓11的设备上需要添加如下声明,表示要访问目标应用中的数据--><queries><package android:name="com.xlong.providerprojecttest"/></queries>
跟权限的声明同级别,允许当前应用去访问一个特定的应用,低版本可以不需要这个声明。
"com.xlong.providerprojecttest"
来自A应用下图
五:跨进程对数据库进行增删查改
1:查
/*** 查询数据*/findViewById(R.id.btn_query).setOnClickListener(view -> {Cursor cursor = getContentResolver().query(uri, null, null,null, null);if(cursor != null){while(cursor.moveToNext()){int nameIndex = cursor.getColumnIndexOrThrow("name");String name = cursor.getString(nameIndex);Log.i(TAG, "onCreate: query name = " + name);}}});
2:增
/*** 插入数据*/findViewById(R.id.btn_insert).setOnClickListener(view -> {ContentValues values = new ContentValues();values.put("name","sansan");values.put("age",22);Uri insert = getContentResolver().insert(uri, values);Log.i(TAG, "onCreate: 插入了" + insert.toString());});
3:改
/*** 更新数据*/findViewById(R.id.btn_update).setOnClickListener(view -> {ContentValues values = new ContentValues();values.put("name","hantianzun");int update = getContentResolver().update(uri, values, "name = ?", new String[]{"zhangsan"});Log.i(TAG, "onCreate: 更新 update =" + update);});
4:删
/*** 删除数据*/findViewById(R.id.btn_delete).setOnClickListener(view -> {int delete = getContentResolver().delete(uri, "name = ?", new String[]{"lisi"});Log.i(TAG, "onCreate: delete " + delete);});
外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传