Binder总体把握:
什么是Binder?
知识储备
进程空间划分
- 一个进程分为用户空间和内核空间,即把进程内的用户和内核隔离开
- 二者的区别:
- 进程间,用户空间的数据不共享,所以用户空间就是不可共享的空间
- 进程间,内核空间的数据可共享,所以内核空间就是可共享的空间
所有进程共用1个内核空间
- 进程内的用户空间和内核空间之间进行交互需要通过系统调用,主要通过函数:
- copy_from_user() : 将用户空间的数据拷贝到内核空间
- copy_to_user() : 将内核空间的数据拷贝到用户空间
图片如下:
进程隔离、跨进程通信(IPC)
-
进程隔离
为了保证安全性、独立性,一个进程不能直接操作或者访问另一个进程,即Android的进程是相互独立、隔离的 -
跨进程通信(IPC)
即进程间需要进行数据交互、通信 -
跨进程通信的基本原理
a.Binder的作用:连接两个进程,实现了mmap()系统调用,主要负责创建数据接收的缓存空间和管理数据接收缓存
b.传统的跨进程通信需要拷贝数据两次,但Binder机制只需要1次,主要是使用了内存映射
Binder的跨进程通信机制模型
模型原理图
## 模型组成角色说明
- 简介
- 工作流程
模型原理步骤说明
额外说明
-
client进程、server进程、service Manager进程之间的交互都必须通过Binder驱动而非直接交互
原因: client、server、service manager进程属于用户空间,不可进行进程间交互;Binder驱动属于内核空间,可以进行进程间的交互原理图如下: 虚线表示非直接交互
-
Binder驱动、Service Manager进程属于Android基础架构(已经实现好的);而Client进程和Server进程属于Android应用层(需要开发者自己实现)。所以,在进行跨进程通信时,开发者只需要自定义Client、Server进程并显示使用上述3个步骤,最终借助Android的基本架构功能就可完成进程间通信
-
Binder请求的线程管理
- Server进程会创建很多线程来处理Binder请求
- Binder模型的线程管理采用Binder驱动的线程池,并由Binder驱动自身进行管理,而不是由Server进程来管理的
- 一个进程的Binder线程默认最大为16,超过的请求会阻塞等待空闲的Binder线程
-
Binder的接收缓存区
binder实际上并没有在内核区中创建接收缓存区,实际的接收缓存区是创建在了接收方进程中,只是在发送方产生了一个映射,在接收方也产生了一个映射。本质上是内存映射原理。不然的话假如接收缓存区是创建在内核区中的话那么依然是两次拷贝,也就是说内核区中的只是一个映射
Binder机制的优点
ContentProvider
它是Android是四大组件之一,是用来在进程间进行数据交互和共享的,也就是跨进程通信,底层是采用的Android中的Binder机制
大体把握:
统一资源标识符(URI)
- 作用:唯一标识ContentProvider和其中的数据。外界进程通过URI找到对应的ContentProvider和其中的数据,再进行数据操作
- 具体使用
URI分为系统预置、自定义,分别对应系统内置的数据和自定义数据库
MIME数据类型
- 作用:指定某个扩展名的文件用某种应用程序打开
- 组成: 每种MIME类型由两部分组成: 类型 + 子类型
- 类型形式:单条记录、多条记录(集合)
ContentProvider类
ContentProvider主要以表格的形式组织数据,同时也支持文件数据,不过表格形式用得比较多。每个表格中包含多张表,每张表的行、列分别对应记录、字段
主要方法
-
进程间共享数据的本质:添加、删除、获取、修改数据
-
ContentProvider的核心方法也是上述四个作用:
// 外部进程向 ContentProvider 中添加数据 public int delete(Uri uri, String selection, String[] selectionArgs) // 外部进程 删除 ContentProvider 中的数据 public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs) // 外部进程更新 ContentProvider 中的数据 public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder) // 外部应用 获取 ContentProvider 中的数据 // 注: // 1. 上述4个方法由外部进程回调,并运行在ContentProvider进程的Binder线程池中(不是主线程) // 2. 存在多线程并发访问,需要实现线程同步 // a. 若ContentProvider的数据存储方式是使用SQLite & 一个,则不需要,因为SQLite内部实现好了线程同步,若是多个SQLite则需要,因为SQL对象之间无法进行线程同步 // b. 若ContentProvider的数据存储方式是内存,则需要自己实现线程同步 <-- 2个其他方法 --> public boolean onCreate() // ContentProvider创建后 或 打开系统后其它进程第一次访问该ContentProvider时 由系统进行调用 // 注:运行在ContentProvider进程的主线程,故不能做耗时操作 public String getType(Uri uri) // 得到数据类型,即返回当前 Url 所代表数据的MIME类型
-
Android为常见的数据(通讯录、日程表等)提供了内置的默认的ContentProvider
-
ContentProvider类并不会直接与外部进程交互,而是通过ContentResolver类
ContentResolver类
作用
统一管理不同ContentProvider间的操作
- 即通过URI即可操作不同的ContentProvider中的数据
- 外部进程通过ContentResolver类从而与ContentProvider类进行交互
用ContentResolver类的原因
一般来说,一款应用要使用多个ContentProvider,若需要了解每个ContentProvider的不同实现从而完成数据交互,操作成本高、难度大。所以就在ContentProvider类上加了一个ContentResolver类对所有的ContentProvider进行统一管理
具体使用
ContentResolver类提供了与ContentProvider类相同名字和作用的4个方法
public Uri insert(Uri uri, ContentValues values)
// 外部进程 删除 ContentProvider 中的数据
public int delete(Uri uri, String selection, String[] selectionArgs)
// 外部进程更新 ContentProvider 中的数据
public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs)
// 外部应用 获取 ContentProvider 中的数据
public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder)
实例说明:
// 可通过在所有继承Context的类中 通过调用getContentResolver()来获得ContentResolver
ContentResolver resolver = getContentResolver();
// 设置ContentProvider的URI
Uri uri = Uri.parse("content://cn.scu.myprovider/user");
// 根据URI 操作 ContentProvider中的数据
// 此处是获取ContentProvider中 user表的所有记录
Cursor cursor = resolver.query(uri, null, null, null, "userid desc");
Android提供了3个用于辅助ContentPRovider的工具类:
- ContentUris
- UriMatcher
- ContentObserver
ContentUris
- 作用:操作URI
- 具体使用
核心方法有两个:withAppendId()、parseId()
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中对应的数据表
-
具体使用
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中的数据变化、通知外界(即访问该数据访问者)
具体使用:
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类进行解除
优点
安全
ContentProvider为应用间的数据交互提供了一个安全的环境:允许把自己的应用数据根据需求开放给其他应用进行增、删、改、查,而不用担心因为直接开放数据库权限而带来的安全问题
访问简单、高效
对比于其他对外共享数据的方式,数据访问方式会因数据存储的方式而不同
- 采用文件方式对外存储数据,需要进行文件操作读写数据
- 采用SharedPreferences共享数据,需要使用SharedPreferences API读写数据
- 而采用ContentProvider方式,解耦了底层数据的存储方式,使得无论底层数据存储采取何种方式,外界对数据的访问方式都是统一的
比如一开始数据存储方式采用SQLite数据库,后来换成MongoDB,也不会对上层ContentProvider使用代码产生影响